Merge branch 'staging' into dist-generic-library
This commit is contained in:
commit
89f917ecc8
27
.github/dependabot.yml
vendored
Normal file
27
.github/dependabot.yml
vendored
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
# To get started with Dependabot version updates, you'll need to specify which
|
||||||
|
# package ecosystems to update and where the package manifests are located.
|
||||||
|
# Please see the documentation for all configuration options:
|
||||||
|
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||||
|
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "npm" # See documentation for possible values
|
||||||
|
directory: "/" # Location of package manifests
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
|
- package-ecosystem: "npm" # See documentation for possible values
|
||||||
|
directory: "/packages/squiggle-lang" # Location of package manifests
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
|
- package-ecosystem: "npm" # See documentation for possible values
|
||||||
|
directory: "/packages/components" # Location of package manifests
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
|
- package-ecosystem: "npm" # See documentation for possible values
|
||||||
|
directory: "/packages/website" # Location of package manifests
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
|
- package-ecosystem: "npm" # See documentation for possible values
|
||||||
|
directory: "/packages/playground" # Location of package manifests
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
6
.github/workflows/ci.yaml
vendored
6
.github/workflows/ci.yaml
vendored
|
@ -79,7 +79,9 @@ jobs:
|
||||||
working-directory: packages/website
|
working-directory: packages/website
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Install dependencies
|
- name: Install dependencies from monorepo level
|
||||||
run: yarn
|
run: cd ../../ && yarn
|
||||||
|
- name: Build rescript in squiggle-lang
|
||||||
|
run: cd ../squiggle-lang && yarn build
|
||||||
- name: Build website assets
|
- name: Build website assets
|
||||||
run: yarn build
|
run: yarn build
|
||||||
|
|
70
.github/workflows/codeql-analysis.yml
vendored
Normal file
70
.github/workflows/codeql-analysis.yml
vendored
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
# For most projects, this workflow file will not need changing; you simply need
|
||||||
|
# to commit it to your repository.
|
||||||
|
#
|
||||||
|
# You may wish to alter this file to override the set of languages analyzed,
|
||||||
|
# or to provide custom queries or build logic.
|
||||||
|
#
|
||||||
|
# ******** NOTE ********
|
||||||
|
# We have attempted to detect the languages in your repository. Please check
|
||||||
|
# the `language` matrix defined below to confirm you have the correct set of
|
||||||
|
# supported CodeQL languages.
|
||||||
|
#
|
||||||
|
name: "CodeQL"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ master ]
|
||||||
|
pull_request:
|
||||||
|
# The branches below must be a subset of the branches above
|
||||||
|
branches: [ master ]
|
||||||
|
schedule:
|
||||||
|
- cron: '42 19 * * 0'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
analyze:
|
||||||
|
name: Analyze
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
actions: read
|
||||||
|
contents: read
|
||||||
|
security-events: write
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
language: [ 'javascript' ]
|
||||||
|
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
|
||||||
|
# Learn more about CodeQL language support at https://git.io/codeql-language-support
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
# Initializes the CodeQL tools for scanning.
|
||||||
|
- name: Initialize CodeQL
|
||||||
|
uses: github/codeql-action/init@v1
|
||||||
|
with:
|
||||||
|
languages: ${{ matrix.language }}
|
||||||
|
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||||
|
# By default, queries listed here will override any specified in a config file.
|
||||||
|
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||||
|
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||||
|
|
||||||
|
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||||
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
|
- name: Autobuild
|
||||||
|
uses: github/codeql-action/autobuild@v1
|
||||||
|
|
||||||
|
# ℹ️ Command-line programs to run using the OS shell.
|
||||||
|
# 📚 https://git.io/JvXDl
|
||||||
|
|
||||||
|
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||||
|
# and modify them (or add more) to build your code if your project
|
||||||
|
# uses a compiled language
|
||||||
|
|
||||||
|
#- run: |
|
||||||
|
# make bootstrap
|
||||||
|
# make release
|
||||||
|
|
||||||
|
- name: Perform CodeQL Analysis
|
||||||
|
uses: github/codeql-action/analyze@v1
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -4,3 +4,4 @@ yarn-error.log
|
||||||
.merlin
|
.merlin
|
||||||
.parcel-cache
|
.parcel-cache
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
**/.sync.ffs_db
|
||||||
|
|
|
@ -36,6 +36,15 @@ You need `yarn`.
|
||||||
|
|
||||||
TODO: fill this out based on all the different packages scripts once they cool down.
|
TODO: fill this out based on all the different packages scripts once they cool down.
|
||||||
|
|
||||||
|
## If you're on NixOS
|
||||||
|
|
||||||
|
You'll need to run a command like this in order to get `yarn build` to run, especially in `packages/squiggle-lang`.
|
||||||
|
```sh
|
||||||
|
patchelf --set-interpreter $(patchelf --print-interpreter $(which mkdir)) ./node_modules/gentype/gentype.exe
|
||||||
|
```
|
||||||
|
|
||||||
|
See [here](https://github.com/NixOS/nixpkgs/issues/107375)
|
||||||
|
|
||||||
# Pull request protocol
|
# Pull request protocol
|
||||||
|
|
||||||
Please work against `staging` branch. **Do not** work against `master`. Please do not merge without approval from some subset of Quinn, Sam, and Ozzie; they will be auto-pinged.
|
Please work against `staging` branch. **Do not** work against `master`. Please do not merge without approval from some subset of Quinn, Sam, and Ozzie; they will be auto-pinged.
|
||||||
|
|
|
@ -44,6 +44,7 @@
|
||||||
yarn2nix
|
yarn2nix
|
||||||
nodePackages.npm
|
nodePackages.npm
|
||||||
nodejs
|
nodejs
|
||||||
|
patchelf
|
||||||
(pkgs.vscode-with-extensions.override {
|
(pkgs.vscode-with-extensions.override {
|
||||||
vscode = pkgs.vscodium;
|
vscode = pkgs.vscodium;
|
||||||
vscodeExtensions = pkgs.vscode-utils.extensionsFromVscodeMarketplace [
|
vscodeExtensions = pkgs.vscode-utils.extensionsFromVscodeMarketplace [
|
||||||
|
|
|
@ -3,14 +3,14 @@
|
||||||
"version": "0.1.6",
|
"version": "0.1.6",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@quri/squiggle-lang": "0.2.2",
|
"@quri/squiggle-lang": "0.2.2",
|
||||||
"@testing-library/jest-dom": "^5.16.2",
|
"@testing-library/jest-dom": "^5.16.3",
|
||||||
"@testing-library/react": "^12.1.2",
|
"@testing-library/react": "^12.1.2",
|
||||||
"@testing-library/user-event": "^13.5.0",
|
"@testing-library/user-event": "^13.5.0",
|
||||||
"@types/jest": "^27.4.0",
|
"@types/jest": "^27.4.0",
|
||||||
"@types/lodash": "^4.14.178",
|
"@types/lodash": "^4.14.178",
|
||||||
"@types/node": "^17.0.16",
|
"@types/node": "^17.0.16",
|
||||||
"@types/react": "^17.0.39",
|
"@types/react": "^17.0.43",
|
||||||
"@types/react-dom": "^17.0.11",
|
"@types/react-dom": "^17.0.14",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
|
@ -18,7 +18,7 @@
|
||||||
"react-scripts": "5.0.0",
|
"react-scripts": "5.0.0",
|
||||||
"react-vega": "^7.4.4",
|
"react-vega": "^7.4.4",
|
||||||
"tsconfig-paths-webpack-plugin": "^3.5.2",
|
"tsconfig-paths-webpack-plugin": "^3.5.2",
|
||||||
"typescript": "^4.5.5",
|
"typescript": "^4.6.3",
|
||||||
"vega": "^5.21.0",
|
"vega": "^5.21.0",
|
||||||
"vega-embed": "^6.20.6",
|
"vega-embed": "^6.20.6",
|
||||||
"vega-lite": "^5.2.0",
|
"vega-lite": "^5.2.0",
|
||||||
|
@ -77,7 +77,7 @@
|
||||||
"webpack-dev-server": "^4.7.4"
|
"webpack-dev-server": "^4.7.4"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"@types/react": "17.0.39"
|
"@types/react": "17.0.43"
|
||||||
},
|
},
|
||||||
"source": "./src/index.ts",
|
"source": "./src/index.ts",
|
||||||
"main": "dist/bundle.js",
|
"main": "dist/bundle.js",
|
||||||
|
|
|
@ -13,11 +13,11 @@ import * as chartSpecification from "./spec-distributions.json";
|
||||||
import * as percentilesSpec from "./spec-percentiles.json";
|
import * as percentilesSpec from "./spec-percentiles.json";
|
||||||
|
|
||||||
let SquiggleVegaChart = createClassFromSpec({
|
let SquiggleVegaChart = createClassFromSpec({
|
||||||
spec: chartSpecification as Spec,
|
spec: chartSpecification as Spec
|
||||||
});
|
});
|
||||||
|
|
||||||
let SquigglePercentilesChart = createClassFromSpec({
|
let SquigglePercentilesChart = createClassFromSpec({
|
||||||
spec: percentilesSpec as Spec,
|
spec: percentilesSpec as Spec
|
||||||
});
|
});
|
||||||
|
|
||||||
export interface SquiggleChartProps {
|
export interface SquiggleChartProps {
|
||||||
|
@ -74,7 +74,7 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = (props) => {
|
||||||
y: y,
|
y: y,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return <SquiggleVegaChart data={{ con: values }} />;
|
return <SquiggleVegaChart data={{ con: values }} actions={false}/>;
|
||||||
} else if (shape.tag === "Discrete") {
|
} else if (shape.tag === "Discrete") {
|
||||||
let xyShape = shape.value.xyShape;
|
let xyShape = shape.value.xyShape;
|
||||||
let totalY = xyShape.ys.reduce((a, b) => a + b);
|
let totalY = xyShape.ys.reduce((a, b) => a + b);
|
||||||
|
@ -89,7 +89,7 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = (props) => {
|
||||||
y: y,
|
y: y,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return <SquiggleVegaChart data={{ dis: values }} />;
|
return <SquiggleVegaChart data={{ dis: values }} actions={false}/>;
|
||||||
} else if (shape.tag === "Mixed") {
|
} else if (shape.tag === "Mixed") {
|
||||||
let discreteShape = shape.value.discrete.xyShape;
|
let discreteShape = shape.value.discrete.xyShape;
|
||||||
let totalDiscrete = discreteShape.ys.reduce((a, b) => a + b);
|
let totalDiscrete = discreteShape.ys.reduce((a, b) => a + b);
|
||||||
|
@ -156,6 +156,7 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = (props) => {
|
||||||
return (
|
return (
|
||||||
<SquiggleVegaChart
|
<SquiggleVegaChart
|
||||||
data={{ con: continuousValues, dis: discreteValues }}
|
data={{ con: continuousValues, dis: discreteValues }}
|
||||||
|
actions={false}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -163,7 +164,7 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = (props) => {
|
||||||
// We are looking at a function. In this case, we draw a Percentiles chart
|
// We are looking at a function. In this case, we draw a Percentiles chart
|
||||||
let start = props.diagramStart ? props.diagramStart : 0;
|
let start = props.diagramStart ? props.diagramStart : 0;
|
||||||
let stop = props.diagramStop ? props.diagramStop : 10;
|
let stop = props.diagramStop ? props.diagramStop : 10;
|
||||||
let count = props.diagramCount ? props.diagramCount : 0.1;
|
let count = props.diagramCount ? props.diagramCount : 100;
|
||||||
let step = (stop - start) / count;
|
let step = (stop - start) / count;
|
||||||
let data = _.range(start, stop, step).map((x) => {
|
let data = _.range(start, stop, step).map((x) => {
|
||||||
if (chartResult.NAME == "Function") {
|
if (chartResult.NAME == "Function") {
|
||||||
|
@ -192,10 +193,13 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = (props) => {
|
||||||
p99: percentiles[12],
|
p99: percentiles[12],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
return 0;
|
|
||||||
});
|
});
|
||||||
return <SquigglePercentilesChart data={{ facet: data }} />;
|
return <SquigglePercentilesChart
|
||||||
|
data={{ facet: data.filter(x => x !== null) }}
|
||||||
|
actions={false}
|
||||||
|
/>;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return <>{chartResults}</>;
|
return <>{chartResults}</>;
|
||||||
|
|
|
@ -25,11 +25,7 @@ export interface SquiggleEditorProps {
|
||||||
onEnvChange?(env: exportEnv): void;
|
onEnvChange?(env: exportEnv): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const highlight = (editor: HTMLInputElement) => {
|
const highlight = (_: HTMLInputElement) => {};
|
||||||
let code = editor.textContent;
|
|
||||||
code = code.replace(/\((\w+?)(\b)/g, '(<font color="#8a2be2">$1</font>$2');
|
|
||||||
editor.innerHTML = code;
|
|
||||||
};
|
|
||||||
|
|
||||||
interface SquiggleEditorState {
|
interface SquiggleEditorState {
|
||||||
expression: string;
|
expression: string;
|
||||||
|
|
|
@ -84,7 +84,8 @@
|
||||||
"y": { "scale": "yscale", "field": "y" },
|
"y": { "scale": "yscale", "field": "y" },
|
||||||
"y2": { "scale": "yscale", "value": 0 },
|
"y2": { "scale": "yscale", "value": 0 },
|
||||||
"fill": {
|
"fill": {
|
||||||
"signal": "{gradient: 'linear', x1: 1, y1: 1, x2: 0, y2: 1, stops: [ {offset: 0.0, color: 'steelblue'}, {offset: clamp(mousex, 0, 1), color: 'steelblue'}, {offset: clamp(mousex, 0, 1), color: 'blue'}, {offset: 1.0, color: 'blue'} ] }"
|
"signal": "{gradient: 'linear', x1: 1, y1: 1, x2: 0, y2: 1, stops: [ {offset: 0.0, color: '#11ac8f'}, {offset: clamp(mousex, 0, 1), color: '#11ac8f'}, {offset: clamp(mousex, 0, 1), color: '#1b6fac'}, {offset: 1.0, color: '#1b6fac'} ] }",
|
||||||
|
"color": "#000"
|
||||||
},
|
},
|
||||||
"interpolate": { "value": "monotone" },
|
"interpolate": { "value": "monotone" },
|
||||||
"fillOpacity": { "value": 1 }
|
"fillOpacity": { "value": 1 }
|
||||||
|
|
22
packages/components/src/stories/SquiggleEditor.stories.mdx
Normal file
22
packages/components/src/stories/SquiggleEditor.stories.mdx
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import { SquiggleEditor } from "../SquiggleEditor";
|
||||||
|
import { Canvas, Meta, Story, Props } from "@storybook/addon-docs";
|
||||||
|
|
||||||
|
<Meta title="Squiggle/SquiggleEditor" component={SquiggleEditor} />
|
||||||
|
|
||||||
|
export const Template = (props) => <SquiggleEditor {...props} />;
|
||||||
|
|
||||||
|
# Squiggle Editor
|
||||||
|
|
||||||
|
Squiggle Editor is a Squiggle chart with a text editor included for changing
|
||||||
|
the distribution.
|
||||||
|
|
||||||
|
<Canvas>
|
||||||
|
<Story
|
||||||
|
name="Normal"
|
||||||
|
args={{
|
||||||
|
initialSquiggleString: "normal(5,2)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Template.bind({})}
|
||||||
|
</Story>
|
||||||
|
</Canvas>
|
|
@ -18,15 +18,15 @@
|
||||||
"antd": "^4.18.5",
|
"antd": "^4.18.5",
|
||||||
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.2",
|
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.2",
|
||||||
"binary-search-tree": "0.2.6",
|
"binary-search-tree": "0.2.6",
|
||||||
"css-loader": "^6.6.0",
|
"css-loader": "^6.7.1",
|
||||||
"gh-pages": "2.2.0",
|
"gh-pages": "3.2.3",
|
||||||
"jstat": "1.9.2",
|
"jstat": "1.9.5",
|
||||||
"lenses-ppx": "5.1.0",
|
"lenses-ppx": "6.1.10",
|
||||||
"less": "3.10.3",
|
"less": "4.1.2",
|
||||||
"lodash": "4.17.15",
|
"lodash": "4.17.21",
|
||||||
"mathjs": "5.10.3",
|
"mathjs": "10.4.1",
|
||||||
"moduleserve": "0.9.1",
|
"moduleserve": "0.9.1",
|
||||||
"moment": "2.24.0",
|
"moment": "2.29.1",
|
||||||
"pdfast": "^0.2.0",
|
"pdfast": "^0.2.0",
|
||||||
"rationale": "0.2.0",
|
"rationale": "0.2.0",
|
||||||
"react": "17.0.2",
|
"react": "17.0.2",
|
||||||
|
@ -35,20 +35,20 @@
|
||||||
"react-use": "^17.3.2",
|
"react-use": "^17.3.2",
|
||||||
"react-vega": "^7.4.4",
|
"react-vega": "^7.4.4",
|
||||||
"vega": "*",
|
"vega": "*",
|
||||||
"vega-embed": "6.6.0",
|
"vega-embed": "6.20.8",
|
||||||
"vega-lite": "*"
|
"vega-lite": "*"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@emotion/babel-plugin": "^11.7.2",
|
"@emotion/babel-plugin": "^11.7.2",
|
||||||
"@parcel/core": "^2.3.2",
|
"@parcel/core": "^2.4.0",
|
||||||
"@types/react": "^17.0.39",
|
"@types/react": "^17.0.43",
|
||||||
"autoprefixer": "^10.4.2",
|
"autoprefixer": "^10.4.2",
|
||||||
"docsify": "^4.12.2",
|
"docsify": "^4.12.2",
|
||||||
"jest": "^27.5.1",
|
"jest": "^27.5.1",
|
||||||
"parcel": "^2.3.2",
|
"parcel": "^2.4.0",
|
||||||
"postcss": "^8.4.7",
|
"postcss": "^8.4.7",
|
||||||
"postcss-cli": "^9.1.0",
|
"postcss-cli": "^9.1.0",
|
||||||
"tailwindcss": "^3.0.23",
|
"tailwindcss": "^3.0.23",
|
||||||
"typescript": "^4.6.2"
|
"typescript": "^4.6.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,3 +81,9 @@ complicated, as it has to return either a number, or a distribution, or even
|
||||||
a representation of a function of distributions. Currently the export is simply
|
a representation of a function of distributions. Currently the export is simply
|
||||||
the generated type that rescript creates, and can be quite confusing. We therefore
|
the generated type that rescript creates, and can be quite confusing. We therefore
|
||||||
highly recommend the use of typescript when creating tests or using this package.
|
highly recommend the use of typescript when creating tests or using this package.
|
||||||
|
|
||||||
|
## Potential Issues
|
||||||
|
If you experiment with generating different types of .gen.ts files and similar, note that they won't be caught by git (because they are in .gitignore). Make sure you delete these extra files, once they are unecessary.
|
||||||
|
```
|
||||||
|
rm src/rescript/**/*.gen.ts
|
||||||
|
```
|
|
@ -0,0 +1,25 @@
|
||||||
|
module ExpressionValue = ReducerInterface.ExpressionValue
|
||||||
|
|
||||||
|
open Jest
|
||||||
|
open Expect
|
||||||
|
|
||||||
|
let expectEvalToBe = (expr: string, answer: string) =>
|
||||||
|
Reducer.eval(expr)->ExpressionValue.toStringResult->expect->toBe(answer)
|
||||||
|
|
||||||
|
describe("builtin", () => {
|
||||||
|
// All MathJs operators and functions are available for string, number and boolean
|
||||||
|
// .e.g + - / * > >= < <= == /= not and or
|
||||||
|
// See https://mathjs.org/docs/expressions/syntax.html
|
||||||
|
// See https://mathjs.org/docs/reference/functions.html
|
||||||
|
test("-1", () => expectEvalToBe("-1", "Ok(-1)"))
|
||||||
|
test("1-1", () => expectEvalToBe("1-1", "Ok(0)"))
|
||||||
|
test("2>1", () => expectEvalToBe("2>1", "Ok(true)"))
|
||||||
|
test("concat('a','b')", () => expectEvalToBe("concat('a','b')", "Ok('ab')"))
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("builtin exception", () => {
|
||||||
|
//It's a pity that MathJs does not return error position
|
||||||
|
test("MathJs Exception", () =>
|
||||||
|
expectEvalToBe("testZadanga()", "Error(JS Exception: Error: Undefined function testZadanga)")
|
||||||
|
)
|
||||||
|
})
|
|
@ -0,0 +1,32 @@
|
||||||
|
open ReducerInterface.ExpressionValue
|
||||||
|
module MathJs = Reducer.MathJs
|
||||||
|
module ErrorValue = Reducer.ErrorValue
|
||||||
|
|
||||||
|
open Jest
|
||||||
|
open ExpectJs
|
||||||
|
|
||||||
|
describe("eval", () => {
|
||||||
|
test("Number", () => expect(MathJs.Eval.eval("1"))->toEqual(Ok(EvNumber(1.))))
|
||||||
|
test("Number expr", () => expect(MathJs.Eval.eval("1-1"))->toEqual(Ok(EvNumber(0.))))
|
||||||
|
test("String", () => expect(MathJs.Eval.eval("'hello'"))->toEqual(Ok(EvString("hello"))))
|
||||||
|
test("String expr", () =>
|
||||||
|
expect(MathJs.Eval.eval("concat('hello ','world')"))->toEqual(Ok(EvString("hello world")))
|
||||||
|
)
|
||||||
|
test("Boolean", () => expect(MathJs.Eval.eval("true"))->toEqual(Ok(EvBool(true))))
|
||||||
|
test("Boolean expr", () => expect(MathJs.Eval.eval("2>1"))->toEqual(Ok(EvBool(true))))
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("errors", () => {
|
||||||
|
// All those errors propagete up and are returned by the resolver
|
||||||
|
test("unknown function", () =>
|
||||||
|
expect(MathJs.Eval.eval("testZadanga()"))->toEqual(
|
||||||
|
Error(ErrorValue.REJavaScriptExn(Some("Undefined function testZadanga"), Some("Error"))),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
test("unknown answer type", () =>
|
||||||
|
expect(MathJs.Eval.eval("1+1i"))->toEqual(
|
||||||
|
Error(ErrorValue.RETodo("Unhandled MathJs literal type: object")),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
})
|
|
@ -0,0 +1,51 @@
|
||||||
|
module Parse = Reducer.MathJs.Parse
|
||||||
|
module Result = Belt.Result
|
||||||
|
|
||||||
|
open Jest
|
||||||
|
open Expect
|
||||||
|
|
||||||
|
let expectParseToBe = (expr, answer) =>
|
||||||
|
Parse.parse(expr)->Result.flatMap(Parse.castNodeType)->Parse.toStringResult->expect->toBe(answer)
|
||||||
|
|
||||||
|
describe("MathJs parse", () => {
|
||||||
|
describe("literals operators paranthesis", () => {
|
||||||
|
test("1", () => expectParseToBe("1", "1"))
|
||||||
|
test("'hello'", () => expectParseToBe("'hello'", "'hello'"))
|
||||||
|
test("true", () => expectParseToBe("true", "true"))
|
||||||
|
test("1+2", () => expectParseToBe("1+2", "add(1, 2)"))
|
||||||
|
test("add(1,2)", () => expectParseToBe("add(1,2)", "add(1, 2)"))
|
||||||
|
test("(1)", () => expectParseToBe("(1)", "(1)"))
|
||||||
|
test("(1+2)", () => expectParseToBe("(1+2)", "(add(1, 2))"))
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("variables", () => {
|
||||||
|
Skip.test("define", () => expectParseToBe("x = 1", "???"))
|
||||||
|
Skip.test("use", () => expectParseToBe("x", "???"))
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("functions", () => {
|
||||||
|
Skip.test("define", () => expectParseToBe("identity(x) = x", "???"))
|
||||||
|
Skip.test("use", () => expectParseToBe("identity(x)", "???"))
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("arrays", () => {
|
||||||
|
test("empty", () => expectParseToBe("[]", "[]"))
|
||||||
|
test("define", () => expectParseToBe("[0, 1, 2]", "[0, 1, 2]"))
|
||||||
|
test("define with strings", () => expectParseToBe("['hello', 'world']", "['hello', 'world']"))
|
||||||
|
Skip.test("range", () => expectParseToBe("range(0, 4)", "range(0, 4)"))
|
||||||
|
test("index", () => expectParseToBe("([0,1,2])[1]", "([0, 1, 2])[1]"))
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("records", () => {
|
||||||
|
test("define", () => expectParseToBe("{a: 1, b: 2}", "{a: 1, b: 2}"))
|
||||||
|
test("use", () => expectParseToBe("record.property", "record['property']"))
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("comments", () => {
|
||||||
|
Skip.test("define", () => expectParseToBe("# This is a comment", "???"))
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("if statement", () => {
|
||||||
|
Skip.test("define", () => expectParseToBe("if (true) { 1 } else { 0 }", "???"))
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,14 @@
|
||||||
|
module Expression = Reducer.Expression
|
||||||
|
module ExpressionValue = ReducerInterface.ExpressionValue
|
||||||
|
|
||||||
|
open Jest
|
||||||
|
open Expect
|
||||||
|
|
||||||
|
let expectParseToBe = (expr: string, answer: string) =>
|
||||||
|
Reducer.parse(expr)->Expression.toStringResult->expect->toBe(answer)
|
||||||
|
|
||||||
|
let expectEvalToBe = (expr: string, answer: string) =>
|
||||||
|
Reducer.eval(expr)->ExpressionValue.toStringResult->expect->toBe(answer)
|
||||||
|
|
||||||
|
// Current configuration does not ignore this file so we have to have a test
|
||||||
|
test("test helpers", () => expect(1)->toBe(1))
|
81
packages/squiggle-lang/__tests__/Reducer/Reducer_test.res
Normal file
81
packages/squiggle-lang/__tests__/Reducer/Reducer_test.res
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
open Jest
|
||||||
|
open Reducer_TestHelpers
|
||||||
|
|
||||||
|
describe("reducer using mathjs parse", () => {
|
||||||
|
// Test the MathJs parser compatibility
|
||||||
|
// Those tests toString that there is a semantic mapping from MathJs to Expression
|
||||||
|
// Reducer.parse is called by Reducer.eval
|
||||||
|
// See https://mathjs.org/docs/expressions/syntax.html
|
||||||
|
// See https://mathjs.org/docs/reference/functions.html
|
||||||
|
// Those tests toString that we are converting mathjs parse tree to what we need
|
||||||
|
|
||||||
|
describe("expressions", () => {
|
||||||
|
test("1", () => expectParseToBe("1", "Ok(1)"))
|
||||||
|
test("(1)", () => expectParseToBe("(1)", "Ok(1)"))
|
||||||
|
test("1+2", () => expectParseToBe("1+2", "Ok((:add 1 2))"))
|
||||||
|
test("(1+2)", () => expectParseToBe("1+2", "Ok((:add 1 2))"))
|
||||||
|
test("add(1,2)", () => expectParseToBe("1+2", "Ok((:add 1 2))"))
|
||||||
|
test("1+2*3", () => expectParseToBe("1+2*3", "Ok((:add 1 (:multiply 2 3)))"))
|
||||||
|
})
|
||||||
|
describe("arrays", () => {
|
||||||
|
//Note. () is a empty list in Lisp
|
||||||
|
// The only builtin structure in Lisp is list. There are no arrays
|
||||||
|
// [1,2,3] becomes (1 2 3)
|
||||||
|
test("empty", () => expectParseToBe("[]", "Ok(())"))
|
||||||
|
test("[1, 2, 3]", () => expectParseToBe("[1, 2, 3]", "Ok((1 2 3))"))
|
||||||
|
test("['hello', 'world']", () => expectParseToBe("['hello', 'world']", "Ok(('hello' 'world'))"))
|
||||||
|
test("index", () => expectParseToBe("([0,1,2])[1]", "Ok((:$atIndex (0 1 2) (1)))"))
|
||||||
|
})
|
||||||
|
describe("records", () => {
|
||||||
|
test("define", () =>
|
||||||
|
expectParseToBe("{a: 1, b: 2}", "Ok((:$constructRecord (('a' 1) ('b' 2))))")
|
||||||
|
)
|
||||||
|
test("use", () =>
|
||||||
|
expectParseToBe(
|
||||||
|
"{a: 1, b: 2}.a",
|
||||||
|
"Ok((:$atIndex (:$constructRecord (('a' 1) ('b' 2))) ('a')))",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("eval", () => {
|
||||||
|
// All MathJs operators and functions are builtin for string, float and boolean
|
||||||
|
// .e.g + - / * > >= < <= == /= not and or
|
||||||
|
// See https://mathjs.org/docs/expressions/syntax.html
|
||||||
|
// See https://mathjs.org/docs/reference/functions.html
|
||||||
|
describe("expressions", () => {
|
||||||
|
test("1", () => expectEvalToBe("1", "Ok(1)"))
|
||||||
|
test("1+2", () => expectEvalToBe("1+2", "Ok(3)"))
|
||||||
|
test("(1+2)*3", () => expectEvalToBe("(1+2)*3", "Ok(9)"))
|
||||||
|
test("2>1", () => expectEvalToBe("2>1", "Ok(true)"))
|
||||||
|
test("concat('a ', 'b')", () => expectEvalToBe("concat('a ', 'b')", "Ok('a b')"))
|
||||||
|
test("log(10)", () => expectEvalToBe("log(10)", "Ok(2.302585092994046)"))
|
||||||
|
test("cos(10)", () => expectEvalToBe("cos(10)", "Ok(-0.8390715290764524)"))
|
||||||
|
// TODO more built ins
|
||||||
|
})
|
||||||
|
describe("arrays", () => {
|
||||||
|
test("empty array", () => expectEvalToBe("[]", "Ok([])"))
|
||||||
|
test("[1, 2, 3]", () => expectEvalToBe("[1, 2, 3]", "Ok([1, 2, 3])"))
|
||||||
|
test("['hello', 'world']", () => expectEvalToBe("['hello', 'world']", "Ok(['hello', 'world'])"))
|
||||||
|
test("index", () => expectEvalToBe("([0,1,2])[1]", "Ok(1)"))
|
||||||
|
test("index not found", () =>
|
||||||
|
expectEvalToBe("([0,1,2])[10]", "Error(Array index not found: 10)")
|
||||||
|
)
|
||||||
|
})
|
||||||
|
describe("records", () => {
|
||||||
|
test("define", () => expectEvalToBe("{a: 1, b: 2}", "Ok({a: 1, b: 2})"))
|
||||||
|
test("index", () => expectEvalToBe("{a: 1}.a", "Ok(1)"))
|
||||||
|
test("index not found", () => expectEvalToBe("{a: 1}.b", "Error(Record property not found: b)"))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("test exceptions", () => {
|
||||||
|
test("javascript exception", () =>
|
||||||
|
expectEvalToBe("jsraise('div by 0')", "Error(JS Exception: Error: 'div by 0')")
|
||||||
|
)
|
||||||
|
|
||||||
|
test("rescript exception", () =>
|
||||||
|
expectEvalToBe("resraise()", "Error(TODO: unhandled rescript exception)")
|
||||||
|
)
|
||||||
|
})
|
|
@ -0,0 +1,11 @@
|
||||||
|
open ReducerInterface.ExpressionValue
|
||||||
|
open Jest
|
||||||
|
open Expect
|
||||||
|
|
||||||
|
describe("ExpressionValue", () => {
|
||||||
|
test("argsToString", () => expect([EvNumber(1.), EvString("a")]->argsToString)->toBe("1, 'a'"))
|
||||||
|
|
||||||
|
test("toStringFunctionCall", () =>
|
||||||
|
expect(("fn", [EvNumber(1.), EvString("a")])->toStringFunctionCall)->toBe("fn(1, 'a')")
|
||||||
|
)
|
||||||
|
})
|
33
packages/squiggle-lang/__tests__/Symbolic_test.res
Normal file
33
packages/squiggle-lang/__tests__/Symbolic_test.res
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
open Jest
|
||||||
|
open Expect
|
||||||
|
open Js.Array
|
||||||
|
open SymbolicDist
|
||||||
|
|
||||||
|
let makeTest = (~only=false, str, item1, item2) =>
|
||||||
|
only
|
||||||
|
? Only.test(str, () => expect(item1) -> toEqual(item2))
|
||||||
|
: test(str, () => expect(item1) -> toEqual(item2))
|
||||||
|
|
||||||
|
let pdfImage = (thePdf, inps) => map(thePdf, inps)
|
||||||
|
|
||||||
|
let parameterWiseAdditionHelper = (n1: SymbolicDistTypes.normal, n2: SymbolicDistTypes.normal) => {
|
||||||
|
let normalDistAtSumMeanConstr = Normal.add(n1, n2)
|
||||||
|
let normalDistAtSumMean: SymbolicDistTypes.normal = switch normalDistAtSumMeanConstr {
|
||||||
|
| #Normal(params) => params
|
||||||
|
}
|
||||||
|
x => Normal.pdf(x, normalDistAtSumMean)
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("Normal distribution with sparklines", () => {
|
||||||
|
|
||||||
|
let normalDistAtMean5: SymbolicDistTypes.normal = {mean: 5.0, stdev: 2.0}
|
||||||
|
let normalDistAtMean10: SymbolicDistTypes.normal = {mean: 10.0, stdev: 2.0}
|
||||||
|
let range20Float = E.A.rangeFloat(0, 20) // [0.0,1.0,2.0,3.0,4.0,...19.0,]
|
||||||
|
|
||||||
|
let pdfNormalDistAtMean5 = x => Normal.pdf(x, normalDistAtMean5)
|
||||||
|
let sparklineMean5 = pdfImage(pdfNormalDistAtMean5, range20Float)
|
||||||
|
makeTest("mean=5", Sparklines.create(sparklineMean5, ()), `▁▂▃▅███▅▃▂▁▁▁▁▁▁▁▁▁▁▁`)
|
||||||
|
|
||||||
|
let sparklineMean15 = normalDistAtMean5 -> parameterWiseAdditionHelper(normalDistAtMean10) -> pdfImage(range20Float)
|
||||||
|
makeTest("parameter-wise addition of two normal distributions", Sparklines.create(sparklineMean15, ()), `▁▁▁▁▁▁▁▁▁▁▂▃▅▇███▇▅▃▂`)
|
||||||
|
})
|
|
@ -3,7 +3,7 @@
|
||||||
"reason": {},
|
"reason": {},
|
||||||
"sources": [
|
"sources": [
|
||||||
{
|
{
|
||||||
"dir": "src",
|
"dir": "src/rescript",
|
||||||
"subdirs": true
|
"subdirs": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
"bundle": "webpack",
|
"bundle": "webpack",
|
||||||
"start": "rescript build -w -with-deps",
|
"start": "rescript build -w -with-deps",
|
||||||
"clean": "rescript clean",
|
"clean": "rescript clean",
|
||||||
|
"test:reducer": "jest --testPathPattern '.*__tests__/Reducer.*'",
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
"test:watch": "jest --watchAll",
|
"test:watch": "jest --watchAll",
|
||||||
"all": "yarn build && yarn bundle && yarn test"
|
"all": "yarn build && yarn bundle && yarn test"
|
||||||
|
@ -19,8 +20,8 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@glennsl/bs-json": "^5.0.2",
|
"@glennsl/bs-json": "^5.0.2",
|
||||||
"jstat": "^1.9.5",
|
"jstat": "^1.9.5",
|
||||||
"lodash": "4.17.15",
|
"lodash": "4.17.21",
|
||||||
"mathjs": "5.10.3",
|
"mathjs": "10.4.1",
|
||||||
"pdfast": "^0.2.0",
|
"pdfast": "^0.2.0",
|
||||||
"rationale": "0.2.0",
|
"rationale": "0.2.0",
|
||||||
"rescript": "^9.1.4"
|
"rescript": "^9.1.4"
|
||||||
|
@ -34,9 +35,9 @@
|
||||||
"gentype": "^4.3.0",
|
"gentype": "^4.3.0",
|
||||||
"jest": "^27.5.1",
|
"jest": "^27.5.1",
|
||||||
"moduleserve": "0.9.1",
|
"moduleserve": "0.9.1",
|
||||||
"ts-jest": "^27.1.3",
|
"ts-jest": "^27.1.4",
|
||||||
"ts-loader": "^9.2.8",
|
"ts-loader": "^9.2.8",
|
||||||
"typescript": "^4.5.5",
|
"typescript": "^4.6.3",
|
||||||
"webpack": "^5.70.0",
|
"webpack": "^5.70.0",
|
||||||
"webpack-cli": "^4.9.2"
|
"webpack-cli": "^4.9.2"
|
||||||
},
|
},
|
||||||
|
|
17
packages/squiggle-lang/src/rescript/Reducer/README.md
Normal file
17
packages/squiggle-lang/src/rescript/Reducer/README.md
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
To interface your library there only 2 files to be modified:
|
||||||
|
|
||||||
|
- Reducer/ReducerInterface/ReducerInterface_ExpressionValue.res
|
||||||
|
|
||||||
|
This is where your additional types are referred for the dispatcher.
|
||||||
|
|
||||||
|
- Reducer/ReducerInterface/ReducerInterface_ExternalLibrary.res
|
||||||
|
|
||||||
|
This is where dispatching to your library is done. If the dispatcher becomes beastly then feel free to divide it into submodules.
|
||||||
|
|
||||||
|
The Reducer is built to use different external libraries as well as different external parsers. Both external parsers and external libraries are plugins.
|
||||||
|
|
||||||
|
And finally try using Reducer.eval to how your extentions look:
|
||||||
|
|
||||||
|
```rescript
|
||||||
|
test("1+2", () => expectEvalToBe( "1+2", "Ok(3)"))
|
||||||
|
```
|
9
packages/squiggle-lang/src/rescript/Reducer/Reducer.res
Normal file
9
packages/squiggle-lang/src/rescript/Reducer/Reducer.res
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
module Dispatch = Reducer_Dispatch
|
||||||
|
module ErrorValue = Reducer_ErrorValue
|
||||||
|
module Expression = Reducer_Expression
|
||||||
|
module Extra = Reducer_Extra
|
||||||
|
module Js = Reducer_Js
|
||||||
|
module MathJs = Reducer_MathJs
|
||||||
|
|
||||||
|
let eval = Expression.eval
|
||||||
|
let parse = Expression.parse
|
8
packages/squiggle-lang/src/rescript/Reducer/Reducer.resi
Normal file
8
packages/squiggle-lang/src/rescript/Reducer/Reducer.resi
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
module Dispatch = Reducer_Dispatch
|
||||||
|
module ErrorValue = Reducer_ErrorValue
|
||||||
|
module Expression = Reducer_Expression
|
||||||
|
module Extra = Reducer_Extra
|
||||||
|
module Js = Reducer_Js
|
||||||
|
module MathJs = Reducer_MathJs
|
||||||
|
let eval: string => result<Expression.expressionValue, ErrorValue.errorValue>
|
||||||
|
let parse: string => result<Expression.expression, ErrorValue.errorValue>
|
|
@ -0,0 +1 @@
|
||||||
|
module Builtin = Reducer_Dispatch_BuiltIn
|
|
@ -0,0 +1,72 @@
|
||||||
|
module ExternalLibrary = ReducerInterface.ExternalLibrary
|
||||||
|
module MathJs = Reducer_MathJs
|
||||||
|
open ReducerInterface.ExpressionValue
|
||||||
|
open Reducer_ErrorValue
|
||||||
|
|
||||||
|
/*
|
||||||
|
MathJs provides default implementations for builtins
|
||||||
|
This is where all the expected builtins like + = * / sin cos log ln etc are handled
|
||||||
|
DO NOT try to add external function mapping here!
|
||||||
|
*/
|
||||||
|
|
||||||
|
exception TestRescriptException
|
||||||
|
|
||||||
|
let callInternal = (call: functionCall): result<'b, errorValue> => {
|
||||||
|
let callMathJs = (call: functionCall): result<'b, errorValue> =>
|
||||||
|
switch call {
|
||||||
|
| ("jsraise", [msg]) => Js.Exn.raiseError(toString(msg)) // For Tests
|
||||||
|
| ("resraise", _) => raise(TestRescriptException) // For Tests
|
||||||
|
| call => call->toStringFunctionCall->MathJs.Eval.eval
|
||||||
|
}
|
||||||
|
|
||||||
|
let constructRecord = arrayOfPairs => {
|
||||||
|
Belt.Array.map(arrayOfPairs, pairValue => {
|
||||||
|
switch pairValue {
|
||||||
|
| EvArray([EvString(key), valueValue]) => (key, valueValue)
|
||||||
|
| _ => ("wrong key type", pairValue->toStringWithType->EvString)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
->Js.Dict.fromArray
|
||||||
|
->EvRecord
|
||||||
|
->Ok
|
||||||
|
}
|
||||||
|
|
||||||
|
let arrayAtIndex = (aValueArray: array<expressionValue>, fIndex: float) =>
|
||||||
|
switch Belt.Array.get(aValueArray, Belt.Int.fromFloat(fIndex)) {
|
||||||
|
| Some(value) => value->Ok
|
||||||
|
| None => REArrayIndexNotFound("Array index not found", Belt.Int.fromFloat(fIndex))->Error
|
||||||
|
}
|
||||||
|
|
||||||
|
let recordAtIndex = (dict: Js.Dict.t<expressionValue>, sIndex) =>
|
||||||
|
switch Js.Dict.get(dict, sIndex) {
|
||||||
|
| Some(value) => value->Ok
|
||||||
|
| None => RERecordPropertyNotFound("Record property not found", sIndex)->Error
|
||||||
|
}
|
||||||
|
|
||||||
|
switch call {
|
||||||
|
// | ("$constructRecord", pairArray)
|
||||||
|
// | ("$atIndex", [EvArray(anArray), EvNumber(fIndex)]) => arrayAtIndex(anArray, fIndex)
|
||||||
|
// | ("$atIndex", [EvRecord(aRecord), EvString(sIndex)]) => recordAtIndex(aRecord, sIndex)
|
||||||
|
| ("$constructRecord", [EvArray(arrayOfPairs)]) => constructRecord(arrayOfPairs)
|
||||||
|
| ("$atIndex", [EvArray(aValueArray), EvArray([EvNumber(fIndex)])]) =>
|
||||||
|
arrayAtIndex(aValueArray, fIndex)
|
||||||
|
| ("$atIndex", [EvRecord(dict), EvArray([EvString(sIndex)])]) => recordAtIndex(dict, sIndex)
|
||||||
|
| ("$atIndex", [obj, index]) =>
|
||||||
|
(toStringWithType(obj) ++ "??~~~~" ++ toStringWithType(index))->EvString->Ok
|
||||||
|
| call => callMathJs(call)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Lisp engine uses Result monad while reducing expressions
|
||||||
|
*/
|
||||||
|
let dispatch = (call: functionCall): result<expressionValue, errorValue> =>
|
||||||
|
try {
|
||||||
|
let (fn, args) = call
|
||||||
|
// There is a bug that prevents string match in patterns
|
||||||
|
// So we have to recreate a copy of the string
|
||||||
|
ExternalLibrary.dispatch((Js.String.make(fn), args), callInternal)
|
||||||
|
} catch {
|
||||||
|
| Js.Exn.Error(obj) => REJavaScriptExn(Js.Exn.message(obj), Js.Exn.name(obj))->Error
|
||||||
|
| _ => RETodo("unhandled rescript exception")->Error
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
type errorValue =
|
||||||
|
| REArrayIndexNotFound(string, int)
|
||||||
|
| REFunctionExpected(string)
|
||||||
|
| REJavaScriptExn(option<string>, option<string>) // Javascript Exception
|
||||||
|
| RERecordPropertyNotFound(string, string)
|
||||||
|
| RETodo(string) // To do
|
||||||
|
|
||||||
|
type t = errorValue
|
||||||
|
|
||||||
|
let errorToString = err =>
|
||||||
|
switch err {
|
||||||
|
| REArrayIndexNotFound(msg, index) => `${msg}: ${Js.String.make(index)}`
|
||||||
|
| REFunctionExpected(msg) => `Function expected: ${msg}`
|
||||||
|
| REJavaScriptExn(omsg, oname) => {
|
||||||
|
let answer = "JS Exception:"
|
||||||
|
let answer = switch oname {
|
||||||
|
| Some(name) => `${answer} ${name}`
|
||||||
|
| _ => answer
|
||||||
|
}
|
||||||
|
let answer = switch omsg {
|
||||||
|
| Some(msg) => `${answer}: ${msg}`
|
||||||
|
| _ => answer
|
||||||
|
}
|
||||||
|
answer
|
||||||
|
}
|
||||||
|
| RERecordPropertyNotFound(msg, index) => `${msg}: ${index}`
|
||||||
|
| RETodo(msg) => `TODO: ${msg}`
|
||||||
|
}
|
|
@ -0,0 +1,91 @@
|
||||||
|
module BuiltIn = Reducer_Dispatch_BuiltIn
|
||||||
|
module ExpressionValue = ReducerInterface.ExpressionValue
|
||||||
|
module Extra = Reducer_Extra
|
||||||
|
module MathJs = Reducer_MathJs
|
||||||
|
module Result = Belt.Result
|
||||||
|
module T = Reducer_Expression_T
|
||||||
|
open Reducer_ErrorValue
|
||||||
|
|
||||||
|
type expression = T.expression
|
||||||
|
type expressionValue = ExpressionValue.expressionValue
|
||||||
|
type t = expression
|
||||||
|
|
||||||
|
/*
|
||||||
|
Shows the Lisp Code as text lisp code
|
||||||
|
*/
|
||||||
|
let rec toString = expression =>
|
||||||
|
switch expression {
|
||||||
|
| T.EList(aList) =>
|
||||||
|
`(${Belt.List.map(aList, aValue => toString(aValue))
|
||||||
|
->Extra.List.interperse(" ")
|
||||||
|
->Belt.List.toArray
|
||||||
|
->Js.String.concatMany("")})`
|
||||||
|
| EValue(aValue) => ExpressionValue.toString(aValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
let toStringResult = codeResult =>
|
||||||
|
switch codeResult {
|
||||||
|
| Ok(a) => `Ok(${toString(a)})`
|
||||||
|
| Error(m) => `Error(${Js.String.make(m)})`
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Converts a MathJs code to Lisp Code
|
||||||
|
*/
|
||||||
|
let parse_ = (expr: string, parser, converter): result<t, errorValue> =>
|
||||||
|
expr->parser->Result.flatMap(node => converter(node))
|
||||||
|
|
||||||
|
let parse = (mathJsCode: string): result<t, errorValue> =>
|
||||||
|
mathJsCode->parse_(MathJs.Parse.parse, MathJs.ToExpression.fromNode)
|
||||||
|
|
||||||
|
module MapString = Belt.Map.String
|
||||||
|
type bindings = MapString.t<unit>
|
||||||
|
let defaultBindings: bindings = MapString.fromArray([])
|
||||||
|
// TODO Define bindings for function execution context
|
||||||
|
|
||||||
|
/*
|
||||||
|
After reducing each level of code tree, we have a value list to evaluate
|
||||||
|
*/
|
||||||
|
let reduceValueList = (valueList: list<expressionValue>): result<expressionValue, 'e> =>
|
||||||
|
switch valueList {
|
||||||
|
| list{EvSymbol(fName), ...args} => (fName, args->Belt.List.toArray)->BuiltIn.dispatch
|
||||||
|
| _ => valueList->Belt.List.toArray->ExpressionValue.EvArray->Ok
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Recursively evaluate/reduce the code tree
|
||||||
|
*/
|
||||||
|
let rec reduceExpression = (expression: t, bindings): result<expressionValue, 'e> =>
|
||||||
|
switch expression {
|
||||||
|
| T.EValue(value) => value->Ok
|
||||||
|
| T.EList(list) => {
|
||||||
|
let racc: result<list<expressionValue>, 'e> = list->Belt.List.reduceReverse(Ok(list{}), (
|
||||||
|
racc,
|
||||||
|
each: expression,
|
||||||
|
) =>
|
||||||
|
racc->Result.flatMap(acc => {
|
||||||
|
each
|
||||||
|
->reduceExpression(bindings)
|
||||||
|
->Result.flatMap(newNode => {
|
||||||
|
acc->Belt.List.add(newNode)->Ok
|
||||||
|
})
|
||||||
|
})
|
||||||
|
)
|
||||||
|
racc->Result.flatMap(acc => acc->reduceValueList)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let evalWBindingsExpression = (aExpression, bindings): result<expressionValue, 'e> =>
|
||||||
|
reduceExpression(aExpression, bindings)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Evaluates MathJs code via Lisp using bindings and answers the result
|
||||||
|
*/
|
||||||
|
let evalWBindings = (codeText: string, bindings: bindings) => {
|
||||||
|
parse(codeText)->Result.flatMap(code => code->evalWBindingsExpression(bindings))
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Evaluates MathJs code via Lisp and answers the result
|
||||||
|
*/
|
||||||
|
let eval = (code: string) => evalWBindings(code, defaultBindings)
|
|
@ -0,0 +1,28 @@
|
||||||
|
module Result = Belt.Result
|
||||||
|
module T = Reducer_Expression_T
|
||||||
|
type expression = T.expression
|
||||||
|
type expressionValue = ReducerInterface.ExpressionValue.expressionValue
|
||||||
|
type t = expression
|
||||||
|
let toString: T.expression => Js.String.t
|
||||||
|
let toStringResult: result<T.expression, 'a> => string
|
||||||
|
let parse: string => result<expression, Reducer_ErrorValue.t>
|
||||||
|
module MapString = Belt.Map.String
|
||||||
|
type bindings = MapString.t<unit>
|
||||||
|
let defaultBindings: bindings
|
||||||
|
let reduceValueList: list<expressionValue> => result<
|
||||||
|
expressionValue,
|
||||||
|
Reducer_ErrorValue.t,
|
||||||
|
>
|
||||||
|
let reduceExpression: (expression, 'a) => result<
|
||||||
|
expressionValue,
|
||||||
|
Reducer_ErrorValue.t,
|
||||||
|
>
|
||||||
|
let evalWBindingsExpression: (expression, 'a) => result<
|
||||||
|
expressionValue,
|
||||||
|
Reducer_ErrorValue.t,
|
||||||
|
>
|
||||||
|
let evalWBindings: (string, bindings) => Result.t<
|
||||||
|
expressionValue,
|
||||||
|
Reducer_ErrorValue.t,
|
||||||
|
>
|
||||||
|
let eval: string => Result.t<expressionValue, Reducer_ErrorValue.t>
|
|
@ -0,0 +1,5 @@
|
||||||
|
open ReducerInterface.ExpressionValue
|
||||||
|
|
||||||
|
type rec expression =
|
||||||
|
| EList(list<expression>) // A list to map-reduce
|
||||||
|
| EValue(expressionValue) // Irreducible built-in value. Reducer should not know the internals. External libraries are responsible
|
|
@ -0,0 +1,2 @@
|
||||||
|
module Array = Reducer_Extra_Array
|
||||||
|
module List = Reducer_Extra_List
|
|
@ -0,0 +1,7 @@
|
||||||
|
/*
|
||||||
|
Insert seperator between the elements of an array
|
||||||
|
*/
|
||||||
|
module ExtraList = Reducer_Extra_List
|
||||||
|
|
||||||
|
let interperse = (anArray, seperator) =>
|
||||||
|
anArray->Belt.List.fromArray->ExtraList.interperse(seperator)->Belt.List.toArray
|
|
@ -0,0 +1,9 @@
|
||||||
|
/*
|
||||||
|
Insert seperator between the elements of a list
|
||||||
|
*/
|
||||||
|
let rec interperse = (aList, seperator) =>
|
||||||
|
switch aList {
|
||||||
|
| list{} => list{}
|
||||||
|
| list{a} => list{a}
|
||||||
|
| list{a, ...rest} => list{a, seperator, ...interperse(rest, seperator)}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
module Gate = Reducer_Js_Gate
|
|
@ -0,0 +1,18 @@
|
||||||
|
open ReducerInterface.ExpressionValue
|
||||||
|
open Reducer_ErrorValue
|
||||||
|
|
||||||
|
external castBool: unit => bool = "%identity"
|
||||||
|
external castNumber: unit => float = "%identity"
|
||||||
|
external castString: unit => string = "%identity"
|
||||||
|
|
||||||
|
/*
|
||||||
|
As JavaScript returns us any type, we need to type check and cast type propertype before using it
|
||||||
|
*/
|
||||||
|
let jsToEv = (jsValue): result<expressionValue, errorValue> => {
|
||||||
|
switch Js.typeof(jsValue) {
|
||||||
|
| "boolean" => jsValue->castBool->EvBool->Ok
|
||||||
|
| "number" => jsValue->castNumber->EvNumber->Ok
|
||||||
|
| "string" => jsValue->castString->EvString->Ok
|
||||||
|
| other => RETodo(`Unhandled MathJs literal type: ${Js.String.make(other)}`)->Error
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
module Eval = Reducer_MathJs_Eval
|
||||||
|
module Parse = Reducer_MathJs_Parse
|
||||||
|
module ToExpression = Reducer_MathJs_ToExpression
|
|
@ -0,0 +1,27 @@
|
||||||
|
module JavaScript = Reducer_Js
|
||||||
|
open ReducerInterface.ExpressionValue
|
||||||
|
open Reducer_ErrorValue
|
||||||
|
|
||||||
|
@module("mathjs") external dummy_: string => unit = "evaluate"
|
||||||
|
let dummy1_ = dummy_ //Deceive the compiler to make the import although we wont make a call from rescript. Otherwise the optimizer deletes the import
|
||||||
|
|
||||||
|
type answer = {"value": unit}
|
||||||
|
|
||||||
|
/*
|
||||||
|
The result has to be delivered in an object so that we can type cast.
|
||||||
|
Rescript cannot type cast on basic values passed on their own.
|
||||||
|
This is why we call evalua inside Javascript and wrap the result in an Object
|
||||||
|
*/
|
||||||
|
let eval__ = %raw(`function (expr) { return {value: Mathjs.evaluate(expr)}; }`)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Call MathJs evaluate and return as a variant
|
||||||
|
*/
|
||||||
|
let eval = (expr: string): result<expressionValue, errorValue> => {
|
||||||
|
try {
|
||||||
|
let answer = eval__(expr)
|
||||||
|
answer["value"]->JavaScript.Gate.jsToEv
|
||||||
|
} catch {
|
||||||
|
| Js.Exn.Error(obj) => REJavaScriptExn(Js.Exn.message(obj), Js.Exn.name(obj))->Error
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,123 @@
|
||||||
|
/*
|
||||||
|
MathJs Nodes
|
||||||
|
We make MathJs Nodes strong-typed
|
||||||
|
*/
|
||||||
|
module Extra = Reducer_Extra
|
||||||
|
open Reducer_ErrorValue
|
||||||
|
|
||||||
|
type node = {"type": string, "isNode": bool, "comment": string}
|
||||||
|
type arrayNode = {...node, "items": array<node>}
|
||||||
|
//assignmentNode
|
||||||
|
//blockNode
|
||||||
|
//conditionalNode
|
||||||
|
type constantNode = {...node, "value": unit}
|
||||||
|
//functionAssignmentNode
|
||||||
|
type functionNode = {...node, "fn": string, "args": array<node>}
|
||||||
|
type indexNode = {...node, "dimensions": array<node>}
|
||||||
|
type objectNode = {...node, "properties": Js.Dict.t<node>}
|
||||||
|
type accessorNode = {...node, "object": node, "index": indexNode}
|
||||||
|
type operatorNode = {...functionNode, "op": string}
|
||||||
|
|
||||||
|
//parenthesisNode
|
||||||
|
type parenthesisNode = {...node, "content": node}
|
||||||
|
//rangeNode
|
||||||
|
//relationalNode
|
||||||
|
type symbolNode = {...node, "name": string}
|
||||||
|
|
||||||
|
external castAccessorNode: node => accessorNode = "%identity"
|
||||||
|
external castArrayNode: node => arrayNode = "%identity"
|
||||||
|
external castConstantNode: node => constantNode = "%identity"
|
||||||
|
external castFunctionNode: node => functionNode = "%identity"
|
||||||
|
external castIndexNode: node => indexNode = "%identity"
|
||||||
|
external castObjectNode: node => objectNode = "%identity"
|
||||||
|
external castOperatorNode: node => operatorNode = "%identity"
|
||||||
|
external castOperatorNodeToFunctionNode: operatorNode => functionNode = "%identity"
|
||||||
|
external castParenthesisNode: node => parenthesisNode = "%identity"
|
||||||
|
external castSymbolNode: node => symbolNode = "%identity"
|
||||||
|
|
||||||
|
/*
|
||||||
|
MathJs Parser
|
||||||
|
*/
|
||||||
|
@module("mathjs") external parse__: string => node = "parse"
|
||||||
|
|
||||||
|
let parse = (expr: string): result<node, errorValue> =>
|
||||||
|
try {
|
||||||
|
Ok(parse__(expr))
|
||||||
|
} catch {
|
||||||
|
| Js.Exn.Error(obj) => REJavaScriptExn(Js.Exn.message(obj), Js.Exn.name(obj))->Error
|
||||||
|
}
|
||||||
|
|
||||||
|
type mathJsNode =
|
||||||
|
| MjAccessorNode(accessorNode)
|
||||||
|
| MjArrayNode(arrayNode)
|
||||||
|
| MjConstantNode(constantNode)
|
||||||
|
| MjFunctionNode(functionNode)
|
||||||
|
| MjIndexNode(indexNode)
|
||||||
|
| MjObjectNode(objectNode)
|
||||||
|
| MjOperatorNode(operatorNode)
|
||||||
|
| MjParenthesisNode(parenthesisNode)
|
||||||
|
| MjSymbolNode(symbolNode)
|
||||||
|
|
||||||
|
let castNodeType = (node: node) =>
|
||||||
|
switch node["type"] {
|
||||||
|
| "AccessorNode" => node->castAccessorNode->MjAccessorNode->Ok
|
||||||
|
| "ArrayNode" => node->castArrayNode->MjArrayNode->Ok
|
||||||
|
| "ConstantNode" => node->castConstantNode->MjConstantNode->Ok
|
||||||
|
| "FunctionNode" => node->castFunctionNode->MjFunctionNode->Ok
|
||||||
|
| "IndexNode" => node->castIndexNode->MjIndexNode->Ok
|
||||||
|
| "ObjectNode" => node->castObjectNode->MjObjectNode->Ok
|
||||||
|
| "OperatorNode" => node->castOperatorNode->MjOperatorNode->Ok
|
||||||
|
| "ParenthesisNode" => node->castParenthesisNode->MjParenthesisNode->Ok
|
||||||
|
| "SymbolNode" => node->castSymbolNode->MjSymbolNode->Ok
|
||||||
|
| _ => RETodo(`Argg, unhandled MathJsNode: ${node["type"]}`)->Error
|
||||||
|
}
|
||||||
|
|
||||||
|
let rec toString = (mathJsNode: mathJsNode): string => {
|
||||||
|
let toStringValue = (a: 'a): string =>
|
||||||
|
if Js.typeof(a) == "string" {
|
||||||
|
`'${Js.String.make(a)}'`
|
||||||
|
} else {
|
||||||
|
Js.String.make(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
let toStringNodeArray = (nodeArray: array<node>): string =>
|
||||||
|
nodeArray
|
||||||
|
->Belt.Array.map(a => toStringMathJsNode(a))
|
||||||
|
->Extra.Array.interperse(", ")
|
||||||
|
->Js.String.concatMany("")
|
||||||
|
|
||||||
|
let toStringFunctionNode = (fnode: functionNode): string =>
|
||||||
|
`${fnode["fn"]}(${fnode["args"]->toStringNodeArray})`
|
||||||
|
|
||||||
|
let toStringObjectEntry = ((key: string, value: node)): string => `${key}: ${value->toStringMathJsNode}`
|
||||||
|
|
||||||
|
let toStringObjectNode = (oNode: objectNode): string =>
|
||||||
|
`{${oNode["properties"]
|
||||||
|
->Js.Dict.entries
|
||||||
|
->Belt.Array.map(entry => entry->toStringObjectEntry)
|
||||||
|
->Extra.Array.interperse(", ")
|
||||||
|
->Js.String.concatMany("")}}`
|
||||||
|
|
||||||
|
let toStringIndexNode = (iNode: indexNode): string =>
|
||||||
|
iNode["dimensions"]
|
||||||
|
->Belt.Array.map(each => toStringResult(each->castNodeType))
|
||||||
|
->Js.String.concatMany("")
|
||||||
|
|
||||||
|
switch mathJsNode {
|
||||||
|
| MjAccessorNode(aNode) => `${aNode["object"]->toStringMathJsNode}[${aNode["index"]->toStringIndexNode}]`
|
||||||
|
| MjArrayNode(aNode) => `[${aNode["items"]->toStringNodeArray}]`
|
||||||
|
| MjConstantNode(cNode) => cNode["value"]->toStringValue
|
||||||
|
| MjFunctionNode(fNode) => fNode->toStringFunctionNode
|
||||||
|
| MjIndexNode(iNode) => iNode->toStringIndexNode
|
||||||
|
| MjObjectNode(oNode) => oNode->toStringObjectNode
|
||||||
|
| MjOperatorNode(opNode) => opNode->castOperatorNodeToFunctionNode->toStringFunctionNode
|
||||||
|
| MjParenthesisNode(pNode) => `(${toStringMathJsNode(pNode["content"])})`
|
||||||
|
| MjSymbolNode(sNode) => sNode["name"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
and toStringResult = (rMathJsNode: result<mathJsNode, errorValue>): string =>
|
||||||
|
switch rMathJsNode {
|
||||||
|
| Error(e) => errorToString(e)
|
||||||
|
| Ok(mathJsNode) => toString(mathJsNode)
|
||||||
|
}
|
||||||
|
and toStringMathJsNode = node => node->castNodeType->toStringResult
|
|
@ -0,0 +1,86 @@
|
||||||
|
module ErrorValue = Reducer_ErrorValue
|
||||||
|
module ExpressionValue = ReducerInterface.ExpressionValue
|
||||||
|
module ExtressionT = Reducer_Expression_T
|
||||||
|
module JavaScript = Reducer_Js
|
||||||
|
module Parse = Reducer_MathJs_Parse
|
||||||
|
module Result = Belt.Result
|
||||||
|
|
||||||
|
type expression = ExtressionT.expression
|
||||||
|
type expressionValue = ExpressionValue.expressionValue
|
||||||
|
type errorValue = ErrorValue.errorValue
|
||||||
|
|
||||||
|
let rec fromNode = (mathJsNode: Parse.node): result<expression, errorValue> =>
|
||||||
|
Parse.castNodeType(mathJsNode)->Result.flatMap(typedMathJsNode => {
|
||||||
|
let fromNodeList = (nodeList: list<Parse.node>): result<list<expression>, 'e> =>
|
||||||
|
Belt.List.reduceReverse(nodeList, Ok(list{}), (racc, currNode) =>
|
||||||
|
racc->Result.flatMap(acc =>
|
||||||
|
fromNode(currNode)->Result.map(currCode => list{currCode, ...acc})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
let castFunctionNode = fNode => {
|
||||||
|
let fn = fNode["fn"]->ExpressionValue.EvSymbol->ExtressionT.EValue
|
||||||
|
let lispArgs = fNode["args"]->Belt.List.fromArray->fromNodeList
|
||||||
|
lispArgs->Result.map(argsCode => list{fn, ...argsCode}->ExtressionT.EList)
|
||||||
|
}
|
||||||
|
|
||||||
|
let caseObjectNode = oNode => {
|
||||||
|
let fromObjectEntries = entryList => {
|
||||||
|
let rargs = Belt.List.reduceReverse(entryList, Ok(list{}), (
|
||||||
|
racc,
|
||||||
|
(key: string, value: Parse.node),
|
||||||
|
) =>
|
||||||
|
racc->Result.flatMap(acc =>
|
||||||
|
fromNode(value)->Result.map(valueExpression => {
|
||||||
|
let entryCode =
|
||||||
|
list{
|
||||||
|
key->ExpressionValue.EvString->ExtressionT.EValue,
|
||||||
|
valueExpression,
|
||||||
|
}->ExtressionT.EList
|
||||||
|
list{entryCode, ...acc}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
let lispName = "$constructRecord"->ExpressionValue.EvSymbol->ExtressionT.EValue
|
||||||
|
rargs->Result.map(args => list{lispName, ExtressionT.EList(args)}->ExtressionT.EList)
|
||||||
|
}
|
||||||
|
|
||||||
|
oNode["properties"]->Js.Dict.entries->Belt.List.fromArray->fromObjectEntries
|
||||||
|
}
|
||||||
|
|
||||||
|
let caseIndexNode = iNode => {
|
||||||
|
let rpropertyCodeList = Belt.List.reduceReverse(
|
||||||
|
iNode["dimensions"]->Belt.List.fromArray,
|
||||||
|
Ok(list{}),
|
||||||
|
(racc, currentPropertyMathJsNode) =>
|
||||||
|
racc->Result.flatMap(acc =>
|
||||||
|
fromNode(currentPropertyMathJsNode)->Result.map(propertyCode => list{propertyCode, ...acc})
|
||||||
|
),
|
||||||
|
)
|
||||||
|
rpropertyCodeList->Result.map(propertyCodeList => ExtressionT.EList(propertyCodeList))
|
||||||
|
}
|
||||||
|
|
||||||
|
let caseAccessorNode = (objectNode, indexNode) => {
|
||||||
|
let fn = "$atIndex"->ExpressionValue.EvSymbol->ExtressionT.EValue
|
||||||
|
|
||||||
|
caseIndexNode(indexNode)->Result.flatMap(indexCode => {
|
||||||
|
fromNode(objectNode)->Result.map(objectCode =>
|
||||||
|
list{fn, objectCode, indexCode}->ExtressionT.EList
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
switch typedMathJsNode {
|
||||||
|
| MjArrayNode(aNode) =>
|
||||||
|
aNode["items"]->Belt.List.fromArray->fromNodeList->Result.map(list => ExtressionT.EList(list))
|
||||||
|
| MjConstantNode(cNode) =>
|
||||||
|
cNode["value"]->JavaScript.Gate.jsToEv->Result.map(v => v->ExtressionT.EValue)
|
||||||
|
| MjFunctionNode(fNode) => fNode->castFunctionNode
|
||||||
|
| MjOperatorNode(opNode) => opNode->Parse.castOperatorNodeToFunctionNode->castFunctionNode
|
||||||
|
| MjParenthesisNode(pNode) => pNode["content"]->fromNode
|
||||||
|
| MjAccessorNode(aNode) => caseAccessorNode(aNode["object"], aNode["index"])
|
||||||
|
| MjObjectNode(oNode) => caseObjectNode(oNode)
|
||||||
|
| MjSymbolNode(sNode) => sNode["name"]->ExpressionValue.EvSymbol->ExtressionT.EValue->Ok
|
||||||
|
| MjIndexNode(iNode) => caseIndexNode(iNode)
|
||||||
|
}
|
||||||
|
})
|
|
@ -0,0 +1,2 @@
|
||||||
|
module ExpressionValue = ReducerInterface_ExpressionValue
|
||||||
|
module ExternalLibrary = ReducerInterface_ExternalLibrary
|
|
@ -0,0 +1,60 @@
|
||||||
|
/*
|
||||||
|
Irreducible values. Reducer does not know about those. Only used for external calls
|
||||||
|
This is a configuration to to make external calls of those types
|
||||||
|
*/
|
||||||
|
module Extra_Array = Reducer_Extra_Array
|
||||||
|
module ErrorValue = Reducer_ErrorValue
|
||||||
|
|
||||||
|
type rec expressionValue =
|
||||||
|
| EvBool(bool)
|
||||||
|
| EvNumber(float)
|
||||||
|
| EvString(string)
|
||||||
|
| EvSymbol(string)
|
||||||
|
| EvArray(array<expressionValue>)
|
||||||
|
| EvRecord(Js.Dict.t<expressionValue>)
|
||||||
|
|
||||||
|
type functionCall = (string, array<expressionValue>)
|
||||||
|
|
||||||
|
let rec toString = aValue =>
|
||||||
|
switch aValue {
|
||||||
|
| EvBool(aBool) => Js.String.make(aBool)
|
||||||
|
| EvNumber(aNumber) => Js.String.make(aNumber)
|
||||||
|
| EvString(aString) => `'${aString}'`
|
||||||
|
| EvSymbol(aString) => `:${aString}`
|
||||||
|
| EvArray(anArray) => {
|
||||||
|
let args =
|
||||||
|
anArray->Belt.Array.map(each => toString(each))->Extra_Array.interperse(", ")->Js.String.concatMany("")
|
||||||
|
`[${args}]`
|
||||||
|
}
|
||||||
|
| EvRecord(aRecord) => {
|
||||||
|
let pairs =
|
||||||
|
aRecord
|
||||||
|
->Js.Dict.entries
|
||||||
|
->Belt.Array.map(((eachKey, eachValue)) => `${eachKey}: ${toString(eachValue)}`)
|
||||||
|
->Extra_Array.interperse(", ")
|
||||||
|
->Js.String.concatMany("")
|
||||||
|
`{${pairs}}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let toStringWithType = aValue =>
|
||||||
|
switch aValue {
|
||||||
|
| EvBool(_) => `Bool::${toString(aValue)}`
|
||||||
|
| EvNumber(_) => `Number::${toString(aValue)}`
|
||||||
|
| EvString(_) => `String::${toString(aValue)}`
|
||||||
|
| EvSymbol(_) => `Symbol::${toString(aValue)}`
|
||||||
|
| EvArray(_) => `Array::${toString(aValue)}`
|
||||||
|
| EvRecord(_) => `Record::${toString(aValue)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
let argsToString = (args: array<expressionValue>): string => {
|
||||||
|
args->Belt.Array.map(arg => arg->toString)->Extra_Array.interperse(", ")->Js.String.concatMany("")
|
||||||
|
}
|
||||||
|
|
||||||
|
let toStringFunctionCall = ((fn, args)): string => `${fn}(${argsToString(args)})`
|
||||||
|
|
||||||
|
let toStringResult = x =>
|
||||||
|
switch x {
|
||||||
|
| Ok(a) => `Ok(${toString(a)})`
|
||||||
|
| Error(m) => `Error(${ErrorValue.errorToString(m)})`
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
module ExpressionValue = ReducerInterface_ExpressionValue
|
||||||
|
|
||||||
|
type expressionValue = ExpressionValue.expressionValue
|
||||||
|
|
||||||
|
module Sample = {
|
||||||
|
// In real life real libraries should be somewhere else
|
||||||
|
/*
|
||||||
|
For an example of mapping polymorphic custom functions. To be deleted after real integration
|
||||||
|
*/
|
||||||
|
let customAdd = (a: float, b: float): float => {a +. b}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Map external calls of Reducer
|
||||||
|
*/
|
||||||
|
let dispatch = (call: ExpressionValue.functionCall, chain): result<expressionValue, 'e> =>
|
||||||
|
switch call {
|
||||||
|
| ("add", [EvNumber(a), EvNumber(b)]) => Sample.customAdd(a, b)->EvNumber->Ok
|
||||||
|
|
||||||
|
| call => chain(call)
|
||||||
|
|
||||||
|
/*
|
||||||
|
If your dispatch is too big you can divide it into smaller dispatches and pass the call so that it gets called finally.
|
||||||
|
|
||||||
|
The final chain(call) invokes the builtin default functions of the interpreter.
|
||||||
|
|
||||||
|
Via chain(call), all MathJs operators and functions are available for string, number , boolean, array and record
|
||||||
|
.e.g + - / * > >= < <= == /= not and or sin cos log ln concat, etc.
|
||||||
|
|
||||||
|
// See https://mathjs.org/docs/expressions/syntax.html
|
||||||
|
// See https://mathjs.org/docs/reference/functions.html
|
||||||
|
|
||||||
|
Remember from the users point of view, there are no different modules:
|
||||||
|
// "doSth( constructorType1 )"
|
||||||
|
// "doSth( constructorType2 )"
|
||||||
|
doSth gets dispatched to the correct module because of the type signature. You get function and operator abstraction for free. You don't need to combine different implementations into one type. That would be duplicating the repsonsibility of the dispatcher.
|
||||||
|
*/
|
||||||
|
}
|
|
@ -22,7 +22,7 @@ let makeSymbolicFromTwoFloats = (name, fn) =>
|
||||||
~inputTypes=[#Float, #Float],
|
~inputTypes=[#Float, #Float],
|
||||||
~run=x =>
|
~run=x =>
|
||||||
switch x {
|
switch x {
|
||||||
| [#Float(a), #Float(b)] => Ok(#SymbolicDist(fn(a, b)))
|
| [#Float(a), #Float(b)] => fn(a, b) |> E.R.fmap(r => (#SymbolicDist(r)))
|
||||||
| e => wrongInputsError(e)
|
| e => wrongInputsError(e)
|
||||||
},
|
},
|
||||||
(),
|
(),
|
||||||
|
@ -35,7 +35,7 @@ let makeSymbolicFromOneFloat = (name, fn) =>
|
||||||
~inputTypes=[#Float],
|
~inputTypes=[#Float],
|
||||||
~run=x =>
|
~run=x =>
|
||||||
switch x {
|
switch x {
|
||||||
| [#Float(a)] => Ok(#SymbolicDist(fn(a)))
|
| [#Float(a)] => fn(a) |> E.R.fmap(r => #SymbolicDist(r))
|
||||||
| e => wrongInputsError(e)
|
| e => wrongInputsError(e)
|
||||||
},
|
},
|
||||||
(),
|
(),
|
||||||
|
|
|
@ -2,7 +2,10 @@ open SymbolicDistTypes
|
||||||
|
|
||||||
module Normal = {
|
module Normal = {
|
||||||
type t = normal
|
type t = normal
|
||||||
let make = (mean, stdev): symbolicDist => #Normal({mean: mean, stdev: stdev})
|
let make = (mean: float, stdev: float): result<symbolicDist,string> =>
|
||||||
|
stdev > 0.0
|
||||||
|
? Ok(#Normal({mean: mean, stdev: stdev}))
|
||||||
|
: Error("Standard deviation of normal distribution must be larger than 0")
|
||||||
let pdf = (x, t: t) => Jstat.Normal.pdf(x, t.mean, t.stdev)
|
let pdf = (x, t: t) => Jstat.Normal.pdf(x, t.mean, t.stdev)
|
||||||
let cdf = (x, t: t) => Jstat.Normal.cdf(x, t.mean, t.stdev)
|
let cdf = (x, t: t) => Jstat.Normal.cdf(x, t.mean, t.stdev)
|
||||||
|
|
||||||
|
@ -45,10 +48,12 @@ module Normal = {
|
||||||
|
|
||||||
module Exponential = {
|
module Exponential = {
|
||||||
type t = exponential
|
type t = exponential
|
||||||
let make = (rate: float): symbolicDist =>
|
let make = (rate: float): result<symbolicDist,string> =>
|
||||||
#Exponential({
|
rate > 0.0
|
||||||
rate: rate,
|
? Ok(#Exponential({
|
||||||
})
|
rate: rate,
|
||||||
|
}))
|
||||||
|
: Error("Exponential distributions mean must be larger than 0")
|
||||||
let pdf = (x, t: t) => Jstat.Exponential.pdf(x, t.rate)
|
let pdf = (x, t: t) => Jstat.Exponential.pdf(x, t.rate)
|
||||||
let cdf = (x, t: t) => Jstat.Exponential.cdf(x, t.rate)
|
let cdf = (x, t: t) => Jstat.Exponential.cdf(x, t.rate)
|
||||||
let inv = (p, t: t) => Jstat.Exponential.inv(p, t.rate)
|
let inv = (p, t: t) => Jstat.Exponential.inv(p, t.rate)
|
||||||
|
@ -84,7 +89,10 @@ module Triangular = {
|
||||||
|
|
||||||
module Beta = {
|
module Beta = {
|
||||||
type t = beta
|
type t = beta
|
||||||
let make = (alpha, beta) => #Beta({alpha: alpha, beta: beta})
|
let make = (alpha, beta) =>
|
||||||
|
alpha > 0.0 && beta > 0.0
|
||||||
|
? Ok(#Beta({alpha: alpha, beta: beta}))
|
||||||
|
: Error("Beta distribution parameters must be positive")
|
||||||
let pdf = (x, t: t) => Jstat.Beta.pdf(x, t.alpha, t.beta)
|
let pdf = (x, t: t) => Jstat.Beta.pdf(x, t.alpha, t.beta)
|
||||||
let cdf = (x, t: t) => Jstat.Beta.cdf(x, t.alpha, t.beta)
|
let cdf = (x, t: t) => Jstat.Beta.cdf(x, t.alpha, t.beta)
|
||||||
let inv = (p, t: t) => Jstat.Beta.inv(p, t.alpha, t.beta)
|
let inv = (p, t: t) => Jstat.Beta.inv(p, t.alpha, t.beta)
|
||||||
|
@ -95,7 +103,10 @@ module Beta = {
|
||||||
|
|
||||||
module Lognormal = {
|
module Lognormal = {
|
||||||
type t = lognormal
|
type t = lognormal
|
||||||
let make = (mu, sigma) => #Lognormal({mu: mu, sigma: sigma})
|
let make = (mu, sigma) =>
|
||||||
|
sigma > 0.0
|
||||||
|
? Ok(#Lognormal({mu: mu, sigma: sigma}))
|
||||||
|
: Error("Lognormal standard deviation must be larger than 0")
|
||||||
let pdf = (x, t: t) => Jstat.Lognormal.pdf(x, t.mu, t.sigma)
|
let pdf = (x, t: t) => Jstat.Lognormal.pdf(x, t.mu, t.sigma)
|
||||||
let cdf = (x, t: t) => Jstat.Lognormal.cdf(x, t.mu, t.sigma)
|
let cdf = (x, t: t) => Jstat.Lognormal.cdf(x, t.mu, t.sigma)
|
||||||
let inv = (p, t: t) => Jstat.Lognormal.inv(p, t.mu, t.sigma)
|
let inv = (p, t: t) => Jstat.Lognormal.inv(p, t.mu, t.sigma)
|
||||||
|
@ -110,11 +121,16 @@ module Lognormal = {
|
||||||
#Lognormal({mu: mu, sigma: sigma})
|
#Lognormal({mu: mu, sigma: sigma})
|
||||||
}
|
}
|
||||||
let fromMeanAndStdev = (mean, stdev) => {
|
let fromMeanAndStdev = (mean, stdev) => {
|
||||||
let variance = Js.Math.pow_float(~base=stdev, ~exp=2.0)
|
if stdev > 0.0 {
|
||||||
let meanSquared = Js.Math.pow_float(~base=mean, ~exp=2.0)
|
let variance = Js.Math.pow_float(~base=stdev, ~exp=2.0)
|
||||||
let mu = Js.Math.log(mean) -. 0.5 *. Js.Math.log(variance /. meanSquared +. 1.0)
|
let meanSquared = Js.Math.pow_float(~base=mean, ~exp=2.0)
|
||||||
let sigma = Js.Math.pow_float(~base=Js.Math.log(variance /. meanSquared +. 1.0), ~exp=0.5)
|
let mu = Js.Math.log(mean) -. 0.5 *. Js.Math.log(variance /. meanSquared +. 1.0)
|
||||||
#Lognormal({mu: mu, sigma: sigma})
|
let sigma = Js.Math.pow_float(~base=Js.Math.log(variance /. meanSquared +. 1.0), ~exp=0.5)
|
||||||
|
Ok(#Lognormal({mu: mu, sigma: sigma}))
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Error("Lognormal standard deviation must be larger than 0")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let multiply = (l1, l2) => {
|
let multiply = (l1, l2) => {
|
||||||
|
@ -137,7 +153,11 @@ module Lognormal = {
|
||||||
|
|
||||||
module Uniform = {
|
module Uniform = {
|
||||||
type t = uniform
|
type t = uniform
|
||||||
let make = (low, high) => #Uniform({low: low, high: high})
|
let make = (low, high) =>
|
||||||
|
high > low
|
||||||
|
? Ok(#Uniform({low: low, high: high}))
|
||||||
|
: Error("High must be larger than low")
|
||||||
|
|
||||||
let pdf = (x, t: t) => Jstat.Uniform.pdf(x, t.low, t.high)
|
let pdf = (x, t: t) => Jstat.Uniform.pdf(x, t.low, t.high)
|
||||||
let cdf = (x, t: t) => Jstat.Uniform.cdf(x, t.low, t.high)
|
let cdf = (x, t: t) => Jstat.Uniform.cdf(x, t.low, t.high)
|
||||||
let inv = (p, t: t) => Jstat.Uniform.inv(p, t.low, t.high)
|
let inv = (p, t: t) => Jstat.Uniform.inv(p, t.low, t.high)
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
open Rationale.Function.Infix
|
open Rationale.Function.Infix
|
||||||
|
|
||||||
module FloatFloatMap = {
|
module FloatFloatMap = {
|
||||||
module Id = Belt.Id.MakeComparable({
|
module Id = Belt.Id.MakeComparable({
|
||||||
type t = float
|
type t = float
|
||||||
|
@ -99,7 +98,8 @@ module O = {
|
||||||
}
|
}
|
||||||
|
|
||||||
module O2 = {
|
module O2 = {
|
||||||
let default = (a,b) => O.default(b,a)
|
let default = (a, b) => O.default(b, a)
|
||||||
|
let toExn = (a, b) => O.toExn(b, a)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Functions */
|
/* Functions */
|
||||||
|
@ -288,6 +288,8 @@ module A = {
|
||||||
))
|
))
|
||||||
|> Rationale.Result.return
|
|> Rationale.Result.return
|
||||||
}
|
}
|
||||||
|
let rangeFloat = (~step=1, start, stop) =>
|
||||||
|
Belt.Array.rangeBy(start, stop, ~step) |> fmap(Belt.Int.toFloat)
|
||||||
|
|
||||||
// This zips while taking the longest elements of each array.
|
// This zips while taking the longest elements of each array.
|
||||||
let zipMaxLength = (array1, array2) => {
|
let zipMaxLength = (array1, array2) => {
|
||||||
|
@ -341,7 +343,8 @@ module A = {
|
||||||
| r => Some(r)
|
| r => Some(r)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
let filter = (o, e) => Js.Array.filter(o, e)
|
let filter = Js.Array.filter
|
||||||
|
let joinWith = Js.Array.joinWith
|
||||||
|
|
||||||
module O = {
|
module O = {
|
||||||
let concatSomes = (optionals: array<option<'a>>): array<'a> =>
|
let concatSomes = (optionals: array<option<'a>>): array<'a> =>
|
||||||
|
@ -456,6 +459,7 @@ module A = {
|
||||||
|
|
||||||
module A2 = {
|
module A2 = {
|
||||||
let fmap = (a,b) => A.fmap(b,a)
|
let fmap = (a,b) => A.fmap(b,a)
|
||||||
|
let joinWith = (a, b) => A.joinWith(b, a)
|
||||||
}
|
}
|
||||||
|
|
||||||
module JsArray = {
|
module JsArray = {
|
||||||
|
|
|
@ -25,61 +25,61 @@ module Uniform = {
|
||||||
|
|
||||||
type beta
|
type beta
|
||||||
module Beta = {
|
module Beta = {
|
||||||
@module("jstat") @scope("uniform") external pdf: (float, float, float) => float = "pdf"
|
@module("jstat") @scope("beta") external pdf: (float, float, float) => float = "pdf"
|
||||||
@module("jstat") @scope("uniform") external cdf: (float, float, float) => float = "cdf"
|
@module("jstat") @scope("beta") external cdf: (float, float, float) => float = "cdf"
|
||||||
@module("jstat") @scope("uniform") external inv: (float, float, float) => float = "inv"
|
@module("jstat") @scope("beta") external inv: (float, float, float) => float = "inv"
|
||||||
@module("jstat") @scope("uniform") external sample: (float, float) => float = "sample"
|
@module("jstat") @scope("beta") external sample: (float, float) => float = "sample"
|
||||||
@module("jstat") @scope("uniform") external mean: (float, float) => float = "mean"
|
@module("jstat") @scope("beta") external mean: (float, float) => float = "mean"
|
||||||
}
|
}
|
||||||
|
|
||||||
module Exponential = {
|
module Exponential = {
|
||||||
@module("jstat") @scope("uniform") external pdf: (float, float) => float = "pdf"
|
@module("jstat") @scope("exponential") external pdf: (float, float) => float = "pdf"
|
||||||
@module("jstat") @scope("uniform") external cdf: (float, float) => float = "cdf"
|
@module("jstat") @scope("exponential") external cdf: (float, float) => float = "cdf"
|
||||||
@module("jstat") @scope("uniform") external inv: (float, float) => float = "inv"
|
@module("jstat") @scope("exponential") external inv: (float, float) => float = "inv"
|
||||||
@module("jstat") @scope("uniform") external sample: (float) => float = "sample"
|
@module("jstat") @scope("exponential") external sample: (float) => float = "sample"
|
||||||
@module("jstat") @scope("uniform") external mean: (float) => float = "mean"
|
@module("jstat") @scope("exponential") external mean: (float) => float = "mean"
|
||||||
}
|
}
|
||||||
|
|
||||||
module Cauchy = {
|
module Cauchy = {
|
||||||
@module("jstat") @scope("uniform") external pdf: (float, float, float) => float = "pdf"
|
@module("jstat") @scope("cauchy") external pdf: (float, float, float) => float = "pdf"
|
||||||
@module("jstat") @scope("uniform") external cdf: (float, float, float) => float = "cdf"
|
@module("jstat") @scope("cauchy") external cdf: (float, float, float) => float = "cdf"
|
||||||
@module("jstat") @scope("uniform") external inv: (float, float, float) => float = "inv"
|
@module("jstat") @scope("cauchy") external inv: (float, float, float) => float = "inv"
|
||||||
@module("jstat") @scope("uniform") external sample: (float, float) => float = "sample"
|
@module("jstat") @scope("cauchy") external sample: (float, float) => float = "sample"
|
||||||
@module("jstat") @scope("uniform") external mean: (float, float) => float = "mean"
|
@module("jstat") @scope("cauchy") external mean: (float, float) => float = "mean"
|
||||||
}
|
}
|
||||||
|
|
||||||
module Triangular = {
|
module Triangular = {
|
||||||
@module("jstat") @scope("uniform") external pdf: (float, float, float, float) => float = "pdf"
|
@module("jstat") @scope("triangular") external pdf: (float, float, float, float) => float = "pdf"
|
||||||
@module("jstat") @scope("uniform") external cdf: (float, float, float, float) => float = "cdf"
|
@module("jstat") @scope("triangular") external cdf: (float, float, float, float) => float = "cdf"
|
||||||
@module("jstat") @scope("uniform") external inv: (float, float, float, float) => float = "inv"
|
@module("jstat") @scope("triangular") external inv: (float, float, float, float) => float = "inv"
|
||||||
@module("jstat") @scope("uniform") external sample: (float, float, float) => float = "sample"
|
@module("jstat") @scope("triangular") external sample: (float, float, float) => float = "sample"
|
||||||
@module("jstat") @scope("uniform") external mean: (float, float, float) => float = "mean"
|
@module("jstat") @scope("triangular") external mean: (float, float, float) => float = "mean"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
module Pareto = {
|
module Pareto = {
|
||||||
@module("jstat") @scope("uniform") external pdf: (float, float, float) => float = "pdf"
|
@module("jstat") @scope("pareto") external pdf: (float, float, float) => float = "pdf"
|
||||||
@module("jstat") @scope("uniform") external cdf: (float, float, float) => float = "cdf"
|
@module("jstat") @scope("pareto") external cdf: (float, float, float) => float = "cdf"
|
||||||
@module("jstat") @scope("uniform") external inv: (float, float, float) => float = "inv"
|
@module("jstat") @scope("pareto") external inv: (float, float, float) => float = "inv"
|
||||||
}
|
}
|
||||||
|
|
||||||
module Poisson = {
|
module Poisson = {
|
||||||
@module("jstat") @scope("uniform") external pdf: (float, float) => float = "pdf"
|
@module("jstat") @scope("poisson") external pdf: (float, float) => float = "pdf"
|
||||||
@module("jstat") @scope("uniform") external cdf: (float, float) => float = "cdf"
|
@module("jstat") @scope("poisson") external cdf: (float, float) => float = "cdf"
|
||||||
@module("jstat") @scope("uniform") external sample: (float) => float = "sample"
|
@module("jstat") @scope("poisson") external sample: (float) => float = "sample"
|
||||||
@module("jstat") @scope("uniform") external mean: (float) => float = "mean"
|
@module("jstat") @scope("poisson") external mean: (float) => float = "mean"
|
||||||
}
|
}
|
||||||
|
|
||||||
module Weibull = {
|
module Weibull = {
|
||||||
@module("jstat") @scope("uniform") external pdf: (float, float, float) => float = "pdf"
|
@module("jstat") @scope("weibull") external pdf: (float, float, float) => float = "pdf"
|
||||||
@module("jstat") @scope("uniform") external cdf: (float, float,float ) => float = "cdf"
|
@module("jstat") @scope("weibull") external cdf: (float, float,float ) => float = "cdf"
|
||||||
@module("jstat") @scope("uniform") external sample: (float,float) => float = "sample"
|
@module("jstat") @scope("weibull") external sample: (float,float) => float = "sample"
|
||||||
@module("jstat") @scope("uniform") external mean: (float,float) => float = "mean"
|
@module("jstat") @scope("weibull") external mean: (float,float) => float = "mean"
|
||||||
}
|
}
|
||||||
|
|
||||||
module Binomial = {
|
module Binomial = {
|
||||||
@module("jstat") @scope("uniform") external pdf: (float, float, float) => float = "pdf"
|
@module("jstat") @scope("binomial") external pdf: (float, float, float) => float = "pdf"
|
||||||
@module("jstat") @scope("uniform") external cdf: (float, float,float ) => float = "cdf"
|
@module("jstat") @scope("binomial") external cdf: (float, float,float ) => float = "cdf"
|
||||||
}
|
}
|
||||||
|
|
||||||
@module("jstat") external sum: array<float> => float = "sum"
|
@module("jstat") external sum: array<float> => float = "sum"
|
||||||
|
|
26
packages/squiggle-lang/src/rescript/utility/Sparklines.res
Normal file
26
packages/squiggle-lang/src/rescript/utility/Sparklines.res
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
// Port of Sindre Sorhus' Sparkly to Rescript
|
||||||
|
// reference implementation: https://github.com/sindresorhus/sparkly
|
||||||
|
// Omitting rgb "fire" style, so no `chalk` dependency
|
||||||
|
// Omitting: NaN handling, special consideration for constant data.
|
||||||
|
|
||||||
|
let ticks = [`▁`, `▂`, `▃`, `▄`, `▅`, `▆`, `▇`, `█`]
|
||||||
|
|
||||||
|
let _ticksLength = E.A.length(ticks)
|
||||||
|
|
||||||
|
let _heightToTickIndex = (maximum: float, v: float) => {
|
||||||
|
let suggestedTickIndex = Js.Math.ceil_int(v /. maximum *. Belt.Int.toFloat(_ticksLength)) - 1
|
||||||
|
max(suggestedTickIndex, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
let create = (relativeHeights: array<float>, ~maximum=?, ()) => {
|
||||||
|
if E.A.length(relativeHeights) === 0 {
|
||||||
|
""
|
||||||
|
} else {
|
||||||
|
let maximum = maximum->E.O2.default(E.A.max(relativeHeights)->E.O2.toExn(""))
|
||||||
|
|
||||||
|
relativeHeights
|
||||||
|
->E.A2.fmap(_heightToTickIndex(maximum))
|
||||||
|
->E.A2.fmap(r => E.A.get(ticks, r)->E.O2.toExn(""))
|
||||||
|
->E.A2.joinWith("")
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,6 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
presets: [require.resolve('@docusaurus/core/lib/babel/preset')],
|
presets: [
|
||||||
|
require.resolve('@docusaurus/core/lib/babel/preset'),
|
||||||
|
["@babel/preset-react", { "runtime": "automatic" }]
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
114
packages/website/docs/Functions.mdx
Normal file
114
packages/website/docs/Functions.mdx
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
---
|
||||||
|
sidebar_position: 7
|
||||||
|
---
|
||||||
|
|
||||||
|
import { SquiggleEditor } from '../src/components/SquiggleEditor'
|
||||||
|
|
||||||
|
# Squiggle Functions Reference
|
||||||
|
|
||||||
|
## Distributions
|
||||||
|
|
||||||
|
### Normal distribution
|
||||||
|
|
||||||
|
The `normal(mean, sd)` function creates a normal distribution with the given mean
|
||||||
|
and standard deviation.
|
||||||
|
|
||||||
|
<SquiggleEditor initialSquiggleString="normal(5, 1)" />
|
||||||
|
|
||||||
|
### Uniform distribution
|
||||||
|
|
||||||
|
The `uniform(low, high)` function creates a uniform distribution between the
|
||||||
|
two given numbers.
|
||||||
|
|
||||||
|
<SquiggleEditor initialSquiggleString="uniform(3, 7)" />
|
||||||
|
|
||||||
|
|
||||||
|
### Lognormal distribution
|
||||||
|
|
||||||
|
The `lognormal(mu, sigma)` returns the log of a normal distribution with parameters
|
||||||
|
mu and sigma. The log of lognormal(mu, sigma) is a normal distribution with parameters
|
||||||
|
mean mu and standard deviation sigma.
|
||||||
|
|
||||||
|
<SquiggleEditor initialSquiggleString="lognormal(0, 0.7)" />
|
||||||
|
|
||||||
|
An alternative format is also available. The "to" notation creates a lognormal
|
||||||
|
distribution with a 90% confidence interval between the two numbers. We add
|
||||||
|
this convinience as lognormal distributions are commonly used in practice.
|
||||||
|
|
||||||
|
<SquiggleEditor initialSquiggleString="2 to 10" />
|
||||||
|
|
||||||
|
Furthermore, it's also possible to create a lognormal from it's actual mean
|
||||||
|
and standard deviation, using `lognormalFromMeanAndStdDev`.
|
||||||
|
|
||||||
|
<SquiggleEditor initialSquiggleString="lognormalFromMeanAndStdDev(20, 10)" />
|
||||||
|
|
||||||
|
|
||||||
|
### Beta distribution
|
||||||
|
|
||||||
|
The `beta(a, b)` function creates a beta distribution with parameters a and b:
|
||||||
|
|
||||||
|
<SquiggleEditor initialSquiggleString="beta(20, 20)" />
|
||||||
|
|
||||||
|
### Exponential distribution
|
||||||
|
|
||||||
|
The `exponential(mean)` function creates an exponential distribution with the given
|
||||||
|
mean.
|
||||||
|
|
||||||
|
<SquiggleEditor initialSquiggleString="exponential(1)" />
|
||||||
|
|
||||||
|
|
||||||
|
### The Triangular distribution
|
||||||
|
|
||||||
|
The `triangular(a,b,c)` function creates a triangular distribution with lower
|
||||||
|
bound a, mode b and upper bound c.
|
||||||
|
|
||||||
|
<SquiggleEditor initialSquiggleString="triangular(1, 2, 4)" />
|
||||||
|
|
||||||
|
### Multimodal distriutions
|
||||||
|
|
||||||
|
The multimodal function combines 2 or more other distributions to create a weighted
|
||||||
|
combination of the two. The first positional arguments represent the distributions
|
||||||
|
to be combined, and the last argument is how much to weigh every distribution in the
|
||||||
|
combination.
|
||||||
|
|
||||||
|
<SquiggleEditor initialSquiggleString="mm(uniform(0,1), normal(1,1), [0.5, 0.5])" />
|
||||||
|
|
||||||
|
It's possible to create discrete distributions using this method.
|
||||||
|
|
||||||
|
<SquiggleEditor initialSquiggleString="mm(0, 1, [0.2,0.8])" />
|
||||||
|
|
||||||
|
As well as mixed distributions:
|
||||||
|
|
||||||
|
<SquiggleEditor initialSquiggleString="mm(3, 8, 1 to 10, [0.2, 0.3, 0.5])" />
|
||||||
|
|
||||||
|
## Other Functions
|
||||||
|
|
||||||
|
### PDF of a distribution
|
||||||
|
The `pdf(distribution, x)` function returns the density of a distribution at the
|
||||||
|
given point x.
|
||||||
|
|
||||||
|
<SquiggleEditor initialSquiggleString="pdf(normal(0,1),0)" />
|
||||||
|
|
||||||
|
### Inverse of a distribution
|
||||||
|
|
||||||
|
The `inv(distribution, prob)` gives the value x or which the probability for all values
|
||||||
|
lower than x is equal to prob. It is the inverse of `cdf`.
|
||||||
|
|
||||||
|
<SquiggleEditor initialSquiggleString="inv(normal(0,1),0.5)" />
|
||||||
|
|
||||||
|
### CDF of a distribution
|
||||||
|
|
||||||
|
The `cdf(distribution,x)` gives the cumulative probability of the distribution
|
||||||
|
or all values lower than x. It is the inverse of `inv`.
|
||||||
|
|
||||||
|
<SquiggleEditor initialSquiggleString="cdf(normal(0,1),0)" />
|
||||||
|
|
||||||
|
### Mean of a distribution
|
||||||
|
The `mean(distribution)` function gives the mean (expected value) of a distribution.
|
||||||
|
|
||||||
|
<SquiggleEditor initialSquiggleString="mean(normal(5, 10))" />
|
||||||
|
|
||||||
|
### Sampling a distribution
|
||||||
|
The `sample(distribution)` samples a given distribution.
|
||||||
|
|
||||||
|
<SquiggleEditor initialSquiggleString="sample(normal(0, 10))" />
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
sidebar_position: 3
|
sidebar_position: 4
|
||||||
---
|
---
|
||||||
|
|
||||||
# Future Features
|
# Future Features
|
||||||
|
@ -77,5 +77,34 @@ Right now, Monte Carlo simulations are totally random. It would be nicer to be a
|
||||||
- Possibly a decent web GUI (a much more advanced playground).
|
- Possibly a decent web GUI (a much more advanced playground).
|
||||||
- A VS Code extention and similar.
|
- A VS Code extention and similar.
|
||||||
|
|
||||||
## Fixes
|
## Bugs
|
||||||
- Discrete distributions are particularly buggy. Try ``mm(1,2,3,4,5,6,7,8,9,10) .* (5 to 8)``
|
- Discrete distributions are particularly buggy. Try ``mm(1,2,3,4,5,6,7,8,9,10) .* (5 to 8)``
|
||||||
|
|
||||||
|
## New Functions
|
||||||
|
|
||||||
|
### Distributions
|
||||||
|
```js
|
||||||
|
cauchy()
|
||||||
|
pareto()
|
||||||
|
metalog()
|
||||||
|
```
|
||||||
|
|
||||||
|
Possibly change mm to mix, or mx(). Also, change input format, maybe to mx([a,b,c], [a,b,c]).
|
||||||
|
|
||||||
|
|
||||||
|
### Functions
|
||||||
|
```js
|
||||||
|
samples(distribution, n)
|
||||||
|
toPdf(distribution)
|
||||||
|
toCdf(distribution)
|
||||||
|
toHash(distribution)
|
||||||
|
trunctate(distribution, leftValue, rightValue)
|
||||||
|
leftTrunctate(distribution, leftValue)
|
||||||
|
rightTrunctate(distribution, rightValue)
|
||||||
|
distributionFromSamples(array, params)
|
||||||
|
distributionFromPoints()
|
||||||
|
distributionFromHash()
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
10
packages/website/docs/Introduction.md
Normal file
10
packages/website/docs/Introduction.md
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
---
|
||||||
|
sidebar_position: 1
|
||||||
|
---
|
||||||
|
|
||||||
|
# Squiggle
|
||||||
|
|
||||||
|
Squiggle is a language for writing calculations under uncertainty. It has use
|
||||||
|
cases in forecasting and writing better evaluations.
|
||||||
|
|
||||||
|
The best way to get started with Squiggle is to [try it out yourself](https://playground.squiggle-language.com/).
|
|
@ -1,16 +1,36 @@
|
||||||
---
|
---
|
||||||
sidebar_position: 2
|
sidebar_position: 3
|
||||||
---
|
---
|
||||||
|
|
||||||
# Javascript Library
|
# Javascript Libraries
|
||||||
|
|
||||||
There's a very simple javscript library for Squiggle here: https://www.npmjs.com/package/squiggle-experimental.
|
There are two JavaScript packages currently available for Squiggle:
|
||||||
|
- [`@quri/squiggle-lang`](https://www.npmjs.com/package/@quri/squiggle-lang)
|
||||||
|
- [`@quri/squiggle-components`](https://www.npmjs.com/package/@quri/squiggle-components)
|
||||||
|
|
||||||
You can see it live on this Observable page: [https://observablehq.com/d/a99e822870c4ca5f](https://observablehq.com/d/a99e822870c4ca5f).
|
Types are available for both packages.
|
||||||
|
|
||||||
|
## Squiggle Language
|
||||||
|
|
||||||
## Simple Example
|
The `@quri/squiggle-lang` package exports a single function, `run`, which given
|
||||||
```
|
a string of Squiggle code, will execute the code and return any exports and the
|
||||||
let squiggle = require("squiggle-experimental@0.1.9/dist/index.js")
|
environment created from the squiggle code.
|
||||||
squiggle.runMePlease("3 + normal(50,1))
|
|
||||||
```
|
`run` has two optional arguments. The first optional argument allows you to set
|
||||||
|
sampling settings for Squiggle when representing distributions. The second optional
|
||||||
|
argument allows you to pass an environment previously created by another `run`
|
||||||
|
call. Passing this environment will mean that all previously declared variables
|
||||||
|
in the previous environment will be made available.
|
||||||
|
|
||||||
|
The return type of `run` is a bit complicated, and comes from auto generated js
|
||||||
|
code that comes from rescript. I highly recommend using typescript when using
|
||||||
|
this library to help navigate the return type.
|
||||||
|
|
||||||
|
## Squiggle Components
|
||||||
|
|
||||||
|
The `@quri/squiggle-components` package offers several components and utilities
|
||||||
|
for people who want to embed Squiggle components into websites. This documentation
|
||||||
|
relies on `@quri/squiggle-components` frequently.
|
||||||
|
|
||||||
|
We host [a storybook](https://components.squiggle-language.com/) with details
|
||||||
|
and usage of each of the components made available.
|
||||||
|
|
|
@ -1,67 +0,0 @@
|
||||||
---
|
|
||||||
sidebar_position: 1
|
|
||||||
---
|
|
||||||
|
|
||||||
# Squiggle Language
|
|
||||||
|
|
||||||
## Distributions
|
|
||||||
```js
|
|
||||||
normal(a,b)
|
|
||||||
uniform(a,b)
|
|
||||||
lognormal(a,b)
|
|
||||||
lognormalFromMeanAndStdDev(mean, stdev)
|
|
||||||
beta(a,b)
|
|
||||||
exponential(a)
|
|
||||||
triangular(a,b,c)
|
|
||||||
mm(a,b,c, [1,2,3]) //todo: change to mix, or mx(). Also, change input format, maybe to mx([a,b,c], [a,b,c]).
|
|
||||||
cauchy() //todo
|
|
||||||
pareto() //todo
|
|
||||||
metalog() //todo
|
|
||||||
```
|
|
||||||
|
|
||||||
## Functions
|
|
||||||
```js
|
|
||||||
pdf(distribution, float)
|
|
||||||
inv(distribution, float)
|
|
||||||
cdf(distribution, float)
|
|
||||||
mean(distribution)
|
|
||||||
sample(distribution)
|
|
||||||
scaleExp(distribution, float)
|
|
||||||
scaleMultiply(distribution, float)
|
|
||||||
scaleLog(distribution, float)
|
|
||||||
samples(distribution, n) //todo
|
|
||||||
toPdf(distribution) //todo
|
|
||||||
toCdf(distribution) //todo
|
|
||||||
toHash(distribution) //todo. Make hash of content, like, {xs:[], ys:[]}
|
|
||||||
trunctate(distribution, leftValue, rightValue) //todo
|
|
||||||
leftTrunctate(distribution, leftValue) //todo
|
|
||||||
rightTrunctate(distribution, rightValue) //todo
|
|
||||||
distributionFromSamples(array, params) //todo
|
|
||||||
distributionFromPoints() //todo
|
|
||||||
distributionFromHash() //todo
|
|
||||||
log() //todo
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
## Example Functions
|
|
||||||
|
|
||||||
```js
|
|
||||||
ozzie_estimate(t) = lognormal({mean: 3 + (t+.1)^2.5, stdev: 8})
|
|
||||||
nuño_estimate(t) = lognormal({mean: 3 + (t+.1)^2, stdev: 10})
|
|
||||||
combined(t) = mm(ozzie_estimate(t) .+ nuño_estimate(t))
|
|
||||||
combined
|
|
||||||
```
|
|
||||||
|
|
||||||
```js
|
|
||||||
us_economy_2018 = (10.5 to 10.9)T
|
|
||||||
growth_rate = 1.08 to 1.2
|
|
||||||
us_economy(t) = us_economy_2018 * (growth_rate^t)
|
|
||||||
|
|
||||||
us_population_2019 = 320M to 330M
|
|
||||||
us_population_growth_rate = 1.01 to 1.1
|
|
||||||
us_population(t) = us_population_2019 * (us_population_growth_rate^t)
|
|
||||||
gdp_per_person(t) = us_economy(t)/us_population(t)
|
|
||||||
gdp_per_person
|
|
||||||
|
|
||||||
gdp_per_person
|
|
||||||
```
|
|
36
packages/website/docs/Language.mdx
Normal file
36
packages/website/docs/Language.mdx
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
---
|
||||||
|
sidebar_position: 2
|
||||||
|
---
|
||||||
|
|
||||||
|
import { SquiggleEditor } from '../src/components/SquiggleEditor'
|
||||||
|
|
||||||
|
# Squiggle Language
|
||||||
|
|
||||||
|
The squiggle language has a very simple syntax. The best way to get to understand
|
||||||
|
it is by simply looking at examples.
|
||||||
|
|
||||||
|
## Basic Language
|
||||||
|
|
||||||
|
As an example:
|
||||||
|
|
||||||
|
<SquiggleEditor initialSquiggleString={`value_of_work = 10 to 70
|
||||||
|
value_of_work`} />
|
||||||
|
|
||||||
|
Squiggle can declare variables (`value_of_work = 10 to 70`) and declare exports
|
||||||
|
(the lone `value_of_work` line). Variables can be used later in a squiggle program
|
||||||
|
and even in other notebooks!
|
||||||
|
|
||||||
|
An export is rendered to the output view so you can see your result.
|
||||||
|
|
||||||
|
the exports can be expressions, such as:
|
||||||
|
|
||||||
|
<SquiggleEditor initialSquiggleString="normal(0,1)" />
|
||||||
|
|
||||||
|
## Functions
|
||||||
|
|
||||||
|
Squiggle supports functions, including the rendering of functions:
|
||||||
|
|
||||||
|
<SquiggleEditor initialSquiggleString={`ozzie_estimate(t) = lognormal({mean: 3 + (t+.1)^2.5, stdev: 8})
|
||||||
|
ozzie_estimate
|
||||||
|
`} />
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
sidebar_position: 4
|
sidebar_position: 5
|
||||||
---
|
---
|
||||||
|
|
||||||
# Three Formats of Distributions
|
# Three Formats of Distributions
|
||||||
|
|
|
@ -3,18 +3,35 @@
|
||||||
|
|
||||||
const lightCodeTheme = require('prism-react-renderer/themes/github');
|
const lightCodeTheme = require('prism-react-renderer/themes/github');
|
||||||
const darkCodeTheme = require('prism-react-renderer/themes/dracula');
|
const darkCodeTheme = require('prism-react-renderer/themes/dracula');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
/** @type {import('@docusaurus/types').Config} */
|
/** @type {import('@docusaurus/types').Config} */
|
||||||
const config = {
|
const config = {
|
||||||
title: 'Squiggle (alpha)',
|
title: 'Squiggle (alpha)',
|
||||||
tagline: "Scorable programming, for use by forecasters",
|
tagline: "Estimation language for forecasters",
|
||||||
url: 'https://squiggle-documentation.netlify.app',
|
url: 'https://squiggle-language.com',
|
||||||
baseUrl: '/',
|
baseUrl: '/',
|
||||||
onBrokenLinks: 'throw',
|
onBrokenLinks: 'throw',
|
||||||
onBrokenMarkdownLinks: 'warn',
|
onBrokenMarkdownLinks: 'warn',
|
||||||
favicon: 'img/favicon.ico',
|
favicon: 'img/favicon.ico',
|
||||||
organizationName: 'QURI', // Usually your GitHub org/user name.
|
organizationName: 'QURIResearch', // Usually your GitHub org/user name.
|
||||||
projectName: 'Squiggle', // Usually your repo name.
|
projectName: 'squiggle', // Usually your repo name.
|
||||||
|
|
||||||
|
plugins: [
|
||||||
|
() => ({
|
||||||
|
configureWebpack(config, isServer, utils, content) {
|
||||||
|
return {
|
||||||
|
resolve: {
|
||||||
|
alias : {
|
||||||
|
"@quri/squiggle-components": path.resolve(__dirname, "../components/src"),
|
||||||
|
"@quri/squiggle-lang": path.resolve(__dirname, "../squiggle-lang/src/js")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
})
|
||||||
|
],
|
||||||
|
|
||||||
presets: [
|
presets: [
|
||||||
[
|
[
|
||||||
|
@ -51,7 +68,7 @@ const config = {
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
type: 'doc',
|
type: 'doc',
|
||||||
docId: 'Language',
|
docId: 'Introduction',
|
||||||
position: 'left',
|
position: 'left',
|
||||||
label: 'Documentation',
|
label: 'Documentation',
|
||||||
},
|
},
|
||||||
|
|
23698
packages/website/package-lock.json
generated
23698
packages/website/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
|
@ -9,8 +9,8 @@
|
||||||
"all": "yarn build"
|
"all": "yarn build"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/core": "2.0.0-beta.15",
|
"@docusaurus/core": "2.0.0-beta.17",
|
||||||
"@docusaurus/preset-classic": "2.0.0-beta.15",
|
"@docusaurus/preset-classic": "2.0.0-beta.17",
|
||||||
"@mdx-js/react": "^1.6.21",
|
"@mdx-js/react": "^1.6.21",
|
||||||
"clsx": "^1.1.1",
|
"clsx": "^1.1.1",
|
||||||
"prism-react-renderer": "^1.2.1",
|
"prism-react-renderer": "^1.2.1",
|
||||||
|
|
13
packages/website/src/components/SquiggleEditor.jsx
Normal file
13
packages/website/src/components/SquiggleEditor.jsx
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import BrowserOnly from '@docusaurus/BrowserOnly';
|
||||||
|
|
||||||
|
export function SquiggleEditor(props) {
|
||||||
|
return (
|
||||||
|
<BrowserOnly fallback={<div>Loading...</div>}>
|
||||||
|
{() => {
|
||||||
|
const LibComponent =
|
||||||
|
require('@quri/squiggle-components').SquiggleEditor;
|
||||||
|
return <LibComponent {...props} />;
|
||||||
|
}}
|
||||||
|
</BrowserOnly>
|
||||||
|
);
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user