├── .gitconfig.include ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ └── other-task.md └── workflows │ ├── build.yml │ ├── deploy.yml │ └── test-esop2025-artifact.yml ├── .gitignore ├── .tidyrc.json ├── .vscode └── settings.json ├── CITATION.cff ├── LICENSE ├── README.md ├── archive ├── 0.3.1.zip └── 0.6.1.zip ├── artifact ├── Dockerfile ├── LICENSE ├── README.md ├── REQUIREMENTS ├── STATUS └── script │ ├── bump_zenodo.sh │ ├── rebuild_benchmark.sh │ ├── zenodo_new_version.sh │ └── zenodo_upload.sh ├── benchmark └── tex │ ├── benchmarks.tex │ ├── table-one.tex │ ├── table-three.tex │ └── table-two.tex ├── fluid └── lib │ ├── convolution.fld │ ├── graphics.fld │ ├── prelude.fld │ └── stats.fld ├── karma.conf.test.js ├── package-lock.json ├── package.json ├── packages.dhall ├── query.mjs ├── script ├── archive │ └── v0.6.1.sh ├── benchmark.sh ├── build-test.sh ├── build.sh ├── bundle-benchmark.sh ├── bundle-fluid-org.sh ├── bundle-fluid.sh ├── bundle-libraries.sh ├── bundle-website.sh ├── git │ └── hooks │ │ └── pre-push ├── npm-publish.sh ├── python │ ├── bwd_perf.py │ ├── energy_data.py │ └── plot_bench.py ├── serve.sh ├── setup │ ├── delete-workflow-runs.sh │ ├── dev-setup.sh │ └── install-hooks.sh ├── test-all.sh ├── test-page.sh ├── test-website-all.sh ├── test-website.sh ├── test.sh ├── tidy.sh └── util │ ├── bundle-module.sh │ ├── bundle.sh │ ├── clean.sh │ ├── compile.sh │ └── lisp-case.sh ├── spago.dhall ├── src ├── App │ ├── CodeMirror.js │ ├── CodeMirror.purs │ ├── Fig.purs │ ├── LoadFigure.purs │ ├── Util.purs │ ├── Util │ │ └── Selector.purs │ ├── View.purs │ └── View │ │ ├── BarChart.js │ │ ├── BarChart.purs │ │ ├── LineChart.purs │ │ ├── MatrixView.js │ │ ├── MatrixView.purs │ │ ├── MultiView.purs │ │ ├── Paragraph.purs │ │ ├── ScatterPlot.js │ │ ├── ScatterPlot.purs │ │ ├── TableView.purs │ │ ├── Util.purs │ │ └── Util │ │ ├── Axes.purs │ │ ├── D3.js │ │ ├── D3.purs │ │ └── Point.purs ├── Benchmark.purs ├── Bind.purs ├── DataType.purs ├── Desug.purs ├── Desugarable.purs ├── Dict.purs ├── Doc.purs ├── EvalGraph.purs ├── Expr.purs ├── Fluid.purs ├── GaloisConnection.purs ├── Graph.purs ├── Graph │ ├── GraphImpl.purs │ ├── Slice.purs │ └── WithGraph.purs ├── Lattice.purs ├── Module.purs ├── Module │ ├── Node.purs │ └── Web.purs ├── Parse.purs ├── Parse │ └── Constants.purs ├── Pretty.purs ├── Primitive.purs ├── Primitive │ ├── Defs.purs │ └── Parse.purs ├── ProgCxt.purs ├── README.md ├── SExpr.purs ├── Util.purs ├── Util │ ├── Map.js │ ├── Map.purs │ ├── Pair.purs │ ├── Parse.purs │ ├── Pretty.purs │ └── Set.purs └── Val.purs ├── test ├── Benchmark │ ├── Util.js │ └── Util.purs ├── Specs │ ├── Bwd.purs │ ├── Comments.purs │ ├── Desugar.purs │ ├── Graphics.purs │ ├── LinkedInputs.purs │ ├── LinkedOutputs.purs │ └── Misc.purs ├── Test.purs ├── Util.purs ├── Util │ ├── Debug.purs │ ├── Mocha.js │ ├── Mocha.purs │ ├── Puppeteer.js │ ├── Puppeteer.purs │ └── Suite.purs └── fluid │ ├── arithmetic.fld │ ├── array.fld │ ├── comments │ ├── app.fld │ ├── dicts.fld │ ├── list-comp.fld │ ├── map.fld │ └── nested-constr.fld │ ├── compose.fld │ ├── dataset │ ├── methane-emissions.fld │ ├── mini-non-renewables.fld │ ├── mini-renewables.fld │ ├── non-renewables.fld │ ├── renewables-new.fld │ ├── renewables-restricted.fld │ ├── renewables.fld │ └── ssp126-2081-2100.fld │ ├── desugar │ ├── list-comp-1.fld │ ├── list-comp-10.fld │ ├── list-comp-2.fld │ ├── list-comp-3.fld │ ├── list-comp-4.fld │ ├── list-comp-5.fld │ ├── list-comp-6.fld │ ├── list-comp-7.fld │ ├── list-comp-8.fld │ ├── list-comp-9.fld │ └── list-enum.fld │ ├── dict-list-comp.fld │ ├── dicts.fld │ ├── div-mod-quot-rem.fld │ ├── factorial.fld │ ├── filter.fld │ ├── first-class-constr.fld │ ├── flatten.fld │ ├── foldr-sumSquares.fld │ ├── graphics │ ├── background.fld │ ├── grouped-bar-chart.fld │ ├── line-chart.fld │ └── stacked-bar-chart.fld │ ├── include-input-into-output.fld │ ├── length.fld │ ├── lexicalScoping.fld │ ├── lib │ ├── dtw.fld │ ├── fnum.fld │ └── some-constants.fld │ ├── linked-inputs │ ├── energyscatter.fld │ └── mini-energyscatter.fld │ ├── linked-outputs │ ├── convolution-data.fld │ ├── convolution.fld │ ├── line-chart.fld │ ├── moving-average.fld │ ├── pairs-data.fld │ └── pairs.fld │ ├── lookup.fld │ ├── map.fld │ ├── mergeSort.fld │ ├── normalise.fld │ ├── nub.fld │ ├── pattern-match.fld │ ├── percent.fld │ ├── plot │ ├── methane.fld │ └── non-renewables.fld │ ├── range.fld │ ├── record-lookup.fld │ ├── records.expect.fld │ ├── records.fld │ ├── reverse.fld │ ├── scratchpad.fld │ └── slicing │ ├── add.expect.fld │ ├── add.fld │ ├── array │ ├── array.expect.fld │ ├── array.fld │ ├── dims.expect.fld │ ├── dims.fld │ ├── lookup.expect.fld │ ├── lookup.fld │ └── renewables.fld │ ├── convolution │ ├── edgeDetect.expect.fld │ ├── edgeDetect.fld │ ├── emboss-wrap.fld │ ├── emboss.expect.fld │ ├── emboss.fld │ ├── filter │ │ ├── edge-detect.fld │ │ ├── emboss.fld │ │ └── gaussian.fld │ ├── gaussian.expect.fld │ ├── gaussian.fld │ └── test-image.fld │ ├── dict │ ├── create.expect.fld │ ├── create.fld │ ├── difference.expect.fld │ ├── difference.fld │ ├── disjointUnion.expect.fld │ ├── disjointUnion.fld │ ├── foldl.expect.fld │ ├── foldl.fld │ ├── get.expect.fld │ ├── get.fld │ ├── intersectionWith.expect.fld │ ├── intersectionWith.fld │ ├── map.expect.fld │ ├── map.fld │ ├── match.expect.fld │ └── match.fld │ ├── divide.expect.fld │ ├── divide.fld │ ├── dtw │ ├── average-series.expect.fld │ ├── average-series.fld │ ├── compute-dtw.expect.fld │ └── compute-dtw.fld │ ├── explained.expect.fld │ ├── explained.fld │ ├── filter.expect.fld │ ├── filter.fld │ ├── intersperse-1.expect.fld │ ├── intersperse-2.expect.fld │ ├── intersperse.fld │ ├── length.expect.fld │ ├── length.fld │ ├── linked-outputs │ ├── bar-chart-line-chart.expect.fld │ ├── bar-chart-line-chart.fld │ ├── stacked-bar-scatter-plot.expect.fld │ └── stacked-bar-scatter-plot.fld │ ├── list-comp-1.expect.fld │ ├── list-comp-2.expect.fld │ ├── list-comp.fld │ ├── lookup.expect.fld │ ├── lookup.fld │ ├── map.expect.fld │ ├── map.fld │ ├── matrix-update.expect.fld │ ├── matrix-update.fld │ ├── multiply.expect.fld │ ├── multiply.fld │ ├── nth.expect.fld │ ├── nth.fld │ ├── output-not-source.expect.fld │ ├── output-not-source.fld │ ├── qcut.expect.fld │ ├── qcut.fld │ ├── section-5-example-1.expect.fld │ ├── section-5-example-2.expect.fld │ ├── section-5-example-3.expect.fld │ ├── section-5-example.fld │ ├── zeros-1.expect.fld │ ├── zeros-2.expect.fld │ ├── zeros.fld │ ├── zipWith-1.expect.fld │ └── zipWith.fld ├── vscode-notes.md ├── website-test.js ├── website ├── README.md ├── Test │ ├── FluidOrg │ │ └── Convolution.purs │ └── Misc │ │ ├── EnergyScatter.purs │ │ └── RenewablesLinked.purs ├── css │ ├── styles.css │ └── view-styles.css ├── esop2025-artifact │ ├── css │ ├── favicon.ico │ ├── fig2 │ │ ├── index.html │ │ └── spec.json │ ├── fig4 │ │ ├── index.html │ │ └── spec.json │ ├── fluid │ │ ├── dataset │ │ │ ├── methane-emissions.fld │ │ │ ├── non-renewables.fld │ │ │ ├── renewables-new.fld │ │ │ └── renewables.fld │ │ ├── moving-average.fld │ │ └── non-renewables.fld │ ├── font │ ├── index.html │ └── shared ├── favicon.ico ├── fluid-org │ ├── .well-known │ │ └── atproto-did │ ├── contributors.html │ ├── convolution-wrapped │ │ ├── index.html │ │ └── spec.json │ ├── convolution │ │ ├── index.html │ │ └── spec.json │ ├── css │ ├── faq.html │ ├── favicon.ico │ ├── fluid-poster.pdf │ ├── fluid │ │ ├── convolution │ │ │ ├── emboss-wrap.fld │ │ │ ├── emboss.fld │ │ │ ├── filter │ │ │ │ └── emboss.fld │ │ │ └── test-image.fld │ │ ├── dataset │ │ │ ├── methane-emissions.fld │ │ │ ├── non-renewables.fld │ │ │ ├── renewables-new.fld │ │ │ └── renewables.fld │ │ ├── moving-average.fld │ │ └── non-renewables.fld │ ├── font │ ├── image │ │ ├── fluid-logo.png │ │ ├── iccs-full-logo.png │ │ └── schmidtsciences_primary_color.png │ ├── index.html │ ├── moving-average │ │ ├── index.html │ │ └── spec.json │ ├── pdf │ │ └── Turing-2023.09.19.pdf │ ├── shared │ ├── spec.json │ └── student-projects │ │ └── index.html ├── font │ ├── GraphikLight.woff2 │ ├── GraphikLightItalic.woff2 │ ├── GraphikMedium.woff2 │ ├── GraphikMediumItalic.woff2 │ └── OdiseanTech.woff2 ├── literate-execution │ ├── ar6-spm │ │ ├── index.html │ │ └── spec.json │ ├── convolution │ │ ├── index.html │ │ └── spec.json │ ├── css │ ├── favicon.ico │ ├── fluid │ │ ├── ar6-spm │ │ │ ├── figure-spm-4.fld │ │ │ ├── likelihoods.fld │ │ │ ├── ssp126.fld │ │ │ ├── ssp126preds.fld │ │ │ ├── ssp245.fld │ │ │ └── ssp245preds.fld │ │ └── convolution │ │ │ ├── emboss-wrap.fld │ │ │ ├── emboss.fld │ │ │ ├── filter │ │ │ └── emboss.fld │ │ │ └── test-image.fld │ ├── font │ ├── image │ ├── index.html │ └── shared ├── misc │ ├── css │ ├── energy-scatter │ │ ├── index.html │ │ └── spec.json │ ├── favicon.ico │ ├── fluid │ │ ├── bar-chart-line-chart.fld │ │ ├── dataset │ │ │ ├── methane-emissions.fld │ │ │ ├── non-renewables.fld │ │ │ ├── renewables-new.fld │ │ │ └── renewables.fld │ │ ├── energyscatter.fld │ │ ├── line-chart.fld │ │ ├── methane.fld │ │ ├── moving-average.fld │ │ └── non-renewables.fld │ ├── font │ ├── index.html │ ├── methane │ │ ├── index.html │ │ └── spec.json │ ├── non-renewables │ │ ├── index.html │ │ └── spec.json │ ├── renewables-linked │ │ ├── index.html │ │ └── spec.json │ └── shared ├── shared │ ├── footer.html │ ├── header.html │ ├── sub-header.html │ └── util.js └── template.html └── yarn.lock /.gitconfig.include: -------------------------------------------------------------------------------- 1 | [alias] 2 | add-unused = "!f() { git commit -a -m \"🧩 [add-unused]: $1\"; }; f" 3 | consolidate = "!f() { git commit -a -m \"🧩 [consolidate]: $1\"; }; f" 4 | doc = "!f() { git commit -a -m \"🧩 [doc] : $1\"; }; f" 5 | fix = "!f() { git commit -a -m \"❗ [fix]: $1\"; }; f" 6 | incomplete = "!f() { git commit -a -m \"❗ [incomplete]: $1\"; }; f" 7 | layout = "!f() { git commit -a -m \"🧩 [layout]\"; }; f" 8 | meta = "!f() { git commit -a -m \"🔧 [meta]: $1\"; }; f" 9 | modify = "!f() { git commit -a -m \"❗ [modify]: $1\"; }; f" 10 | modify-unused = "!f() { git commit -a -m \"🧩 [modify-unused]: $1\"; }; f" 11 | move = "!f() { git commit -a -m \"🧩 [move]: $1\"; }; f" 12 | permute = "!f() { git commit -a -m \"🧩 [permute]: $1\"; }; f" 13 | refactor = "!f() { git commit -a -m \"🧩 [refactor]: $1\"; }; f" 14 | remove-unused = "!f() { git commit -a -m \"🧩 [remove-unused]: $1\"; }; f" 15 | rename = "!f() { git commit -a -m \"🧩 [rename]: $1\"; }; f" 16 | new-test = "!f() { git commit -a -m \"🧩 [new-test]: $1\"; }; f" 17 | pop-task = "!f() { git commit -a -m \"🧩 [pop-task]: $1\"; }; f" 18 | type-holes = "!f() { git commit -a -m \"❗ [type-holes]: $1\"; }; f" 19 | unconsolidate = "!f() { git commit -a -m \"🧩 [unconsolidate]: $1\"; }; f" 20 | undo-commit = "!f() { git reset HEAD~1; }; f" 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/other-task.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Other task 3 | about: Any other issue 4 | title: '' 5 | labels: 6 | assignees: '' 7 | 8 | --- 9 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: [workflow_call] 4 | 5 | jobs: 6 | test: 7 | strategy: 8 | matrix: 9 | os: [ubuntu-22.04, macOS-14] 10 | runs-on: ${{ matrix.os }} 11 | 12 | steps: 13 | - uses: actions/checkout@v4 14 | - uses: actions/setup-node@v4 15 | with: 16 | node-version: 22 17 | - name: enable-glob 18 | run: | 19 | shopt -s extglob nullglob globstar 20 | if: runner.os == 'Linux' 21 | - name: test 22 | run: | 23 | yarn install 24 | set -x -e 25 | yarn purs-tidy check src/**/*.purs test/**/*.purs website/**/*.purs 26 | yarn build-test 27 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: deploy 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - 'old/**' 7 | - '**.md' 8 | pull_request: 9 | paths-ignore: 10 | - 'old/**' 11 | - '**.md' 12 | workflow_dispatch: 13 | 14 | jobs: 15 | build: 16 | uses: ./.github/workflows/build.yml 17 | 18 | deploy-npm: 19 | runs-on: ubuntu-22.04 20 | needs: build 21 | if: false # disable for now 22 | # if: github.ref == 'refs/heads/develop' 23 | permissions: 24 | contents: read 25 | id-token: write 26 | steps: 27 | - uses: actions/checkout@v4 28 | - uses: actions/setup-node@v4 29 | with: 30 | node-version: 22 31 | registry-url: 'https://registry.npmjs.org' 32 | - run: | 33 | yarn install 34 | yarn build 35 | yarn npm-publish 36 | env: 37 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 38 | 39 | deploy-fluid-org: 40 | runs-on: ubuntu-22.04 41 | needs: build 42 | if: github.ref == 'refs/heads/release' 43 | steps: 44 | - uses: actions/checkout@v4 45 | - name: build 46 | run: | 47 | yarn install 48 | yarn build 49 | - name: gh-pages 50 | uses: peaceiris/actions-gh-pages@v3 51 | with: 52 | github_token: ${{ secrets.GITHUB_TOKEN }} 53 | publish_dir: dist/fluid-org 54 | keep_files: false 55 | 56 | deploy-esop2025-artifact: 57 | runs-on: ubuntu-22.04 58 | needs: build 59 | if: github.ref == 'refs/heads/develop' && github.repository == 'rolyp/esop25-web-artifact' 60 | steps: 61 | - uses: actions/checkout@v4 62 | - name: build 63 | run: | 64 | yarn install 65 | yarn build-esop2025-artifact 66 | - name: gh-pages 67 | uses: peaceiris/actions-gh-pages@v3 68 | with: 69 | github_token: ${{ secrets.GITHUB_TOKEN }} 70 | publish_dir: dist/esop2025-artifact 71 | keep_files: false 72 | -------------------------------------------------------------------------------- /.github/workflows/test-esop2025-artifact.yml: -------------------------------------------------------------------------------- 1 | name: test-esop2025-artifact 2 | 3 | on: [workflow_dispatch] 4 | 5 | jobs: 6 | # test an approximation of the instructions submitted to the ESOP 2025 AE process 7 | verify-artifact-evaluation-instructions: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/setup-node@v4 11 | with: 12 | node-version: 22 13 | - name: Downloading and connecting to Docker image 14 | run: | 15 | set +x 16 | wget "https://zenodo.org/records/14637654/files/esop-artifact.tar.gz?download=1" -O esop-artifact.tar.gz 17 | gunzip esop-artifact.tar.gz 18 | docker load -i esop-artifact.tar 19 | docker run -d \ 20 | --name esop-container \ 21 | -p 8080:8080 \ 22 | esop-artifact \ 23 | tail -f /dev/null 24 | - name: Testing the installation [`yarn test` -- `yarn test-all` errors with "No usable sandbox!" in GitHub Actions] 25 | run: | 26 | docker exec esop-container bash -c "yarn test" 27 | - name: Reproducing figures in Section 2 [not automated -- follow manual steps in artifact README.md] 28 | run: | 29 | # 30 | - name: Reproducing tables in Section 5 31 | run: | 32 | docker exec esop-container bash -c "yarn benchmark" 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .cache/ 2 | .psc*/ 3 | .spago 4 | dist/ 5 | tmp/ 6 | node_modules/ 7 | output/ 8 | output-es/ 9 | *.log 10 | .DS_Store 11 | .psc-ide-port 12 | .vscode/ 13 | artifact/*.tar.gz -------------------------------------------------------------------------------- /.tidyrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "importSort": "source", 3 | "importWrap": "source", 4 | "indent": 3, 5 | "operatorsFile": null, 6 | "ribbon": 1, 7 | "typeArrowPlacement": "first", 8 | "unicode": "source", 9 | "width": null 10 | } 11 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | } 3 | -------------------------------------------------------------------------------- /CITATION.cff: -------------------------------------------------------------------------------- 1 | cff-version: 1.0.0 2 | message: "If you use fluid in your own research, please cite it as below." 3 | authors: 4 | - family-names: "Perera" 5 | given-names: "Roly" 6 | orcid: https://orcid.org/0000-0001-9249-9862 7 | - family-names: "Bond" 8 | given-names: "Joseph" 9 | orcid: "https://orcid.org/0009-0008-5995-6994" 10 | - family-names: "David" 11 | given-names: "Cristina" 12 | orcid: "https://orcid.org/0000-0002-9106-934X" 13 | - family-names: "Nguyen" 14 | given-names: "Minh" 15 | orcid: "https://orcid.org/0000-0003-3845-9928" 16 | - family-names: "Orchard" 17 | given-names: "Dominic" 18 | orcid: "https://orcid.org/0000-0002-7058-7842" 19 | title: "fluid programming language" 20 | version: 0.7.30 21 | doi: 10.5281/zenodo.14637654 22 | date-released: 2025-01-13 23 | url: "https://github.com/explorable-viz/fluid" 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019-2025 Open Source Contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Fluid: Language-integrated data provenance 2 | 3 | Fluid is an experimental programming language which integrates a bidirectional dynamic analysis to connect outputs to data sources in a fine-grained way. Fluid is implemented in PureScript and runs in the browser. 4 | 5 | [![develop](https://github.com/explorable-viz/fluid/actions/workflows/develop.yml/badge.svg)](https://github.com/explorable-viz/fluid/actions/workflows/develop.yml) 6 | [![GitHub pages](https://github.com/explorable-viz/fluid/actions/workflows/pages/pages-build-deployment/badge.svg)](https://github.com/explorable-viz/fluid/actions/workflows/pages/pages-build-deployment) 7 | 8 | ## Installation 9 | 10 | ### Software required 11 | - git 12 | - Node.js >=14.0.0 13 | - yarn >= 1.22 14 | 15 | Additionally, for Windows users only: 16 | 17 | - [Ubuntu WSL](https://ubuntu.com/desktop/wsl) 18 | 19 | ### Building 20 | 21 | - Clone the repository (for Windows users, do this under the Ubuntu WSL) 22 | - Run `./script/setup/dev-setup.sh` from the top-level directory 23 | - `yarn install` to install Node dependencies 24 | - `yarn build` 25 | 26 | ## Use 27 | 28 | The following assumes you have already successfully run `yarn build` (see above). 29 | 30 | ### Running programs from the command line 31 | 32 | Fluid examples in the `dist/fluid/fluid` can be evaluated from the command line as follows 33 | (from the top-level directory): 34 | 35 | ``` 36 | npx fluid evaluate -f 37 | ``` 38 | Note that the path is relative and should not include the `.fld` extension, e.g. for the `range.fld` example: 39 | ``` 40 | % npx fluid evaluate -f example/range 41 | ((0, 0) : ((0, 1) : ((1, 0) : ((1, 1) : [])))) 42 | Success 43 | ``` 44 | 45 | ### Running websites locally (as part of Fluid development) 46 | As an example, to build and run the website `literate-execution`: 47 | - `yarn build` to ensure Fluid source code has been compiled (can be skipped on subsequent runs) 48 | - `yarn bundle-website -w literate-execution` (can be skipped if the website being run is `fluid-org`) 49 | - `yarn serve literate-execution` (you may be prompted to proceed; press `y`) 50 | - Open a browser to the served URL (defaults to `127.0.0.1:8080`) 51 | 52 | Note: `yarn bundle-serve literate-execution` is a convenient shorthand for `bundle-website -w` followed by `serve`. 53 | 54 | ## Testing 55 | 56 | ### Running the tests from the command line 57 | 58 | After building, tests can be run from the command line via `yarn test-all` 59 | 60 | ### Running tests in browser 61 | - As per command-line tests above, but run `yarn test-browser`, which opens 62 | a browser window. 63 | - To observe the status of tests, click `Debug` in the browser window, and then open the JavaScript Console for your browser (e.g., via the Developer Tools). 64 | 65 | ### Run Puppeteer tests for page Y of website X 66 | 67 | Rebuild with `puppeteerTests.headless` set to `false` to run in browser. Then: 68 | - `yarn bundle-website X` 69 | - `./script/test-page.sh X X.Y` 70 | 71 | ## Development via VS Code 72 | 73 | The following are some notes on developing Fluid using VS Code. 74 | 75 | - Avoid having PureScript installed globally 76 | - Install the PureScript IDE extension 77 | - In the PureScript IDE extension settings, select `Add Npm Path` 78 | 79 | - For Windows users: 80 | - Launch VSCode through Ubuntu (WSL) terminal 81 | - Install WSL extension in VSCode 82 | -------------------------------------------------------------------------------- /archive/0.3.1.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/explorable-viz/fluid/e42120612f714fe85676b28538ec3698cd9b2c58/archive/0.3.1.zip -------------------------------------------------------------------------------- /archive/0.6.1.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/explorable-viz/fluid/e42120612f714fe85676b28538ec3698cd9b2c58/archive/0.6.1.zip -------------------------------------------------------------------------------- /artifact/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:20-slim 2 | 3 | WORKDIR /usr/src/app 4 | 5 | 6 | RUN apt-get update 7 | RUN apt-get install -y wget gnupg 8 | RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - 9 | RUN echo "deb http://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google.list 10 | RUN apt-get update 11 | RUN apt-get install -y google-chrome-stable xvfb 12 | 13 | RUN apt-get update 14 | RUN apt-get install -y ca-certificates git zip unzip python3 python3-venv python3-pip texlive-latex-base texlive-latex-extra 15 | RUN python3 -m venv /usr/src/python/ 16 | RUN /usr/src/python/bin/pip3 install numpy pandas matplotlib jinja2 17 | RUN apt-get update && apt-get -y install sudo 18 | RUN sudo apt-get install libtinfo5 19 | RUN sudo apt-get clean && rm -rf /var/lib/apt/lists/* 20 | 21 | COPY package.json ./ 22 | COPY yarn.lock ./ 23 | 24 | RUN yarn install 25 | 26 | COPY . ./ 27 | 28 | EXPOSE 8080 29 | 30 | RUN useradd -ms /bin/bash evaluator && \ 31 | echo "evaluator ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers 32 | 33 | RUN chown -R evaluator /usr/src/app/benchmark 34 | 35 | USER evaluator 36 | 37 | RUN sudo yarn build 38 | RUN sudo yarn bundle-website -w Esop2025Artifact 39 | RUN sudo yarn bundle-website -w Misc 40 | -------------------------------------------------------------------------------- /artifact/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019-2025 Open Source Contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /artifact/REQUIREMENTS: -------------------------------------------------------------------------------- 1 | Instructions have been tested with the following specs: 2 | DOCKER IMAGE: 3 | ARCHITECTURE: x86_64 4 | SOFTWARE: Docker version 27.4.1 build b9d17ea, git, gunzip 5 | BUILD FROM SOURCE: 6 | SOFTWARE: git, Node.JS >= 14.0.0, yarn >= 1.22, python3 with maptlotlib, numpy, pandas, argparse, re 7 | 8 | PROCESSOR: Intel Core i7-10850H CPU @ 2.70GHz 9 | MEMORY: 16.4GB 10 | -------------------------------------------------------------------------------- /artifact/STATUS: -------------------------------------------------------------------------------- 1 | AVAILABLE: available at DOI: 10.5281/zenodo.14626987 2 | FUNCTIONAL: We provide instructions for installing 3 | and testing the installation, as well as reproducing figures and tables 4 | from the paper the artifact is supporting. 5 | REUSABLE: We provide instructions 6 | for an interested user to rebuild from source, and some information 7 | on how to construct additional experiments. 8 | -------------------------------------------------------------------------------- /artifact/script/bump_zenodo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -xe 4 | 5 | DEPOSIT="$1" 6 | 7 | NEW_ID=$(./script/zenodo_new_version.sh $DEPOSIT) 8 | 9 | if [ $NEW_ID == "null" ]; then 10 | echo "New version already pending publication." 11 | exit 1 12 | fi 13 | 14 | echo "NEW_ID = $NEW_ID" 15 | 16 | docker save esop-artifact -o esop-artifact.tar 17 | gzip esop-artifact.tar 18 | 19 | ./script/zenodo_upload.sh $NEW_ID esop-artifact.tar.gz 20 | ./script/zenodo_upload.sh $NEW_ID README.md 21 | ./script/zenodo_upload.sh $NEW_ID LICENSE 22 | ./script/zenodo_upload.sh $NEW_ID REQUIREMENTS 23 | ./script/zenodo_upload.sh $NEW_ID STATUS -------------------------------------------------------------------------------- /artifact/script/rebuild_benchmark.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -xe 3 | 4 | sudo /usr/src/python/bin/python3 script/python/plot_bench.py -t paper -b table-one -s benchmark/benchmarks_artifact.csv 5 | sudo /usr/src/python/bin/python3 script/python/plot_bench.py -t paper -b table-two -s benchmark/benchmarks_artifact.csv 6 | sudo /usr/src/python/bin/python3 script/python/plot_bench.py -t paper -b table-two -s benchmark/benchmarks_artifact.csv 7 | pushd benchmark 8 | pushd tex 9 | sudo pdflatex benchmarks.tex 10 | -------------------------------------------------------------------------------- /artifact/script/zenodo_new_version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Creates a new version of an existing deposit 3 | # 4 | # usage: ./zenodo_new_version.sh [deposit id] [--verbose|-v] 5 | # 6 | # on success returns deposit id 7 | 8 | set -ex 9 | 10 | DEPOSIT="$1" 11 | 12 | VERBOSE=0 13 | if [ "$2" == "--verbose" ] || [ "$2" == "-v" ]; then 14 | VERBOSE=1 15 | fi 16 | 17 | ZENODO_ENDPOINT=${ZENODO_ENDPOINT:-https://zenodo.org} 18 | 19 | DEPOSITION_ENDPOINT="${ZENODO_ENDPOINT}/api/deposit/depositions/${DEPOSIT}/actions/newversion" 20 | 21 | if [ "$VERBOSE" -eq 1 ]; then 22 | echo "Requesting to create a new version for deposit [${DEPOSIT}]..." 23 | fi 24 | 25 | curl --progress-bar \ 26 | --retry 5 \ 27 | --retry-delay 5 \ 28 | -H "Content-Type: application/json" \ 29 | -X POST\ 30 | --data "{}"\ 31 | "${DEPOSITION_ENDPOINT}?access_token=${ZENODO_TOKEN}"\ 32 | | jq .id 33 | -------------------------------------------------------------------------------- /artifact/script/zenodo_upload.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Upload big files to Zenodo. 3 | # 4 | # usage: ./zenodo_upload.sh [deposition id] [filename] [--verbose|-v] 5 | # 6 | 7 | set -e 8 | 9 | VERBOSE=0 10 | if [ "$3" == "--verbose" ] || [ "$3" == "-v" ]; then 11 | VERBOSE=1 12 | fi 13 | 14 | # strip deposition url prefix if provided; see https://github.com/jhpoelen/zenodo-upload/issues/2#issuecomment-797657717 15 | DEPOSITION=$( echo $1 | sed 's+^http[s]*://zenodo.org/deposit/++g' ) 16 | FILEPATH="$2" 17 | FILENAME=$(echo $FILEPATH | sed 's+.*/++g') 18 | FILENAME=${FILENAME// /%20} 19 | ZENODO_ENDPOINT=${ZENODO_ENDPOINT:-https://zenodo.org} 20 | 21 | BUCKET=$(curl ${ZENODO_ENDPOINT}/api/deposit/depositions/"$DEPOSITION"?access_token="$ZENODO_TOKEN" | jq --raw-output .links.bucket) 22 | 23 | if [ "$VERBOSE" -eq 1 ]; then 24 | echo "Deposition ID: $DEPOSITION" 25 | echo "File path: $FILEPATH" 26 | echo "File name: $FILENAME" 27 | echo "Bucket URL: $BUCKET" 28 | echo "Uploading file..." 29 | fi 30 | 31 | curl --progress-bar \ 32 | --retry 5 \ 33 | --retry-delay 5 \ 34 | -o /dev/null \ 35 | --upload-file "$FILEPATH" \ 36 | $BUCKET/"$FILENAME"?access_token="$ZENODO_TOKEN" 37 | -------------------------------------------------------------------------------- /benchmark/tex/benchmarks.tex: -------------------------------------------------------------------------------- 1 | \documentclass{article} 2 | 3 | \usepackage{booktabs} 4 | \usepackage{geometry} 5 | \newgeometry{left=3cm} 6 | 7 | \begin{document} 8 | \title{Benchmark Tables for Cognacy Queries over Dependence Graphs for \\Transparent Visualisations} 9 | {\small\input{table-one.tex}} 10 | {\small\input{table-two.tex}} 11 | {\small\input{table-three.tex}} 12 | 13 | \end{document} -------------------------------------------------------------------------------- /benchmark/tex/table-one.tex: -------------------------------------------------------------------------------- 1 | \begin{table} 2 | \caption{Tests: paper, Benches: table-one} 3 | \label{paper-table-one} 4 | \begin{tabular}{lllr} 5 | \toprule 6 | & T-Eval & G-Eval & EvalSlowdown \\ 7 | Test-Name & & & \\ 8 | \midrule 9 | edgeDetect & 897.1 ($\pm$47.3) & 2500.7 ($\pm$159.9) & 2.79 \\ 10 | emboss & 763.3 ($\pm$32.5) & 1918.2 ($\pm$73.9) & 2.51 \\ 11 | gaussian & 852.3 ($\pm$79.2) & 2039.3 ($\pm$104.1) & 2.39 \\ 12 | grouped-bar-chart & 100.7 ($\pm$5.4) & 1617.6 ($\pm$29.7) & 16.06 \\ 13 | line-chart & 130.0 ($\pm$5.0) & 1279.6 ($\pm$27.7) & 9.84 \\ 14 | stacked-bar-chart & 53.8 ($\pm$2.9) & 913.2 ($\pm$15.8) & 16.97 \\ 15 | bar-chart-line-chart & 3251.2 ($\pm$302.6) & 7143.3 ($\pm$277.4) & 2.20 \\ 16 | stacked-bar-scatter-plot & 1702.6 ($\pm$98.9) & 33288.0 ($\pm$2006.2) & 19.55 \\ 17 | \bottomrule 18 | \end{tabular} 19 | \end{table} 20 | -------------------------------------------------------------------------------- /benchmark/tex/table-three.tex: -------------------------------------------------------------------------------- 1 | \begin{table} 2 | \caption{Tests: paper, Benches: table-three} 3 | \label{paper-table-three} 4 | \begin{tabular}{llllrr} 5 | \toprule 6 | & T-DemBy & G-DemBy-Dir & G-DemBy-Suff & S & S' \\ 7 | Test-Name & & & & & \\ 8 | \midrule 9 | edgeDetect & 770.6 ($\pm$26.5) & 351.9 ($\pm$10.5) & 915.7 ($\pm$44.1) & 2.19 & 0.84 \\ 10 | emboss & 702.7 ($\pm$41.4) & 294.4 ($\pm$6.0) & 610.5 ($\pm$28.1) & 2.39 & 1.15 \\ 11 | gaussian & 666.5 ($\pm$20.9) & 334.4 ($\pm$13.9) & 648.8 ($\pm$20.6) & 1.99 & 1.03 \\ 12 | grouped-bar-chart & 88.2 ($\pm$3.8) & 6.4 ($\pm$0.4) & 1444.0 ($\pm$52.2) & 13.78 & 0.06 \\ 13 | line-chart & 107.3 ($\pm$4.6) & 4.0 ($\pm$0.2) & 622.6 ($\pm$11.5) & 26.82 & 0.17 \\ 14 | stacked-bar-chart & 44.6 ($\pm$1.7) & 3.6 ($\pm$0.2) & 783.5 ($\pm$13.3) & 12.39 & 0.06 \\ 15 | bar-chart-line-chart & 2679.9 ($\pm$137.1) & 26.2 ($\pm$0.9) & 3742.8 ($\pm$139.4) & 102.29 & 0.72 \\ 16 | stacked-bar-scatter-plot & 1313.4 ($\pm$84.1) & 68.6 ($\pm$2.9) & 28719.8 ($\pm$1533.5) & 19.15 & 0.05 \\ 17 | \bottomrule 18 | \end{tabular} 19 | \end{table} 20 | -------------------------------------------------------------------------------- /benchmark/tex/table-two.tex: -------------------------------------------------------------------------------- 1 | \begin{table} 2 | \caption{Tests: paper, Benches: table-two} 3 | \label{paper-table-two} 4 | \begin{tabular}{lllr} 5 | \toprule 6 | & T-Demands & G-Demands & Bwd-Speedup \\ 7 | Test-Name & & & \\ 8 | \midrule 9 | edgeDetect & 355.6 ($\pm$21.3) & 19.6 ($\pm$2.4) & 18.14 \\ 10 | emboss & 289.6 ($\pm$14.8) & 13.8 ($\pm$1.9) & 20.99 \\ 11 | gaussian & 296.6 ($\pm$16.0) & 12.8 ($\pm$0.3) & 23.17 \\ 12 | grouped-bar-chart & 47.7 ($\pm$0.8) & 2.5 ($\pm$0.1) & 19.08 \\ 13 | line-chart & 59.8 ($\pm$3.1) & 2.8 ($\pm$0.1) & 21.36 \\ 14 | stacked-bar-chart & 29.0 ($\pm$1.8) & 1.7 ($\pm$0.0) & 17.06 \\ 15 | bar-chart-line-chart & 688.5 ($\pm$17.7) & 39.1 ($\pm$1.9) & 17.61 \\ 16 | stacked-bar-scatter-plot & 392.2 ($\pm$19.0) & 26.7 ($\pm$2.2) & 14.69 \\ 17 | \bottomrule 18 | \end{tabular} 19 | \end{table} 20 | -------------------------------------------------------------------------------- /fluid/lib/convolution.fld: -------------------------------------------------------------------------------- 1 | let zero m n image = 2 | let (m_max, n_max) = dims image 3 | in if (m >= 1) `and` (m <= m_max) `and` (n >= 1) `and` (n <= n_max) 4 | then image!(m, n) 5 | else 0; 6 | 7 | let wrap m n image = 8 | let (m_max, n_max) = dims image 9 | in image!( ((m - 1) `mod` m_max) + 1, ((n - 1) `mod` n_max) + 1); 10 | 11 | let extend m n image = 12 | let (m_max, n_max) = dims image; 13 | m' = min (max m 1) m_max; 14 | n' = min (max n 1) n_max 15 | in image!(m', n'); 16 | 17 | let matrixSum matr = 18 | let (m, n) = dims matr 19 | in foldl (+) 0 [ matr!(i, j) | (i, j) <- range (1, 1) (m, n)]; 20 | 21 | let convolve image kernel lookup = 22 | let ((m, n), (i, j)) = (dims image, dims kernel); 23 | (half_i, half_j) = (i `quot` 2, j `quot` 2); 24 | area = i * j 25 | in [| let interMatrix = """Intermediate ${i}x${j} matrix for element (${m'}, ${n'})""" [| 26 | let x = m' + i' - 1 - half_i; 27 | y = n' + j' - 1 - half_j in 28 | """Multiply these two elements""" 29 | lookup x y image * kernel!(i', j') 30 | | (i', j') in (i, j) |] 31 | in matrixSum interMatrix `quot` area 32 | | (m', n') in (m, n) |]; 33 | -------------------------------------------------------------------------------- /fluid/lib/stats.fld: -------------------------------------------------------------------------------- 1 | let split [] = ([], []); 2 | split (x : xs) = 3 | let (ys, zs) = split xs in (x : zs, ys); 4 | 5 | let merge xs ys = 6 | match (xs, ys) as { 7 | ([], _) -> ys; 8 | (x : xs', []) -> xs; 9 | (x : xs', y : ys') -> 10 | if x < y 11 | then x : merge xs' ys 12 | else y : merge xs ys' 13 | }; 14 | 15 | let mergesort xs = 16 | if length xs < 2 17 | then xs 18 | else 19 | let (ys, zs) = split xs in 20 | merge (mergesort ys) (mergesort zs); 21 | 22 | -- assume data is sorted 23 | let findQuantile q p data = 24 | let rank = (p / q) * (length data - 1) 25 | in if rank == floor rank 26 | then nth rank data 27 | else let x1 = floor rank; 28 | x2 = ceiling rank; 29 | left = nth x1 data; 30 | right = nth x2 data 31 | in left + (rank - x1) * (right - left); 32 | 33 | let findPercentile = findQuantile 100; 34 | 35 | let accumBins data Nil = []; 36 | accumBins data [l] = []; 37 | accumBins data (l : r : es) = 38 | let (ge, le) = splitOn (fun x -> x <= r) data 39 | in (le , r - l) : accumBins ge (r : es); 40 | 41 | let cut data nbins = 42 | let low = minimum data; 43 | binwidth = (maximum data - low) / nbins; 44 | edges = [ low + (x * binwidth) | x <- enumFromTo 0 nbins ] 45 | in accumBins data edges; 46 | 47 | let qcut data qs = 48 | let low = minimum data; 49 | high = maximum data; 50 | edges = append (low : [ findPercentile x data | x <- qs], [high]) 51 | in accumBins data edges; 52 | 53 | let mkHistogram data dataName nbins = 54 | let binWidth = (maximum data - minimum data) / nbins 55 | in BarChart { 56 | caption: "Histogram of distribution of " ++ dataName, 57 | size: { width: 275, height: 185}, 58 | stackedBars: map (fun (data, edge) -> { x: data, bars: [{ y: "density", z: (length data) / binWidth }] }) (cut data nbins) 59 | }; 60 | 61 | let likelihoodLE data target = 62 | length (filter (fun x -> x <= target) data) / length data; 63 | 64 | let likelihoodGE data target = 65 | length (filter (fun x -> x >= target) data) / length data; 66 | 67 | let likelihoodMap table prob = (fromSome (find (fun x -> x.prob <= prob) table)).msg; 68 | 69 | let mkPercent num = (numToStr (num * 100)) ++ "%"; 70 | 71 | let leqP n m = 72 | if n <= m 73 | then "less" 74 | else "more"; 75 | 76 | let gradedLeqP n m = 77 | let ratio = n / m 78 | in if ratio <= 1.0 79 | then if ratio <=0.5 80 | then "much less" 81 | else "less" 82 | else if ratio >= 2.0 83 | then "much more" 84 | else "more"; 85 | -------------------------------------------------------------------------------- /karma.conf.test.js: -------------------------------------------------------------------------------- 1 | const { log } = require("console") 2 | 3 | module.exports = function (config) { 4 | config.set({ 5 | autoWatch: true, 6 | basePath: "", 7 | browsers: ["ChromeHeadlessNoSandbox"], 8 | browserDisconnectTimeout: 600000, 9 | browserNoActivityTimeout: 2400000, 10 | customLaunchers: { 11 | ChromeHeadlessNoSandbox: { 12 | base: 'ChromeHeadless', 13 | flags: [ 14 | '--disable-gpu', 15 | '--no-sandbox' 16 | ] 17 | } 18 | }, 19 | client: { 20 | mocha: { 21 | timeout: 600000 22 | } 23 | }, 24 | colors: true, 25 | failOnEmptyTestSuite: true, 26 | files: [ 27 | "./dist/test/fluid.js", 28 | { 29 | pattern: "./fluid/**/*.fld", 30 | watched: true, 31 | included: false, 32 | served: true, 33 | nocache: false 34 | }, 35 | { 36 | pattern: "./test/**/*.fld", 37 | watched: true, 38 | included: false, 39 | served: true, 40 | nocache: false, 41 | } 42 | ], 43 | frameworks: ["mocha"], 44 | logLevel: config.LOG_ERROR, 45 | proxies: { 46 | "/fluid/": "/base/fluid/", 47 | "/test/fluid/": "/base/test/fluid/" 48 | }, 49 | reporters: ["mocha"], 50 | singleRun: true 51 | }) 52 | } 53 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@explorable-viz/fluid", 3 | "version": "0.7.89", 4 | "description": "Fluid is an experimental programming language which integrates a bidirectional dynamic analysis to connect outputs to data sources in a fine-grained way. Fluid is implemented in PureScript and runs in the browser.", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/explorable-viz/fluid.git#release" 9 | }, 10 | "keywords": [ 11 | "PureScript", 12 | "JavaScript" 13 | ], 14 | "author": "Fluid Contributors", 15 | "license": "MIT", 16 | "bugs": { 17 | "url": "https://github.com/explorable-viz/fluid/issues" 18 | }, 19 | "homepage": "https://f.luid.org", 20 | "files": [ 21 | "dist/fluid/shared", 22 | "dist/fluid/fluid/lib", 23 | "script/bundle-website.sh", 24 | "script/bundle-page.sh", 25 | "script/util/clean.sh", 26 | "script/util/lisp-case.sh" 27 | ], 28 | "scripts": { 29 | "benchmark": "./script/benchmark.sh", 30 | "build": "./script/build.sh", 31 | "build-test": "./script/build-test.sh", 32 | "bundle-fluid": "./script/bundle-fluid.sh", 33 | "bundle-serve": "sh -c './script/bundle-website.sh -w \"$@\" && ./script/serve.sh \"$1\"' _", 34 | "bundle-website": "./script/bundle-website.sh", 35 | "fluid": "node ./dist/fluid/shared/fluid.mjs", 36 | "npm-publish": "./script/npm-publish.sh", 37 | "serve": "./script/serve.sh", 38 | "test": "./script/test.sh", 39 | "test-all": "./script/test-all.sh", 40 | "test-browser": "./script/test.sh --browsers=Chrome --singleRun=false", 41 | "test-page": "./script/test-page.sh", 42 | "test-website": "./script/test-website.sh", 43 | "test-website-all": "./script/test-website-all.sh", 44 | "tidy": "./script/tidy.sh" 45 | }, 46 | "dependencies": { 47 | "@codemirror/commands": "6.2.2", 48 | "@codemirror/state": "6.2.0", 49 | "@codemirror/view": "6.9.3", 50 | "benchmark": "^2.1.4", 51 | "commander": "^12.1.0", 52 | "d3": "^7.9.0", 53 | "datetime": "^0.0.3", 54 | "express": "^4.19.2", 55 | "express-static": "^1.2.6", 56 | "http-serve": "^1.0.1", 57 | "http-shutdown": "^1.2.2", 58 | "node": "^22.11.0", 59 | "node-process": "^1.0.1", 60 | "os": "^0.1.2", 61 | "util": "^0.12.5" 62 | }, 63 | "devDependencies": { 64 | "esbuild": "0.15.1", 65 | "gh-pages": "^6.1.1", 66 | "karma": "6.4.0", 67 | "karma-chrome-launcher": "3.1.1", 68 | "karma-mocha": "2.0.1", 69 | "karma-mocha-reporter": "2.2.5", 70 | "mocha": "^10.0.0", 71 | "puppeteer": "^23.2.0", 72 | "purescript": "^0.15.10", 73 | "purescript-language-server": "0.16.6", 74 | "purescript-psa": "0.8.2", 75 | "purs-backend-es": "1.4.2", 76 | "purs-tidy": "^0.9.3", 77 | "spago": "^0.21.0", 78 | "typescript": "^5.5.3" 79 | }, 80 | "bin": { 81 | "fluid": "./dist/fluid/shared/fluid.mjs", 82 | "website-test": "./dist/fluid/shared/website-test.js" 83 | }, 84 | "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" 85 | } 86 | -------------------------------------------------------------------------------- /script/archive/v0.6.1.sh: -------------------------------------------------------------------------------- 1 | # Run in gh-pages branch (v0.6.1) to generate archive of that version 2 | #!/usr/bin/env bash 3 | set -xe 4 | 5 | NAME=v0.6.1.zip 6 | 7 | zip -r \ 8 | --exclude $NAME \ 9 | .git/\* \ 10 | .spago/\* \ 11 | node_modules/\* \ 12 | output/\* \ 13 | output-es/\* \ 14 | - $NAME . > $NAME 15 | -------------------------------------------------------------------------------- /script/benchmark.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -xe 3 | 4 | node output-es/Benchmark/index.mjs 5 | -------------------------------------------------------------------------------- /script/build-test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -xe 3 | 4 | yarn build # bundles FluidOrg 5 | yarn bundle-website -w esop2025-artifact 6 | yarn bundle-website -w misc 7 | yarn test-all 8 | -------------------------------------------------------------------------------- /script/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -xe 3 | 4 | rm -rf dist/ 5 | ./script/util/compile.sh 6 | . script/util/clean.sh test 7 | . script/util/bundle.sh test Test.Test 8 | ./script/bundle-benchmark.sh 9 | ./script/bundle-fluid.sh 10 | ./script/bundle-libraries.sh 11 | ./script/bundle-fluid-org.sh 12 | -------------------------------------------------------------------------------- /script/bundle-benchmark.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -xe 3 | 4 | yarn purs-backend-es bundle-app --main Benchmark --to output-es/Benchmark/index.mjs --platform=node -------------------------------------------------------------------------------- /script/bundle-fluid-org.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -xe 3 | 4 | WEBSITE=fluid-org 5 | yarn bundle-website -w $WEBSITE 6 | 7 | unzip -o archive/0.3.1.zip -d dist/$WEBSITE > /dev/null # already has 0.3.1 as top-level folder 8 | unzip -o archive/0.6.1.zip -d dist/$WEBSITE/0.6.1 > /dev/null 9 | -------------------------------------------------------------------------------- /script/bundle-fluid.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -xe 3 | 4 | FLUID_EXECUTABLE="dist/fluid/shared/fluid.mjs" 5 | SHEBANG="#!/usr/bin/env node" 6 | 7 | yarn purs-backend-es bundle-app --main Fluid --to $FLUID_EXECUTABLE --platform=node 8 | 9 | if [[ ! -f "$FLUID_EXECUTABLE" ]]; then 10 | echo "Error: File $FLUID_EXECUTABLE not found." 11 | exit 1 12 | fi 13 | 14 | if [[ $(head -n 1 "$FLUID_EXECUTABLE") != "$SHEBANG" ]]; then 15 | { echo "$SHEBANG"; cat "$FLUID_EXECUTABLE"; } > "$FLUID_EXECUTABLE.tmp" && mv "$FLUID_EXECUTABLE.tmp" "$FLUID_EXECUTABLE" 16 | fi 17 | 18 | chmod +x "$FLUID_EXECUTABLE" 19 | cp -r fluid dist/fluid 20 | -------------------------------------------------------------------------------- /script/bundle-libraries.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -xe 3 | 4 | ./script/util/bundle-module.sh load-figure.js App.LoadFigure 5 | # See https://discourse.purescript.org/t/purescript-0-15-spago-affjax/3045/3 6 | # Using bundle-module.sh results in mixed module, which does not work well, therefore force puppeteer 7 | # to bundle to CommonJS module 8 | esbuild ./output-es/Test.Util.Puppeteer/index.js --bundle --platform=node > dist/fluid/shared/webtest-lib.js 9 | 10 | 11 | WEBTEST_EXECUTABLE="dist/fluid/shared/website-test.js" 12 | SHEBANG="#!/usr/bin/env node" 13 | 14 | cp website-test.js dist/fluid/shared/website-test.js 15 | 16 | if [[ ! -f "$WEBTEST_EXECUTABLE" ]]; then 17 | echo "Error: File $WEBTEST_EXECUTABLE not found." 18 | exit 1 19 | fi 20 | 21 | if [[ $(head -n 1 "$WEBTEST_EXECUTABLE") != "$SHEBANG" ]]; then 22 | { echo "$SHEBANG"; cat "$WEBTEST_EXECUTABLE"; } > "$WEBTEST_EXECUTABLE.tmp" && mv "$WEBTEST_EXECUTABLE.tmp" "$WEBTEST_EXECUTABLE" 23 | fi 24 | 25 | chmod +x "$WEBTEST_EXECUTABLE" 26 | -------------------------------------------------------------------------------- /script/bundle-website.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -xeu 3 | 4 | PREFIX="" 5 | 6 | while getopts "w:l" opt; do 7 | case $opt in 8 | w) WEBSITE="$OPTARG";; 9 | l) PREFIX=node_modules/@explorable-viz/fluid;; 10 | esac 11 | done 12 | 13 | PREFIX_=${PREFIX:+$PREFIX/} 14 | echo "Cleaning dist/$WEBSITE" 15 | . "${PREFIX_}script/util/clean.sh" $WEBSITE 16 | 17 | shopt -s nullglob 18 | 19 | echo "Processing other static files:" 20 | set +xu # try to remove +u 21 | TO_COPY=() 22 | shopt -s dotglob extglob 23 | for CHILD in website/$WEBSITE/!(.|..); do 24 | BASENAME="$(basename "$CHILD")" 25 | if [[ "$BASENAME" =~ ^[a-z.] ]]; then 26 | TO_COPY+=("$CHILD") 27 | fi 28 | done 29 | shopt -u extglob dotglob 30 | set -xu 31 | 32 | for CHILD in "${TO_COPY[@]}"; do 33 | cp -rL "$CHILD" dist/$WEBSITE 34 | done 35 | 36 | echo "Processing Fluid source files:" 37 | cp -r "${PREFIX_}dist/fluid/fluid" dist/$WEBSITE 38 | [ -d "website/$WEBSITE/fluid" ] && cp -r "website/$WEBSITE/fluid" dist/$WEBSITE 39 | 40 | echo "Processing shared JavaScript files:" 41 | cp -r "${PREFIX_}dist/fluid/shared" dist/$WEBSITE 42 | 43 | echo "Bundled website $WEBSITE" 44 | -------------------------------------------------------------------------------- /script/git/hooks/pre-push: -------------------------------------------------------------------------------- 1 | 2 | #!/usr/bin/env bash 3 | # (ensure we pull bash from path not /usr/bin) 4 | set -e 5 | 6 | if git status --porcelain | grep -q '^[ M]' 7 | then 8 | echo "Locally modified files. Stash or discard before pushing." 9 | exit 1 10 | else 11 | echo "No locally modified files. Checking format." 12 | yarn tidy 13 | if git status --porcelain | grep -q '^[ M]' 14 | then 15 | echo "Format fixed. Commit again before pushing." 16 | exit 1 17 | fi 18 | echo "Pushing." 19 | fi 20 | -------------------------------------------------------------------------------- /script/npm-publish.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # run from project root 3 | set -xe 4 | 5 | npm config set '//registry.npmjs.org/:_authToken' "${NPM_TOKEN}" 6 | yarn publish --access public 7 | -------------------------------------------------------------------------------- /script/python/bwd_perf.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pandas as pd 3 | import matplotlib.pyplot as plt 4 | 5 | def parse(): 6 | # Read benchmark csv 7 | benchmarks = pd.read_csv('Benchmarks/benchmarks.csv', skipinitialspace=True, delimiter=',', index_col='Test-Name') 8 | 9 | # Extract test names of interest 10 | test_names = ['convolution/edgeDetect', 'convolution/emboss', 'convolution/gaussian', 'graphics/grouped-bar-chart', 'graphics/line-chart', 'graphics/stacked-bar-chart', 'slicing/dtw/compute-dtw'] 11 | df = pd.DataFrame(benchmarks.loc[test_names]) 12 | print(df[['Trace-Eval', 'Trace-Bwd', 'Graph-Eval', 'Graph-Bwd']]) 13 | 14 | # Reorder benchmark columns 15 | column_order = ['Trace-Eval', 'Trace-Bwd', 'Trace-Fwd', 'Graph-Eval', 'Graph-Bwd', 'Graph-BwdDual', 'Graph-BwdAll', 'Graph-Fwd', 'Graph-FwdDual', 'Graph-FwdAsDeMorgan'] 16 | # column_colors = ['#0d6b12', '#93dbb5', '#3e3875', '#b8bef5', '#910303', '#e84d4d', '#e8bceb', '#5073a1'] 17 | 18 | # Plot as bar chart 19 | df[column_order].plot( kind="bar" 20 | # , color=column_colors 21 | , ylabel="Milliseconds", rot=0) 22 | 23 | # Inserting a coloured horizontal line just to make clearer which columns have zero values 24 | plt.ylim(bottom=-10) 25 | plt.gca().axhline(0, lw=0.3, color='blue', label="Zero accuracy") 26 | 27 | plt.show() 28 | parse() -------------------------------------------------------------------------------- /script/serve.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -xe 3 | 4 | if [ -z "$1" ]; then 5 | echo "Please specify subfolder of 'dist' to serve content from." >&2 6 | exit 1 7 | fi 8 | 9 | if [ ! -d "dist/$1" ]; then 10 | echo "Error: Directory 'dist/$1' does not exist." >&2 11 | exit 1 12 | fi 13 | 14 | npx http-serve dist/$1 -a 0.0.0.0 -d -c-1 15 | -------------------------------------------------------------------------------- /script/setup/delete-workflow-runs.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # https://stackoverflow.com/questions/57927115 3 | set -xe 4 | 5 | export OWNER="explorable-viz" 6 | export REPOSITORY="fluid" 7 | export WORKFLOW="purescript" 8 | 9 | gh api -X GET /repos/$OWNER/$REPOSITORY/actions/runs --paginate \ 10 | | jq '.workflow_runs[] | select(.name == '\"$WORKFLOW\"') | .id' \ 11 | | xargs -t -I{} gh api -X DELETE /repos/$OWNER/$REPOSITORY/actions/runs/{} 12 | -------------------------------------------------------------------------------- /script/setup/dev-setup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # run from project root 3 | # set -x 4 | set -e 5 | 6 | git config --local include.path "../.gitconfig.include" # install Git aliases 7 | ./script/setup/install-hooks.sh 8 | -------------------------------------------------------------------------------- /script/setup/install-hooks.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # run from project root 3 | # set -x 4 | set -e 5 | 6 | echo "(Re)installing Git hooks." 7 | rm -f .git/hooks/* 8 | cp script/git/hooks/* .git/hooks 9 | -------------------------------------------------------------------------------- /script/test-all.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -xe 3 | 4 | . script/test.sh 5 | . script/test-website-all.sh 6 | -------------------------------------------------------------------------------- /script/test-page.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | yarn puppeteer browsers install chrome 5 | yarn puppeteer browsers install firefox 6 | 7 | WEBSITE=$1 8 | MODULE=$2 9 | 10 | # don't need to have "deployed" this to dist/ 11 | # instead the following just picks up from output-es/ 12 | WEBSITE_LISP_CASE=$(./script/util/lisp-case.sh "$WEBSITE") 13 | node website-test.js $WEBSITE_LISP_CASE /output-es/Website.Test.$MODULE/index.js 14 | -------------------------------------------------------------------------------- /script/test-website-all.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # run from project root 3 | set -xe 4 | 5 | WEBSITES=($(for FILE in website/Test/* website/Test/*.purs; do 6 | basename "$FILE" | sed 's/\.[^.]*$//' 7 | done | sort -u)) 8 | 9 | for WEBSITE in "${WEBSITES[@]}"; do 10 | . script/test-website.sh $WEBSITE 11 | done 12 | -------------------------------------------------------------------------------- /script/test-website.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # run from project root 3 | set -e 4 | 5 | WEBSITE=$1 6 | 7 | echo "Testing website: ${WEBSITE}" 8 | 9 | if [[ -e "website/Test/$WEBSITE.purs" ]]; then 10 | . script/test-page.sh $WEBSITE $WEBSITE 11 | fi 12 | 13 | if [[ -e "website/Test/$WEBSITE" ]]; then 14 | PAGES=($(for FILE in website/Test/$WEBSITE/*.purs; do 15 | basename "$FILE" | sed 's/\.[^.]*$//' 16 | done | sort -u)) 17 | else 18 | PAGES=() 19 | fi 20 | 21 | echo "Processing ${#PAGES[@]} additional Test/${WEBSITE} pages: ${PAGES[@]}" 22 | 23 | for PAGE in "${PAGES[@]}"; do 24 | . script/test-page.sh $WEBSITE $WEBSITE.$PAGE 25 | done 26 | -------------------------------------------------------------------------------- /script/test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -xe 3 | 4 | karma start karma.conf.test.js $@ 5 | -------------------------------------------------------------------------------- /script/tidy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -xe 3 | 4 | # Got bored of trying to make recursive globs work cross-platform 5 | yarn purs-tidy format-in-place \ 6 | src/*.purs \ 7 | src/**/*.purs \ 8 | src/App/**/*.purs \ 9 | src/App/View/**/*.purs \ 10 | test/*.purs \ 11 | test/**/*.purs \ 12 | website/*.purs \ 13 | website/**/*.purs \ 14 | > /dev/null 15 | -------------------------------------------------------------------------------- /script/util/bundle-module.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -xe 3 | yarn purs-backend-es bundle-module -m $2 --to dist/fluid/shared/$1 ${@:3} > /dev/null 4 | -------------------------------------------------------------------------------- /script/util/bundle.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -xe 3 | yarn purs-backend-es bundle-app --main $2 --to dist/$1/fluid.js ${@:3} > /dev/null 4 | -------------------------------------------------------------------------------- /script/util/clean.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -xe 3 | 4 | rm -rf dist/$1 5 | mkdir -p dist/$1 6 | -------------------------------------------------------------------------------- /script/util/compile.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -xe 3 | 4 | rm -rf output 5 | rm -rf output-es 6 | yarn tidy 7 | yarn spago build --purs-args '--strict --censor-codes=UserDefinedWarning' 8 | -------------------------------------------------------------------------------- /script/util/lisp-case.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | toLispCase() { 5 | INPUT="$1" 6 | RESULT=$(echo "$INPUT" | sed -E 's/([a-z0-9])([A-Z])/\1-\2/g' | tr '[:upper:]' '[:lower:]') 7 | echo "$RESULT" 8 | } 9 | 10 | toLispCase "$1" 11 | -------------------------------------------------------------------------------- /spago.dhall: -------------------------------------------------------------------------------- 1 | {- 2 | Welcome to a Spago project! 3 | You can edit this file as you like. 4 | -} 5 | { name = "" 6 | , dependencies = 7 | [ "aff" 8 | , "aff-promise" 9 | , "affjax" 10 | , "affjax-web" 11 | , "argonaut-codecs" 12 | , "arrays" 13 | , "bifunctors" 14 | , "console" 15 | , "control" 16 | , "debug" 17 | , "effect" 18 | , "either" 19 | , "exceptions" 20 | , "exists" 21 | , "filterable" 22 | , "foldable-traversable" 23 | , "foreign" 24 | , "foreign-object" 25 | , "functions" 26 | , "functors" 27 | , "graphs" 28 | , "http-methods" 29 | , "identity" 30 | , "integers" 31 | , "lists" 32 | , "maybe" 33 | , "newtype" 34 | , "node-buffer" 35 | , "node-child-process" 36 | , "node-fs" 37 | , "nonempty" 38 | , "numbers" 39 | , "optparse" 40 | , "ordered-collections" 41 | , "parsing" 42 | , "partial" 43 | , "prelude" 44 | , "profunctor" 45 | , "st" 46 | , "strings" 47 | , "tailrec" 48 | , "toppokki" 49 | , "transformers" 50 | , "tuples" 51 | , "unfoldable" 52 | , "unicode" 53 | , "unsafe-coerce" 54 | , "web-events" 55 | ] 56 | , packages = ./packages.dhall 57 | , sources = [ "src/**/*.purs", "test/**/*.purs", "website/**/*.purs" ] 58 | , backend = "purs-backend-es build" 59 | } 60 | -------------------------------------------------------------------------------- /src/App/CodeMirror.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | import * as d3 from "d3" 4 | 5 | // This prelude currently duplicated across all FFI implementations. 6 | function curry2 (f) { 7 | return x1 => x2 => f(x1, x2) 8 | } 9 | 10 | function curry3 (f) { 11 | return x1 => x2 => x3 => f(x1, x2, x3) 12 | } 13 | 14 | function curry4 (f) { 15 | return x1 => x2 => x3 => x4 => f(x1, x2, x3, x4) 16 | } 17 | 18 | import {EditorState} from "@codemirror/state" 19 | import {EditorView, keymap, lineNumbers} from "@codemirror/view" 20 | import {defaultKeymap} from "@codemirror/commands" 21 | 22 | let startState = EditorState.create({ 23 | doc: "", 24 | extensions: 25 | [ keymap.of(defaultKeymap) 26 | , EditorView.editable.of(false) 27 | , lineNumbers() 28 | ] 29 | }) 30 | 31 | function getContentsLength_ (ed) { 32 | return ed.state.doc.length 33 | } 34 | 35 | function addEditorView_ (id) { 36 | return () => { 37 | const div = d3.select('#' + id).node() 38 | return new EditorView({ 39 | state: startState, 40 | parent: div, 41 | }) 42 | } 43 | } 44 | 45 | function replaceSelection_ (editorState, str) { 46 | return editorState.replaceSelection(str) 47 | } 48 | 49 | function dispatch_ (editorView, tr) { 50 | return () => { 51 | editorView.dispatch(tr) 52 | } 53 | } 54 | 55 | function update_(editorState, specs) { 56 | return () => { 57 | return editorState.update(...specs) 58 | } 59 | } 60 | 61 | export var addEditorView = addEditorView_ 62 | export var dispatch = curry2(dispatch_) 63 | export var getContentsLength = getContentsLength_ 64 | export var replaceSelection = curry2(replaceSelection_) 65 | export var update = curry2(update_) 66 | -------------------------------------------------------------------------------- /src/App/CodeMirror.purs: -------------------------------------------------------------------------------- 1 | module App.CodeMirror where 2 | 3 | import Prelude 4 | import App.View.Util (HTMLId) 5 | import Effect (Effect) 6 | 7 | -- The CodeMirror API, documented in TypeScript, is horrendous. Expose the smallest subset that 8 | -- supports our use cases. 9 | 10 | type ChangeSpec = 11 | { from :: Int 12 | , to :: Int 13 | , insert :: String 14 | } 15 | 16 | type EditorState = 17 | { 18 | 19 | } 20 | 21 | type EditorView = 22 | { state :: EditorState 23 | } 24 | 25 | type Transaction = 26 | { 27 | } 28 | 29 | type TransactionSpec = 30 | { changes :: ChangeSpec 31 | } 32 | 33 | foreign import addEditorView :: HTMLId -> Effect EditorView 34 | foreign import dispatch :: EditorView -> Transaction -> Effect Unit 35 | foreign import getContentsLength :: EditorView -> Int 36 | foreign import replaceSelection :: EditorState -> String -> TransactionSpec 37 | foreign import update :: EditorState -> Array TransactionSpec -> Effect Transaction 38 | -------------------------------------------------------------------------------- /src/App/LoadFigure.purs: -------------------------------------------------------------------------------- 1 | -- Should this be src/Αpp rather than website/Website.LoadFigure? 2 | module App.LoadFigure where 3 | 4 | import Prelude hiding (absurd) 5 | 6 | import Affjax.ResponseFormat (json) 7 | import Affjax.Web (get, printError) 8 | import App.Fig (drawFig, drawFile, loadFig) 9 | import App.Util (runAffs_) 10 | import App.View.Util (FigSpec) 11 | import Bind (Bind) 12 | import Data.Argonaut.Decode (decodeJson) 13 | import Data.Either (Either(..)) 14 | import Data.Maybe (Maybe(..)) 15 | import Data.Tuple (uncurry) 16 | import Effect (Effect) 17 | import Graph (DVertex'(..)) 18 | import Module.Web (File(..), Folder(..), loadFile') 19 | import Util (error, (×)) 20 | import Val (BaseVal(..), MatrixDim(..), MatrixRep(..), Val(..), asVal) 21 | 22 | type JsonSpec = 23 | { fluidSrcPath :: Array String 24 | , datasets :: Array (Bind String) 25 | , imports :: Array String 26 | , file :: String 27 | , inputs :: Array String 28 | , query :: Boolean 29 | } 30 | 31 | figSpecFromJson :: JsonSpec -> FigSpec 32 | figSpecFromJson spec = 33 | { fluidSrcPaths: Folder <$> spec.fluidSrcPath 34 | , datasets: spec.datasets 35 | , imports: spec.imports 36 | , file: File spec.file 37 | , inputs: spec.inputs 38 | , query: 39 | if spec.query then 40 | Just $ asVal >=> case _ of 41 | v@(Val α (Matrix (MatrixRep (_ × MatrixDim (3 × _) × MatrixDim (3 × _))))) -> Just $ DVertex (α × v) 42 | _ -> Nothing 43 | else Nothing 44 | } 45 | 46 | loadFigure :: String -> Effect Unit 47 | loadFigure fileName = runAffs_ (uncurry drawFig) 48 | [ do 49 | -- TODO: simplify 50 | result <- get json fileName 51 | case result of 52 | Left err -> error ("Json fetching failed with " <> printError err) 53 | Right response -> 54 | case decodeJson response.body of 55 | Left err -> error ("JSON decoding failed with " <> show err) 56 | Right spec -> do 57 | ("fig" × _) <$> loadFig (figSpecFromJson spec) 58 | ] 59 | 60 | drawCode :: String -> String -> Effect Unit 61 | drawCode folder file = runAffs_ drawFile 62 | [ loadFile' [ Folder folder ] (File file) 63 | ] 64 | -------------------------------------------------------------------------------- /src/App/View.purs: -------------------------------------------------------------------------------- 1 | module App.View where 2 | 3 | import Prelude hiding (absurd) 4 | 5 | import App.Util (SelStates, 𝕊, dict, from) 6 | import App.View.BarChart (BarChart) 7 | import App.View.LineChart (LineChart) 8 | import App.View.Paragraph (Paragraph) 9 | import App.View.MatrixView (MatrixView(..), matrixRep) 10 | import App.View.MultiView (MultiView(..)) 11 | import App.View.ScatterPlot (ScatterPlot) 12 | import App.View.TableView (TableView(..), arrayDictToArray2, defaultFilter, headers) 13 | import App.View.Util (View, pack) 14 | import Data.List (List(..), (:)) 15 | import Data.Maybe (Maybe(..)) 16 | import Data.Tuple (snd) 17 | import DataType (cBarChart, cCons, cLineChart, cMultiView, cNil, cParagraph, cScatterPlot) 18 | import Dict (Dict) 19 | import Util (type (×)) 20 | import Val (BaseVal(..), Val(..)) 21 | 22 | -- Convert annotated value to appropriate view, discarding top-level annotations for now. 23 | -- Ignore view state for now.. 24 | view :: Partial => String -> Val (SelStates 𝕊) -> Maybe View -> View 25 | view title (Val _ (Constr c (u : Nil))) _ 26 | | c == cBarChart = pack (dict from u :: BarChart) 27 | | c == cLineChart = pack (dict from u :: LineChart) 28 | | c == cScatterPlot = pack (dict from u :: ScatterPlot) 29 | | c == cParagraph = pack (from u :: Paragraph (SelStates 𝕊)) 30 | | c == cMultiView = pack (MultiView (vws <*> (const Nothing <$> vws))) 31 | where 32 | vws = view title <$> ((from u :: Dict (SelStates 𝕊 × Val (SelStates 𝕊))) # map snd) 33 | view title u@(Val _ (Constr c _)) _ 34 | | c == cNil || c == cCons = pack (TableView { title, filter: defaultFilter, colNames, rows }) 35 | where 36 | records = dict identity <$> from u 37 | colNames = headers records 38 | rows = arrayDictToArray2 colNames records <#> map snd 39 | view title (Val _ (Matrix r)) _ = 40 | pack (MatrixView { title, matrix: matrixRep r }) 41 | -------------------------------------------------------------------------------- /src/App/View/MultiView.purs: -------------------------------------------------------------------------------- 1 | module App.View.MultiView where 2 | 3 | import Prelude 4 | 5 | import App.Util.Selector (multiViewEntry) 6 | import App.View.Util (class Drawable, class Drawable2, View, drawView) 7 | import App.View.Util.D3 (ElementType(..), create) 8 | import App.View.Util.D3 as D3 9 | import Bind ((↦)) 10 | import Data.Foldable (sequence_) 11 | import Data.Newtype (class Newtype) 12 | import Dict (Dict) 13 | import Effect (Effect) 14 | import Util (error) 15 | import Util.Map (mapWithKey) 16 | import Web.Event.EventTarget (EventListener) 17 | 18 | newtype MultiView = MultiView (Dict View) 19 | 20 | instance Drawable MultiView where 21 | draw { divId, view: MultiView views } figVal figView redraw = 22 | sequence_ $ flip mapWithKey views \x view -> 23 | drawView { divId, suffix: x, view } (multiViewEntry x >>> figVal) figView redraw 24 | 25 | instance Drawable2 MultiView where 26 | createRootElement = createRootElement' 27 | setSelStates = setSelStates 28 | 29 | createRootElement' :: MultiView -> D3.Selection -> String -> Effect D3.Selection 30 | createRootElement' (MultiView views) div childId = do 31 | _ <- div # create G [ "id" ↦ childId ] 32 | sequence_ $ flip mapWithKey views \_ _ -> do 33 | error "todo" 34 | error "todo" 35 | 36 | setSelStates :: MultiView -> EventListener -> D3.Selection -> Effect Unit 37 | setSelStates _ = error "todo" 38 | 39 | derive instance Newtype MultiView _ 40 | -------------------------------------------------------------------------------- /src/App/View/ScatterPlot.purs: -------------------------------------------------------------------------------- 1 | module App.View.ScatterPlot where 2 | 3 | import Prelude 4 | 5 | import App.Util (class Reflect, SelStates, Selectable, 𝕊, dict, from, isPrimary, isSecondary) 6 | import App.Util.Selector (ViewSelSetter, scatterPlot, scatterPoint) 7 | import App.View.Util (class Drawable, class Drawable2, UIHelpers, draw', selListener, uiHelpers) 8 | import App.View.Util.D3 as D3 9 | import App.View.Util.Point (Point(..)) 10 | import Bind ((⟼)) 11 | import Data.Int (toNumber) 12 | import Data.Tuple (snd) 13 | import DataType (f_caption, f_points, f_labels) 14 | import Dict (Dict) 15 | import Effect (Effect) 16 | import Foreign.Object (Object, fromFoldable) 17 | import Lattice ((∨)) 18 | import Primitive (string, unpack) 19 | import Util (type (×), (!)) 20 | import Util.Map (get) 21 | import Val (Val) 22 | import Web.Event.EventTarget (EventListener) 23 | 24 | newtype ScatterPlot = ScatterPlot 25 | { caption :: Selectable String 26 | , points :: Array (Point Number) 27 | , labels :: Point String 28 | } 29 | 30 | type ScatterPlotHelpers = 31 | { point_attrs :: ScatterPlot -> PointIndex -> Object String 32 | } 33 | 34 | foreign import createRootElement2 :: UIHelpers -> ScatterPlot -> D3.Selection -> String -> Effect D3.Selection 35 | foreign import setSelStates2 :: ScatterPlotHelpers -> UIHelpers -> ScatterPlot -> EventListener -> D3.Selection -> Effect Unit 36 | 37 | scatterPlotHelpers :: ScatterPlotHelpers 38 | scatterPlotHelpers = 39 | { point_attrs 40 | } 41 | where 42 | point_attrs :: ScatterPlot -> PointIndex -> Object String 43 | point_attrs (ScatterPlot { points }) { i } = 44 | fromFoldable 45 | [ "r" ⟼ toNumber point_smallRadius * if isPrimary sel then 1.6 else if isSecondary sel then 1.25 else 1.0 ] 46 | where 47 | Point { x, y } = points ! i 48 | sel = snd x ∨ snd y 49 | point_smallRadius = 2 50 | 51 | instance Drawable ScatterPlot where 52 | draw rSpec figVal _ redraw = 53 | draw' uiHelpers rSpec =<< selListener figVal redraw scatterPlotPoint 54 | where 55 | scatterPlotPoint :: ViewSelSetter PointIndex 56 | scatterPlotPoint { i } = scatterPoint i >>> scatterPlot 57 | 58 | instance Drawable2 ScatterPlot where 59 | createRootElement = createRootElement2 uiHelpers 60 | setSelStates = setSelStates2 scatterPlotHelpers uiHelpers 61 | 62 | instance Reflect (Dict (SelStates 𝕊 × Val (SelStates 𝕊))) ScatterPlot where 63 | from r = ScatterPlot 64 | { caption: unpack string (snd (get f_caption r)) 65 | , points: dict from <$> from (snd (get f_points r)) 66 | , labels: dict from (snd (get f_labels r)) 67 | } 68 | 69 | type PointIndex = { i :: Int } 70 | -------------------------------------------------------------------------------- /src/App/View/Util/Axes.purs: -------------------------------------------------------------------------------- 1 | module App.View.Util.Axes where 2 | 3 | import Prelude 4 | 5 | import Data.List (List(..)) 6 | import DataType (cDefault, cRotated) 7 | import Primitive (ToFrom, typeError) 8 | import Val (BaseVal(..)) 9 | 10 | data Orientation 11 | = Default 12 | | Rotated 13 | 14 | -- ====================== 15 | -- boilerplate 16 | -- ====================== 17 | 18 | derive instance Eq Orientation 19 | 20 | -- Hefty amount of boilerplate just for a type isomorphic to Bool :-o 21 | orientation :: forall a. ToFrom Orientation a 22 | orientation = 23 | { pack: case _ of 24 | Default -> Constr cDefault Nil 25 | Rotated -> Constr cRotated Nil 26 | , unpack: case _ of 27 | Constr c Nil 28 | | c == cDefault -> Default 29 | | c == cRotated -> Rotated 30 | v -> typeError v "Orientation" 31 | } 32 | -------------------------------------------------------------------------------- /src/App/View/Util/Point.purs: -------------------------------------------------------------------------------- 1 | module App.View.Util.Point where 2 | 3 | import App.Util (class Reflect, SelStates, Selectable, 𝕊, get_intOrNumber) 4 | import App.View.Util.Axes (Orientation, orientation) 5 | import App.View.Util.D3 (Coord) 6 | import Data.Newtype (class Newtype) 7 | import Data.Tuple (snd) 8 | import DataType (f_x, f_y) 9 | import Dict (Dict) 10 | import Primitive (string, unpack) 11 | import Util (type (×)) 12 | import Util.Map (get) 13 | import Val (Val) 14 | 15 | newtype Point a = Point (Coord (Selectable a)) 16 | 17 | -- ====================== 18 | -- boilerplate 19 | -- ====================== 20 | 21 | derive instance Newtype (Point a) _ 22 | 23 | instance Reflect (Dict (SelStates 𝕊 × Val (SelStates 𝕊))) (Point Number) where 24 | from r = Point 25 | { x: get_intOrNumber f_x r 26 | , y: get_intOrNumber f_y r 27 | } 28 | 29 | instance Reflect (Dict (SelStates 𝕊 × Val (SelStates 𝕊))) (Point String) where 30 | from r = Point 31 | { x: unpack string (snd (get f_x r)) 32 | , y: unpack string (snd (get f_y r)) 33 | } 34 | 35 | instance Reflect (Dict (SelStates 𝕊 × Val (SelStates 𝕊))) (Point Orientation) where 36 | from r = Point 37 | { x: unpack orientation (snd (get f_x r)) 38 | , y: unpack orientation (snd (get f_y r)) 39 | } 40 | -------------------------------------------------------------------------------- /src/Benchmark.purs: -------------------------------------------------------------------------------- 1 | module Benchmark where 2 | 3 | import Prelude 4 | 5 | import Data.Array (concat) 6 | import Data.Array.NonEmpty (fromArray) 7 | import Data.Either (Either(..)) 8 | import Data.Traversable (sequence) 9 | import Effect (Effect) 10 | import Effect.Aff (Error, runAff_) 11 | import Effect.Class.Console (log) 12 | import Module.Node (loadFile) 13 | import Node.Encoding (Encoding(..)) 14 | import Node.FS.Sync (writeTextFile) 15 | import Test.Benchmark.Util (BenchAcc(..)) 16 | import Test.Specs.Bwd (bwd_cases) 17 | import Test.Specs.Desugar (desugar_cases) 18 | import Test.Specs.Graphics (graphics_cases) 19 | import Test.Specs.Misc (misc_cases) 20 | import Test.Util.Suite (BenchSuite, bwdSuite, suite, withDatasetSuite) 21 | import Util (definitely, error, (×), type (+)) 22 | 23 | main :: Effect Unit 24 | main = runAff_ cb do 25 | outs <- sequence $ 26 | ( \(str × row) -> do 27 | log $ "Benchmarking: " <> str 28 | (str × _) <$> row 29 | ) 30 | <$> (concat (benchmarks <@> (10 × true))) 31 | pure $ BenchAcc $ definitely "More than one benchmark" $ fromArray outs 32 | 33 | cb :: Error + BenchAcc -> Effect Unit 34 | cb (Left err) = error $ show err 35 | cb (Right bacc) = do 36 | writeTextFile ASCII "benchmark/benchmarks_artifact.csv" $ show bacc 37 | log "Benchmarking data written to benchmark/benchmarks_artifact.csv" 38 | 39 | benchmarks :: Array BenchSuite 40 | benchmarks = 41 | [ suite loadFile desugar_cases 42 | , suite loadFile misc_cases 43 | , bwdSuite loadFile bwd_cases 44 | , withDatasetSuite loadFile graphics_cases 45 | ] -------------------------------------------------------------------------------- /src/Bind.purs: -------------------------------------------------------------------------------- 1 | module Bind where 2 | 3 | import Prelude 4 | import Data.List (List(..), (:)) 5 | import Data.Set (Set, empty) 6 | import Data.Tuple (Tuple(..), fst, snd) 7 | import Util (type (×), definitely, singleton, whenever) 8 | import Util.Set ((∪)) 9 | 10 | -- Not easy as a newtype as there is no Coercible instance for Set. 11 | type Var = String 12 | 13 | varAnon = "_" :: Var 14 | 15 | -- Discrete partial order for variables. 16 | mustGeq :: Var -> Var -> Var 17 | mustGeq x y = definitely "greater" (whenever (x == y) x) 18 | 19 | type Bind a = Var × a 20 | 21 | key :: forall a. Bind a -> Var 22 | key = fst 23 | 24 | val :: forall a. Bind a -> a 25 | val = snd 26 | 27 | keys :: forall a. List (Bind a) -> Set Var 28 | keys Nil = empty 29 | keys ((x ↦ _) : ρ) = singleton x ∪ keys ρ 30 | 31 | showBind :: forall a. Show a => Var -> a -> Bind String 32 | showBind x = show >>> (x ↦ _) 33 | 34 | infix 4 Tuple as ↦ 35 | infix 4 showBind as ⟼ 36 | infixl 4 mustGeq as ⪂ 37 | -------------------------------------------------------------------------------- /src/Desug.purs: -------------------------------------------------------------------------------- 1 | module Desug where 2 | 3 | import Prelude 4 | 5 | import Control.Monad.Error.Class (class MonadError) 6 | import Desugarable (desug, desugBwd) 7 | import Effect.Exception (Error) 8 | import Expr (Expr) 9 | import GaloisConnection (GaloisConnection(..)) 10 | import Lattice (class BoundedLattice, Raw) 11 | import SExpr (Expr) as S 12 | import Util (defined) 13 | 14 | -- Core-language slicing can produce "partial" slices, but these are not (yet) tolerated by desugaring. 15 | type Desugaring a = 16 | { gc :: GaloisConnection (S.Expr a) (Expr a) 17 | , e :: Raw Expr -- original (non-partial) desugared expression 18 | } 19 | 20 | desugGC 21 | :: forall a m 22 | . MonadError Error m 23 | => Eq a 24 | => BoundedLattice a 25 | => Raw S.Expr 26 | -> m (Desugaring a) 27 | desugGC s = pure $ { gc: GC { fwd, bwd }, e } 28 | where 29 | e = defined $ desug s 30 | fwd s' = defined $ desug s' 31 | bwd e' = desugBwd e' s 32 | -------------------------------------------------------------------------------- /src/Desugarable.purs: -------------------------------------------------------------------------------- 1 | module Desugarable where 2 | 3 | import Prelude 4 | 5 | import Control.Monad.Error.Class (class MonadError) 6 | import Effect.Exception (Error) 7 | import Lattice (class BoundedJoinSemilattice, class BoundedLattice, Raw) 8 | 9 | class (Functor s, Functor e) <= Desugarable s e | s -> e where 10 | desug :: forall a m. MonadError Error m => BoundedLattice a => s a -> m (e a) 11 | desugBwd :: forall a. BoundedJoinSemilattice a => e a -> Raw s -> s a 12 | -------------------------------------------------------------------------------- /src/Dict.purs: -------------------------------------------------------------------------------- 1 | -- Better name and more consistent interface to Foreign.Object, plus some additional functions. 2 | -- Newtype wrapper so we can fix Ord instance be consistent with Eq (i.e. to use isSubmap vs. toAscArray). 3 | module Dict 4 | ( fromFoldable 5 | , toArrayWithKey 6 | , unions 7 | , Dict(..) 8 | ) where 9 | 10 | import Prelude hiding (apply) 11 | 12 | import Data.FoldableWithIndex (class FoldableWithIndex, foldMapWithIndexDefaultL, foldlWithIndex, foldrWithIndexDefault) 13 | import Data.Newtype (class Newtype) 14 | import Data.Traversable (class Foldable, class Traversable, foldl) 15 | import Foreign.Object (Object, fromFoldable, toArrayWithKey, union) as O 16 | import Foreign.Object (delete, empty, filterKeys, insert, isEmpty, isSubmap, lookup, mapWithKey, unionWith) 17 | import Util (class IsEmpty, type (×)) 18 | import Util.Map (class Map, class MapF, intersectionWith, keys, maplet, toUnfoldable, values) 19 | import Util.Map as Map 20 | import Util.Set (class Set, difference, size, union, (∈)) 21 | 22 | -- Think we can generalise to keys of any type coercible to String. 23 | newtype Dict a = Dict (O.Object a) 24 | 25 | derive instance Newtype (Dict a) _ 26 | derive newtype instance Eq a => Eq (Dict a) 27 | 28 | -- More sensible than Foreign.Object Ord instance. 29 | instance Ord a => Ord (Dict a) where 30 | compare (Dict d) (Dict d') = 31 | if isSubmap d d' then 32 | if isSubmap d' d then EQ 33 | else LT 34 | else GT 35 | 36 | instance Apply Dict where 37 | apply (Dict f) (Dict x) = Dict (intersectionWith ($) f x) 38 | 39 | derive instance Functor Dict 40 | derive instance Foldable Dict 41 | derive instance Traversable Dict 42 | 43 | instance FoldableWithIndex String Dict where 44 | foldlWithIndex f z (Dict d) = foldlWithIndex f z d 45 | foldrWithIndex f = foldrWithIndexDefault f 46 | foldMapWithIndex f = foldMapWithIndexDefaultL f 47 | 48 | instance IsEmpty (Dict a) where 49 | isEmpty (Dict d) = isEmpty d 50 | 51 | instance Set (Dict a) String where 52 | empty = Dict empty 53 | filter p (Dict d) = Dict (filterKeys p d) 54 | size (Dict d) = size d 55 | member x (Dict d) = x ∈ d 56 | difference (Dict d) (Dict d') = Dict (difference d d') 57 | union (Dict d) (Dict d') = Dict (O.union d d') 58 | 59 | instance Map (Dict a) String a where 60 | maplet k v = Dict (maplet k v) 61 | keys (Dict d) = keys d 62 | values (Dict d) = values d 63 | filterKeys p (Dict d) = Dict (filterKeys p d) 64 | unionWith f (Dict d) (Dict d') = Dict (unionWith f d d') 65 | lookup k (Dict d) = lookup k d 66 | delete k (Dict d) = Dict (delete k d) 67 | insert k v (Dict d) = Dict (insert k v d) 68 | toUnfoldable (Dict d) = toUnfoldable d 69 | 70 | instance MapF Dict String where 71 | intersectionWith f (Dict d) (Dict d') = Dict (intersectionWith f d d') 72 | difference (Dict d) (Dict d') = Dict (Map.difference d d') 73 | mapWithKey f (Dict d) = Dict (mapWithKey f d) 74 | 75 | fromFoldable :: forall f a. Foldable f => f (String × a) -> Dict a 76 | fromFoldable = Dict <<< O.fromFoldable 77 | 78 | toArrayWithKey :: forall a b. (String -> a -> b) -> Dict a -> Array b 79 | toArrayWithKey f (Dict d) = O.toArrayWithKey f d 80 | 81 | unions :: forall f a. Foldable f => f (Dict a) -> Dict a 82 | unions = foldl union (Dict empty) 83 | -------------------------------------------------------------------------------- /src/GaloisConnection.purs: -------------------------------------------------------------------------------- 1 | module GaloisConnection where 2 | 3 | import Prelude hiding (join, top) 4 | 5 | import Data.Newtype (class Newtype) 6 | import Data.Profunctor.Strong ((***)) as Strong 7 | import Data.Tuple (fst, snd) as Tuple 8 | import Data.Tuple (uncurry) 9 | import Lattice (class BoundedMeetSemilattice, class JoinSemilattice, class Neg, neg, top, (∨)) 10 | import Util (Endo, type (×), (×), dup) 11 | 12 | newtype GaloisConnection a b = GC 13 | { fwd :: a -> b -- upper adjoint, meet-preserving 14 | , bwd :: b -> a -- lower adjoint, join-preserving 15 | } 16 | 17 | derive instance Newtype (GaloisConnection a b) _ 18 | 19 | deMorgan :: forall a b. Neg a => Neg b => Endo (a -> b) 20 | deMorgan = (neg >>> _) >>> (_ >>> neg) 21 | 22 | -- Could unify deMorgan and dual but would need to reify notion of opposite category. 23 | dual :: forall a b. Neg a => Neg b => GaloisConnection a b -> GaloisConnection b a 24 | dual (GC { fwd, bwd }) = GC { fwd: deMorgan bwd, bwd: deMorgan fwd } 25 | 26 | -- TODO: restate in terms of (&&&). 27 | relatedInputs 28 | :: forall a b 29 | . Neg a 30 | => Neg b 31 | => JoinSemilattice b 32 | => GaloisConnection a b 33 | -> GaloisConnection (a × b) a 34 | relatedInputs f = (f *** identity) >>> meet >>> dual f 35 | 36 | relatedOutputs 37 | :: forall a b 38 | . Neg a 39 | => JoinSemilattice a 40 | => Neg b 41 | => GaloisConnection a b 42 | -> GaloisConnection (b × a) b 43 | relatedOutputs f = (dual f *** identity) >>> meet >>> f 44 | 45 | instance Semigroupoid GaloisConnection where 46 | compose (GC { fwd: fwd1, bwd: bwd1 }) (GC { fwd: fwd2, bwd: bwd2 }) = 47 | GC { fwd: fwd1 <<< fwd2, bwd: bwd1 >>> bwd2 } 48 | 49 | instance Category GaloisConnection where 50 | identity = GC { fwd: identity, bwd: identity } 51 | 52 | -- Galois connections have products. Data.Profunctor requires pre-/post-composability with arbitrary functions 53 | -- (which may not have adjoints), similar to how Haskell's Control.Arrow requires an injection from arbitrary 54 | -- functions. So for now side-step Data.Profunctor[.Strong] and define products directly. 55 | splitStrong 56 | :: forall a b c d 57 | . GaloisConnection a b 58 | -> GaloisConnection c d 59 | -> GaloisConnection (a × c) (b × d) 60 | splitStrong (GC { fwd: fwd1, bwd: bwd1 }) (GC { fwd: fwd2, bwd: bwd2 }) = 61 | GC { fwd: fwd1 Strong.*** fwd2, bwd: bwd1 Strong.*** bwd2 } 62 | 63 | first :: forall a b c. GaloisConnection a b -> GaloisConnection (a × c) (b × c) 64 | first = (_ *** identity) 65 | 66 | second :: forall a b c. GaloisConnection a b -> GaloisConnection (c × a) (c × b) 67 | second = (identity *** _) 68 | 69 | fanout :: forall a b c. Neg a => JoinSemilattice a => GaloisConnection a b -> GaloisConnection a c -> GaloisConnection a (b × c) 70 | fanout f g = join >>> (f *** g) 71 | 72 | meet :: forall a. Neg a => JoinSemilattice a => GaloisConnection (a × a) a 73 | meet = dual join 74 | 75 | join :: forall a. JoinSemilattice a => GaloisConnection a (a × a) 76 | join = GC { fwd: dup, bwd: uncurry (∨) } 77 | 78 | unfst :: forall a b. BoundedMeetSemilattice b => GaloisConnection a (a × b) 79 | unfst = GC { fwd: \a -> a × top, bwd: Tuple.fst } 80 | 81 | unsnd :: forall a b. BoundedMeetSemilattice a => GaloisConnection b (a × b) 82 | unsnd = GC { fwd: \b -> top × b, bwd: Tuple.snd } 83 | 84 | infixr 3 splitStrong as *** 85 | infixr 3 fanout as &&& 86 | -------------------------------------------------------------------------------- /src/Graph/Slice.purs: -------------------------------------------------------------------------------- 1 | module Graph.Slice where 2 | 3 | import Prelude hiding (map) 4 | 5 | import Control.Monad.Rec.Class (Step(..), tailRecM) 6 | import Data.List (List(..), (:)) 7 | import Data.List as L 8 | import Data.Map (Map, lookup) 9 | import Data.Map as M 10 | import Data.Maybe (maybe) 11 | import Data.Set (Set, empty, insert) 12 | import Data.Set (map) as Set 13 | import Data.Tuple (fst) 14 | import Graph (class Graph, DVertex'(..), Edge, HyperEdge, Vertex, addresses, inEdges, inEdges', outN, sinks, vertexData) 15 | import Graph.WithGraph (WithGraph, extend, runWithGraph_spy) 16 | import Test.Util.Debug (checking) 17 | import Util (type (×), singleton, validateWhen, (×), (⊆)) 18 | import Util.Set ((∈)) 19 | 20 | type BwdConfig = 21 | { visited :: Set Vertex 22 | , αs :: List Vertex 23 | , pending :: List HyperEdge 24 | } 25 | 26 | bwdSlice :: forall g. Graph g => Set Vertex × g -> g 27 | bwdSlice (αs × g) = fst $ 28 | αs 29 | -- No outputsAreSources analog of inputAreSinks; we do however need to restrict to sources (see #818). 30 | # validateWhen checking.outputsInGraph "inputs are sinks" (_ ⊆ addresses g) 31 | -- # (\_ -> αs ∩ sources g) 32 | # \αs' -> runWithGraph_spy (tailRecM go { visited: empty, αs: L.fromFoldable αs', pending: Nil }) empty 33 | where 34 | 35 | go :: BwdConfig -> WithGraph (Step BwdConfig Unit) 36 | go { αs: Nil, pending: Nil } = pure $ Done unit 37 | go { visited, αs: Nil, pending: (DVertex (α × vd) × βs) : pending } = do 38 | if α ∈ visited then 39 | pure $ Loop { visited, αs: Nil, pending } 40 | else do 41 | extend (DVertex (α × vd)) βs 42 | pure $ Loop { visited: insert α visited, αs: Nil, pending } 43 | go { visited, αs: α : αs', pending } = do 44 | let βs = outN g α 45 | pure $ Loop { visited, αs: L.fromFoldable βs <> αs', pending: (DVertex (α × vertexData g α) × βs) : pending } 46 | 47 | type PendingVertices = Map Vertex (Set Vertex) 48 | type FwdConfig = 49 | { pending :: PendingVertices 50 | , es :: List Edge 51 | } 52 | 53 | fwdSlice :: forall g. Graph g => Set Vertex × g -> g 54 | fwdSlice (αs × g) = fst $ 55 | αds 56 | # validateWhen checking.inputsAreSinks "inputs are sinks" (\_ -> αs ⊆ sinks g) 57 | # runWithGraph_spy (tailRecM go { pending: M.empty, es: inEdges g αs }) 58 | where 59 | go :: FwdConfig -> WithGraph (Step FwdConfig Unit) 60 | go { es: Nil } = pure $ Done unit 61 | go { pending, es: (α × β) : es } = 62 | if βs == outN g α then do 63 | extend (DVertex (α × vertexData g α)) βs 64 | pure $ Loop { pending: M.delete α pending, es: inEdges' g α <> es } 65 | else 66 | pure $ Loop { pending: M.insert α βs pending, es } 67 | where 68 | βs = maybe (singleton β) (insert β) (lookup α pending) 69 | αds = Set.map (\α -> DVertex (α × vertexData g α)) αs 70 | -------------------------------------------------------------------------------- /src/Module/Node.purs: -------------------------------------------------------------------------------- 1 | module Module.Node 2 | ( loadFile 3 | , parseProgram 4 | , module_ 5 | , datasetAs 6 | , loadProgCxt 7 | , module F 8 | , module Module 9 | , prepConfig 10 | ) where 11 | 12 | import Prelude 13 | 14 | import Bind (Bind) 15 | import Control.Monad.Error.Class (try) 16 | import Control.Monad.Except (class MonadError) 17 | import Data.Either (either) 18 | import Data.Maybe (Maybe(..)) 19 | import Effect.Aff.Class (class MonadAff, liftAff) 20 | import Effect.Exception (Error) 21 | import Lattice (Raw) 22 | import Module (Config, initialConfig, parse, prependFolder) 23 | import Module (File(..), Folder(..), FileLoader) as F 24 | import Module (datasetAs, loadProgCxt, module_, parseProgram, prepConfig) as M 25 | import Node.Encoding (Encoding(..)) 26 | import Node.FS.Aff (readTextFile, stat) 27 | import Node.FS.Stats (isFile) 28 | import ProgCxt (ProgCxt) 29 | import SExpr (Expr) as S 30 | import Util (AffError, error, findM) 31 | 32 | loadFile :: forall m. F.FileLoader m 33 | loadFile folders (F.File file) = do 34 | let urls = flip prependFolder (F.File $ file <> ".fld") <$> folders 35 | url <- findM urls exists Nothing 36 | case url of 37 | Nothing -> error $ "File " <> file <> " not found." 38 | Just name -> liftAff $ readTextFile ASCII name 39 | where 40 | exists :: F.File -> m (Maybe String) 41 | exists (F.File url) = do 42 | stats <- liftAff $ try (stat url) 43 | pure $ if (either (const false) isFile stats) then Just url else Nothing 44 | 45 | parseProgram ∷ ∀ m. Array F.Folder -> F.File → AffError m (Raw S.Expr) 46 | parseProgram = M.parseProgram loadFile 47 | 48 | module_ :: forall m. MonadAff m => MonadError Error m => Array F.Folder -> F.File -> Raw ProgCxt -> m (Raw ProgCxt) 49 | module_ = M.module_ loadFile 50 | 51 | datasetAs :: forall m. MonadAff m => MonadError Error m => Array F.Folder -> Bind F.File -> Raw ProgCxt -> m (Raw ProgCxt) 52 | datasetAs = M.datasetAs loadFile 53 | 54 | loadProgCxt :: forall m. MonadAff m => MonadError Error m => Array F.Folder -> Array String -> Array (Bind String) -> m (Raw ProgCxt) 55 | loadProgCxt fluidSrcPaths = M.loadProgCxt { loadFile, fluidSrcPaths } 56 | 57 | prepConfig :: forall m. MonadAff m => MonadError Error m => Array F.Folder -> F.File -> ProgCxt Unit -> m Config 58 | prepConfig fluidSrcPaths = M.prepConfig { loadFile, fluidSrcPaths } 59 | -------------------------------------------------------------------------------- /src/Module/Web.purs: -------------------------------------------------------------------------------- 1 | module Module.Web 2 | ( loadFile 3 | , loadFile' 4 | , parseProgram 5 | , module_ 6 | , datasetAs 7 | , loadProgCxt 8 | , module F 9 | , module Module 10 | , prepConfig 11 | ) where 12 | 13 | import Prelude 14 | 15 | import Affjax (Error(..)) as A 16 | import Affjax (Response) 17 | import Affjax.ResponseFormat (string) 18 | import Affjax.StatusCode (StatusCode(..)) 19 | import Affjax.Web (defaultRequest, printError, request) 20 | import Bind (Bind) 21 | import Control.Monad.Error.Class (throwError) 22 | import Control.Monad.Except (class MonadError, ExceptT(..), runExceptT) 23 | import Data.Either (Either(..), either) 24 | import Data.HTTP.Method (Method(..)) 25 | import Effect.Aff (Aff) 26 | import Effect.Aff.Class (class MonadAff, liftAff) 27 | import Effect.Class.Console (log) 28 | import Effect.Exception (Error) 29 | import Effect.Exception (error) as E 30 | import Lattice (Raw) 31 | import Module (Config, initialConfig, parse, prependFolder) 32 | import Module (FileLoader, Folder(..), File(..)) as F 33 | import Module (datasetAs, loadProgCxt, module_, parseProgram, prepConfig) as M 34 | import ProgCxt (ProgCxt) 35 | import SExpr (Expr) as S 36 | import Util (type (×), (×), AffError, debug, findM) 37 | 38 | loadFile :: forall m. F.FileLoader m 39 | loadFile folders (F.File file) = do 40 | let urls = flip prependFolder (F.File $ file <> ".fld") <$> folders 41 | result <- runExceptT $ do 42 | (_ × (url')) <- ExceptT $ liftAff $ findM urls checkUrl (Left A.RequestFailedError) 43 | when debug.logging $ liftAff $ log ("loadFile: resolved URL: " <> url') 44 | contents <- ExceptT $ liftAff $ request (defaultRequest { url = url', method = Left GET, responseFormat = string }) 45 | pure contents.body 46 | either (throwError <<< E.error <<< printError) pure result 47 | where 48 | checkUrl :: F.File -> Aff (Either A.Error (Response String × String)) 49 | checkUrl (F.File url) = do 50 | resp <- request (defaultRequest { url = url, method = Left HEAD, responseFormat = string }) 51 | pure case resp of 52 | Right resp' | resp'.status == StatusCode 200 -> Right (resp' × url) 53 | Right _ -> Left A.RequestFailedError 54 | Left err -> Left err 55 | 56 | loadFile' :: forall m. Array F.Folder -> F.File -> AffError m (F.File × String) 57 | loadFile' folders file = (file × _) <$> loadFile folders file 58 | 59 | parseProgram :: forall m. Array F.Folder -> F.File -> AffError m (Raw S.Expr) 60 | parseProgram = M.parseProgram loadFile 61 | 62 | module_ :: forall m. MonadAff m => MonadError Error m => Array F.Folder -> F.File -> Raw ProgCxt -> m (Raw ProgCxt) 63 | module_ = M.module_ loadFile 64 | 65 | datasetAs :: forall m. MonadAff m => MonadError Error m => Array F.Folder -> Bind F.File -> Raw ProgCxt -> m (Raw ProgCxt) 66 | datasetAs = M.datasetAs loadFile 67 | 68 | loadProgCxt :: forall m. MonadAff m => MonadError Error m => Array F.Folder -> Array String -> Array (Bind String) -> m (Raw ProgCxt) 69 | loadProgCxt fluidSrcPaths = M.loadProgCxt { loadFile, fluidSrcPaths } 70 | 71 | prepConfig :: forall m. MonadAff m => MonadError Error m => Array F.Folder -> F.File -> ProgCxt Unit -> m Config 72 | prepConfig fluidSrcPaths = M.prepConfig { loadFile, fluidSrcPaths } 73 | -------------------------------------------------------------------------------- /src/Parse/Constants.purs: -------------------------------------------------------------------------------- 1 | module Parse.Constants where 2 | 3 | -- Constants (should also be used by prettyprinter). Haven't found a way to avoid the type definition. 4 | str 5 | :: { arrayLBracket :: String 6 | , arrayRBracket :: String 7 | , as :: String 8 | , backslash :: String 9 | , backtick :: String 10 | , bar :: String 11 | , colon :: String 12 | , colonEq :: String 13 | , comma :: String 14 | , curlylBrace :: String 15 | , curlyrBrace :: String 16 | , dictLBracket :: String 17 | , dictRBracket :: String 18 | , dot :: String 19 | , ellipsis :: String 20 | , else_ :: String 21 | , equals :: String 22 | , fun :: String 23 | , if_ :: String 24 | , in_ :: String 25 | , lArrow :: String 26 | , lBracket :: String 27 | , let_ :: String 28 | , lparenth :: String 29 | , match :: String 30 | , rArrow :: String 31 | , rBracket :: String 32 | , rparenth :: String 33 | , semiColon :: String 34 | , then_ :: String 35 | , backquote :: String 36 | , triplequote :: String 37 | , dollar :: String 38 | } 39 | 40 | str = 41 | { arrayLBracket: "[|" 42 | , arrayRBracket: "|]" 43 | , as: "as" 44 | , backslash: "\\" 45 | , backtick: "`" 46 | , bar: "|" 47 | , colon: ":" 48 | , colonEq: ":=" 49 | , comma: "," 50 | , curlylBrace: "{" 51 | , curlyrBrace: "}" 52 | , dictLBracket: "{|" 53 | , dictRBracket: "|}" 54 | , dot: "." 55 | , ellipsis: ".." 56 | , else_: "else" 57 | , equals: "=" 58 | , fun: "fun" 59 | , if_: "if" 60 | , in_: "in" 61 | , lArrow: "<-" 62 | , lBracket: "[" 63 | , let_: "let" 64 | , lparenth: "(" 65 | , match: "match" 66 | , rArrow: "->" 67 | , rBracket: "]" 68 | , rparenth: ")" 69 | , semiColon: ";" 70 | , then_: "then" 71 | , backquote: "@" 72 | , triplequote: "\"\"\"" 73 | , dollar: "$" 74 | } 75 | -------------------------------------------------------------------------------- /src/Primitive/Parse.purs: -------------------------------------------------------------------------------- 1 | module Primitive.Parse where 2 | 3 | import Data.Map (Map, fromFoldable) 4 | import Parsing.Expr (Assoc(..)) 5 | import Bind (Var) 6 | import Util (type (×), (×)) 7 | 8 | -- name in user land, precedence 0 from 9 (similar to Haskell 98), associativity 9 | type OpDef = 10 | { op :: Var 11 | , prec :: Int 12 | , assoc :: Assoc 13 | } 14 | 15 | opDef :: Var -> Int -> Assoc -> Var × OpDef 16 | opDef op prec assoc = op × { op, prec, assoc } 17 | 18 | -- Syntactic information only. No requirement that any of these be defined. 19 | opDefs :: Map String OpDef 20 | opDefs = fromFoldable 21 | [ opDef "." 8 AssocLeft 22 | , opDef "!" 8 AssocLeft 23 | , opDef "**" 8 AssocRight 24 | , opDef "*" 7 AssocLeft 25 | , opDef "/" 7 AssocLeft 26 | , opDef "+" 6 AssocLeft 27 | , opDef "-" 6 AssocLeft 28 | , opDef ":" 6 AssocRight 29 | , opDef "++" 5 AssocRight 30 | , opDef "==" 4 AssocNone 31 | , opDef "/=" 4 AssocNone 32 | , opDef "<" 4 AssocLeft 33 | , opDef ">" 4 AssocLeft 34 | , opDef "<=" 4 AssocLeft 35 | , opDef ">=" 4 AssocLeft 36 | ] 37 | 38 | -------------------------------------------------------------------------------- /src/ProgCxt.purs: -------------------------------------------------------------------------------- 1 | module ProgCxt where 2 | 3 | import Prelude 4 | 5 | import Bind (Bind) 6 | import Data.List (List, zipWith) 7 | import Data.Newtype (class Newtype) 8 | import Data.Profunctor.Strong (second) 9 | import Data.Set (unions) 10 | import Data.Traversable (class Foldable, class Traversable) 11 | import Data.Tuple (snd) 12 | import Expr (Expr, Module) 13 | import Graph (class Vertices, Vertex, vertices) 14 | import Util.Set ((∪)) 15 | import Val (Env) 16 | 17 | -- Module context (plus datasets, reflecting current ad hoc approach to those). 18 | newtype ProgCxt a = ProgCxt 19 | { primitives :: Env a 20 | , mods :: List (Module a) -- in reverse order 21 | , datasets :: List (Bind (Expr a)) 22 | } 23 | 24 | instance Vertices (ProgCxt Vertex) where 25 | vertices (ProgCxt { primitives, mods, datasets }) = 26 | vertices primitives 27 | ∪ unions (vertices <$> mods) 28 | ∪ unions ((vertices <<< snd) <$> datasets) 29 | 30 | -- ====================== 31 | -- boilerplate 32 | -- ====================== 33 | derive instance Newtype (ProgCxt a) _ 34 | derive instance Functor ProgCxt 35 | derive instance Traversable ProgCxt 36 | derive instance Foldable ProgCxt 37 | 38 | instance Apply ProgCxt where 39 | apply (ProgCxt fζ) (ProgCxt ζ) = 40 | ProgCxt 41 | { primitives: fζ.primitives <*> ζ.primitives 42 | , mods: fζ.mods `zipWith (<*>)` ζ.mods 43 | , datasets: (second (<*>) <$> fζ.datasets) `zipWith (<*>)` ζ.datasets 44 | } 45 | -------------------------------------------------------------------------------- /src/README.md: -------------------------------------------------------------------------------- 1 | # Coding conventions 2 | 3 | ## Imports 4 | 5 | - except for `Prelude`, imported elements are always named explicitly 6 | - modules listed in the following order: `Prelude`; standard PureScript libraries (alphabetical); our modules (alphabetical) 7 | -------------------------------------------------------------------------------- /src/Util/Map.js: -------------------------------------------------------------------------------- 1 | export function intersectionWith_Object (f) { 2 | return function (m1) { 3 | return function (m2) { 4 | var m = {} 5 | for (var k in m1) { 6 | if (hasOwnProperty.call(m1, k) && hasOwnProperty.call(m2, k)) { 7 | m[k] = f(m1[k])(m2[k]) 8 | } 9 | } 10 | return m 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Util/Pair.purs: -------------------------------------------------------------------------------- 1 | module Util.Pair where 2 | 3 | import Prelude 4 | 5 | import Data.Foldable (class Foldable, foldrDefault, foldMapDefaultL) 6 | import Data.List (List) 7 | import Data.List (unzip, zip) as L 8 | import Data.Traversable (class Traversable, sequenceDefault) 9 | import Util (type (×), (×)) 10 | 11 | -- a |-> a × a can't derive a functor instance, so use this 12 | data Pair a = Pair a a 13 | 14 | instance Show a => Show (Pair a) where 15 | show (Pair x y) = "(Pair " <> show x <> " " <> show y <> ")" 16 | 17 | toTuple :: forall a. Pair a -> a × a 18 | toTuple (Pair x y) = x × y 19 | 20 | fromTuple :: forall a. a × a -> Pair a 21 | fromTuple (x × y) = Pair x y 22 | 23 | zip :: forall a. List a -> List a -> List (Pair a) 24 | zip xs ys = L.zip xs ys <#> fromTuple 25 | 26 | unzip :: forall a. List (Pair a) -> List a × List a 27 | unzip xys = xys <#> toTuple # L.unzip 28 | 29 | -- ====================== 30 | -- boilerplate 31 | -- ====================== 32 | derive instance Eq a => Eq (Pair a) 33 | derive instance Ord a => Ord (Pair a) 34 | 35 | instance Functor Pair where 36 | map f (Pair x y) = Pair (f x) (f y) 37 | 38 | instance Apply Pair where 39 | apply (Pair f g) (Pair x y) = Pair (f x) (g y) 40 | 41 | instance Applicative Pair where 42 | pure x = Pair x x 43 | 44 | instance Foldable Pair where 45 | foldl f z (Pair x y) = f (f z x) y 46 | foldr f = foldrDefault f 47 | foldMap f = foldMapDefaultL f 48 | 49 | instance Traversable Pair where 50 | traverse f (Pair x y) = Pair <$> f x <*> f y 51 | sequence = sequenceDefault 52 | -------------------------------------------------------------------------------- /src/Util/Parse.purs: -------------------------------------------------------------------------------- 1 | module Util.Parse where 2 | 3 | import Prelude hiding (absurd) 4 | 5 | import Control.Alt ((<|>)) 6 | import Data.List (List(..)) 7 | import Data.List (many, some) as L 8 | import Data.List.NonEmpty (NonEmptyList, cons', toList) 9 | import Parsing (Parser) 10 | import Parsing.Combinators (try) 11 | import Util (nonEmpty) 12 | 13 | type SParser = Parser String 14 | 15 | -- helpers (could generalise further) 16 | sepBy_try :: forall a sep. SParser a -> SParser sep -> SParser (List a) 17 | sepBy_try p sep = (sepBy1_try p sep <#> toList) <|> pure Nil 18 | 19 | sepBy1_try :: forall a sep. SParser a -> SParser sep -> SParser (NonEmptyList a) 20 | sepBy1_try p sep = cons' <$> p <*> L.many (try $ sep *> p) 21 | 22 | some :: forall a. SParser a → SParser (NonEmptyList a) 23 | some p = nonEmpty <$> L.some p 24 | -------------------------------------------------------------------------------- /src/Util/Set.purs: -------------------------------------------------------------------------------- 1 | module Util.Set where 2 | 3 | import Prelude hiding (append) 4 | 5 | import Data.Foldable (class Foldable, foldl) 6 | import Data.Set (Set) 7 | import Data.Set as Set 8 | import Foreign.Object (Object) 9 | import Foreign.Object as Object 10 | import Util (class IsEmpty, Endo) 11 | 12 | -- Generalises Set but also supports a fixed element type. Doesn't support transforming element type. 13 | class IsEmpty a <= Set a b | a -> b where 14 | empty :: a 15 | filter :: (b -> Boolean) -> Endo a 16 | size :: a -> Int 17 | difference :: a -> Endo a 18 | member :: b -> a -> Boolean 19 | union :: a -> Endo a 20 | 21 | infix 5 difference as \\ 22 | infix 5 member as ∈ 23 | infixr 6 union as ∪ 24 | 25 | instance Ord a => Set (Set a) a where 26 | empty = Set.empty 27 | filter = Set.filter 28 | size = Set.size 29 | difference = Set.difference 30 | member = Set.member 31 | union = Set.union 32 | 33 | instance Set (Object a) String where 34 | empty = Object.empty 35 | filter = Object.filterKeys 36 | size = Object.size 37 | difference x y = foldl (flip Object.delete) x (Object.keys y) 38 | member = Object.member 39 | union = Object.union 40 | 41 | unions ∷ ∀ f a b. Foldable f ⇒ Set a b ⇒ f a → a 42 | unions = foldl union empty 43 | -------------------------------------------------------------------------------- /test/Benchmark/Util.js: -------------------------------------------------------------------------------- 1 | export function microtime () { 2 | return performance.now() 3 | } 4 | -------------------------------------------------------------------------------- /test/Specs/Comments.purs: -------------------------------------------------------------------------------- 1 | module Test.Specs.Comments where 2 | 3 | import Test.Util.Suite (TestSpec) 4 | 5 | comments_cases :: Array TestSpec 6 | comments_cases = 7 | [ { file: "comments/nested-constr" 8 | , imports: [] 9 | , fwd_expect: "False" 10 | } 11 | , { file: "comments/dicts" 12 | , imports: [] 13 | , fwd_expect: 14 | "{[\"d\"] : {}, [\"e\"] : {[\"a\"] : 5, [\"ab\"] : 6}, [\"e_ab\"] : 6, [\"f\"] : {[\"a\"] : 6, [\"ab\"] : 7}, [\"g\"] : {[\"a\"] : 5}}" 15 | } 16 | , { file: "comments/map", imports: [], fwd_expect: "(5 : (7 : (13 : (15 : (4 : (3 : (-3 : [])))))))" } 17 | , { file: "comments/list-comp", imports: [], fwd_expect: "(14 : (12 : (10 : (13 : (11 : (9 : (12 : (10 : (8 : [])))))))))" } 18 | , { file: "comments/app" 19 | , imports: [] 20 | , fwd_expect: "2" 21 | } 22 | ] 23 | -------------------------------------------------------------------------------- /test/Specs/Desugar.purs: -------------------------------------------------------------------------------- 1 | module Test.Specs.Desugar where 2 | 3 | import Test.Util.Suite (TestSpec) 4 | 5 | desugar_cases :: Array TestSpec 6 | desugar_cases = 7 | [ { file: "desugar/list-comp-1" 8 | , imports: [] 9 | , fwd_expect: "(14 : (12 : (10 : (13 : (11 : (9 : (12 : (10 : (8 : [])))))))))" 10 | } 11 | , { file: "desugar/list-comp-2" 12 | , imports: [] 13 | , fwd_expect: 14 | "(14 : (14 : (14 : (12 : (12 : (12 : (10 : (10 : (10 : (13 : (13 : (13 : (11 : (11 : (11 : (9 : \ 15 | \(9 : (9 : (12 : (12 : (12 : (10 : (10 : (10 : (8 : (8 : (8 : [])))))))))))))))))))))))))))" 16 | } 17 | , { file: "desugar/list-comp-3", imports: [], fwd_expect: "(9 : (8 : []))" } 18 | , { file: "desugar/list-comp-4", imports: [], fwd_expect: "(5 : (4 : (3 : [])))" } 19 | , { file: "desugar/list-comp-5", imports: [], fwd_expect: "(5 : (4 : (3 : [])))" } 20 | , { file: "desugar/list-comp-6", imports: [], fwd_expect: "(5 : [])" } 21 | , { file: "desugar/list-comp-7", imports: [], fwd_expect: "([] : [])" } 22 | , { file: "desugar/list-comp-8", imports: [], fwd_expect: "(5 : (4 : (3 : [])))" } 23 | , { file: "desugar/list-comp-9", imports: [], fwd_expect: "(10 : (19 : []))" } 24 | , { file: "desugar/list-comp-10", imports: [], fwd_expect: "[]" } 25 | , { file: "desugar/list-enum", imports: [], fwd_expect: "(3 : (4 : (5 : (6 : (7 : [])))))" } 26 | ] 27 | -------------------------------------------------------------------------------- /test/Specs/Graphics.purs: -------------------------------------------------------------------------------- 1 | module Test.Specs.Graphics where 2 | 3 | import Bind ((↦)) 4 | 5 | import Test.Util.Suite (TestWithDatasetSpec) 6 | 7 | graphics_cases :: Array TestWithDatasetSpec 8 | graphics_cases = 9 | [ { imports: [ "lib/graphics" ] 10 | , dataset: "data" ↦ "dataset/renewables-restricted" 11 | , file: "graphics/background" 12 | } 13 | , { imports: [ "lib/graphics" ] 14 | , dataset: "data" ↦ "dataset/renewables-restricted" 15 | , file: "graphics/grouped-bar-chart" 16 | } 17 | , { imports: [ "lib/graphics" ] 18 | , dataset: "data" ↦ "dataset/renewables-restricted" 19 | , file: "graphics/line-chart" 20 | } 21 | , { imports: [ "lib/graphics" ] 22 | , dataset: "data" ↦ "dataset/renewables-restricted" 23 | , file: "graphics/stacked-bar-chart" 24 | } 25 | ] 26 | -------------------------------------------------------------------------------- /test/Specs/Misc.purs: -------------------------------------------------------------------------------- 1 | module Test.Specs.Misc where 2 | 3 | import Test.Util.Suite (TestSpec) 4 | 5 | misc_cases :: Array TestSpec 6 | misc_cases = 7 | [ { file: "arithmetic", imports: [], fwd_expect: "42" } 8 | , { file: "array", imports: [], fwd_expect: "(1, (3, 3))" } 9 | , { file: "compose", imports: [], fwd_expect: "5" } 10 | , { file: "dicts" 11 | , imports: [] 12 | , fwd_expect: 13 | "{[\"d\"] : {}, [\"e\"] : {[\"a\"] : 5, [\"ab\"] : 6}, [\"e_ab\"] : 6, [\"f\"] : {[\"a\"] : 6, [\"ab\"] : 7}, [\"g\"] : {[\"a\"] : 5}}" 14 | } 15 | , { file: "div-mod-quot-rem" 16 | , imports: [] 17 | , fwd_expect: 18 | "((1 : (-1 : (-2 : (2 : [])))) : \ 19 | \((2 : (2 : (1 : (1 : [])))) : \ 20 | \((1 : (-1 : (-1 : (1 : [])))) : \ 21 | \((2 : (2 : (-2 : (-2 : [])))) : []))))" 22 | } 23 | , { file: "factorial", imports: [], fwd_expect: "40320" } 24 | , { file: "filter", imports: [], fwd_expect: "(8 : (7 : []))" } 25 | , { file: "first-class-constr", imports: [], fwd_expect: "((10 : []) : ((12 : []) : ((20 : []) : [])))" } 26 | , { file: "flatten" 27 | , imports: [] 28 | , fwd_expect: "((3, \"simon\") : ((4, \"john\") : ((6, \"sarah\") : ((7, \"claire\") : []))))" 29 | } 30 | , { file: "foldr-sumSquares", imports: [], fwd_expect: "661" } 31 | , { file: "include-input-into-output" 32 | , imports: [ "lib/some-constants" ] 33 | , fwd_expect: "(1, 1)" 34 | } 35 | , { file: "lexicalScoping", imports: [], fwd_expect: "\"6\"" } 36 | , { file: "length", imports: [], fwd_expect: "2" } 37 | , { file: "lookup", imports: [], fwd_expect: "Some \"sarah\"" } 38 | , { file: "map", imports: [], fwd_expect: "(5 : (7 : (13 : (15 : (4 : (3 : (-3 : [])))))))" } 39 | , { file: "mergeSort", imports: [], fwd_expect: "(1 : (2 : (3 : [])))" } 40 | , { file: "normalise", imports: [], fwd_expect: "(33, 66)" } 41 | , { file: "nub", imports: [], fwd_expect: "(1 : (2 : (3 : (4 : []))))" } 42 | , { file: "pattern-match", imports: [], fwd_expect: "4" } 43 | , { file: "range", imports: [], fwd_expect: "((0, 0) : ((0, 1) : ((1, 0) : ((1, 1) : []))))" } 44 | , { file: "records", imports: [], fwd_expect: "{[\"a\"] : 2, [\"b\"] : 6, [\"c\"] : 7, [\"d\"] : (5 : []), [\"e\"] : 7}" } 45 | , { file: "record-lookup", imports: [], fwd_expect: "True" } 46 | , { file: "reverse", imports: [], fwd_expect: "(2 : (1 : []))" } 47 | ] 48 | -------------------------------------------------------------------------------- /test/Test.purs: -------------------------------------------------------------------------------- 1 | module Test.Test where 2 | 3 | import Prelude hiding (add) 4 | 5 | import Data.Array (concat) 6 | import Data.Profunctor.Strong (second) 7 | import Effect (Effect) 8 | import Module.Web (loadFile) 9 | import Test.Specs.Bwd (bwd_cases) 10 | import Test.Specs.Comments (comments_cases) 11 | import Test.Specs.Desugar (desugar_cases) 12 | import Test.Specs.Graphics (graphics_cases) 13 | import Test.Specs.LinkedInputs (linkedInputs_cases) 14 | import Test.Specs.LinkedOutputs (linkedOutputs_cases) 15 | import Test.Specs.Misc (misc_cases) 16 | import Test.Util (TestSuite) 17 | import Test.Util.Mocha (run) 18 | import Test.Util.Suite (BenchSuite, bwdSuite, linkedInputsSuite, linkedOutputsSuite, suite, withDatasetSuite) 19 | import Util ((×)) 20 | 21 | main :: Effect Unit 22 | main = run tests 23 | 24 | -- main = run $ asTestSuite (suite loadFile comments_cases) 25 | 26 | -- main = run scratchpad 27 | 28 | scratchpad :: TestSuite 29 | scratchpad = asTestSuite $ suite loadFile 30 | [ { file: "comments/map" 31 | , imports: [] 32 | , fwd_expect: "(5 : (7 : (13 : (15 : (4 : (3 : (-3 : [])))))))" 33 | } 34 | ] 35 | 36 | asTestSuite :: BenchSuite -> TestSuite 37 | asTestSuite suite = second void <$> suite (1 × false) 38 | 39 | tests :: TestSuite 40 | tests = concat (benchmarks <#> asTestSuite) 41 | <> linkedOutputsSuite linkedOutputs_cases 42 | <> linkedInputsSuite linkedInputs_cases 43 | 44 | benchmarks :: Array BenchSuite 45 | benchmarks = 46 | [ suite loadFile desugar_cases 47 | , suite loadFile misc_cases 48 | , suite loadFile comments_cases 49 | , bwdSuite loadFile bwd_cases 50 | , withDatasetSuite loadFile graphics_cases 51 | ] 52 | -------------------------------------------------------------------------------- /test/Util/Debug.purs: -------------------------------------------------------------------------------- 1 | module Test.Util.Debug where 2 | 3 | -- These flags considered only when Util.debug.tracing is true. 4 | tracing 5 | :: { runWithGraphT :: Boolean 6 | , graphBwdSlice :: Boolean 7 | , graphBwdSlice_vertexData :: Boolean 8 | , graphFwdSlice :: Boolean 9 | , checkEq :: Boolean 10 | , bwdSelection :: Boolean 11 | , fwdAfterBwd :: Boolean 12 | , mediatingData :: Boolean 13 | , mouseEvent :: Boolean 14 | , intermediates :: Boolean 15 | } 16 | 17 | tracing = 18 | { runWithGraphT: false 19 | , graphBwdSlice: false 20 | , graphBwdSlice_vertexData: false 21 | , graphFwdSlice: false 22 | , checkEq: false 23 | , bwdSelection: false 24 | , fwdAfterBwd: false 25 | , mediatingData: false 26 | , mouseEvent: false 27 | , intermediates: true 28 | } 29 | 30 | -- Invariants that are potentially expensive to check and that we might want to disable in production, 31 | -- that are not covered explicitly by tests. 32 | checking 33 | :: { edgeListGC :: Boolean 34 | , edgeListSorted :: Boolean 35 | , inputsAreSinks :: Boolean 36 | , outputsInGraph :: Boolean 37 | , allocRoundTrip :: Boolean 38 | } 39 | 40 | checking = 41 | { edgeListGC: true 42 | , edgeListSorted: true 43 | , inputsAreSinks: true 44 | , outputsInGraph: true 45 | , allocRoundTrip: false 46 | } 47 | 48 | -- Should be set to true except when there are specific outstanding problems. 49 | testing 50 | :: { fwdPreservesTop :: Boolean 51 | , bwdDuals :: Boolean 52 | , fwdDuals :: Boolean 53 | , naiveFwd :: Boolean 54 | } 55 | 56 | testing = 57 | { fwdPreservesTop: true 58 | , bwdDuals: true 59 | , fwdDuals: true 60 | , naiveFwd: true 61 | } 62 | 63 | timing 64 | :: { selectionResult :: Boolean 65 | } 66 | 67 | timing = 68 | { selectionResult: false 69 | } 70 | -------------------------------------------------------------------------------- /test/Util/Mocha.js: -------------------------------------------------------------------------------- 1 | /* global exports, it, describe */ 2 | 3 | // module Test.Spec.Mocha2 4 | 5 | if (typeof describe !== 'function' || typeof it !== 'function') { 6 | throw new Error('Mocha globals seem to be unavailable!'); 7 | } 8 | 9 | /* 10 | only :: Boolean 11 | name :: String 12 | run :: (Effect Unit -> (Error -> Effect Unit) -> Effect Unit) 13 | done 14 | 15 | */ 16 | export function itAsync(only) { 17 | "use strict"; 18 | return function (name) { 19 | return function (run) { 20 | return function () { 21 | var f = only ? it.only : it; 22 | f(name, function (done) { // f :: String -> 23 | return run(function () { 24 | done(); 25 | return function () {}; 26 | })(function (err) { 27 | done(err); 28 | return function () {}; 29 | })(); 30 | }); 31 | }; 32 | }; 33 | }; 34 | } 35 | 36 | export function itPending(name) { 37 | "use strict"; 38 | return function () { 39 | it(name); 40 | }; 41 | } 42 | 43 | export function describe(only) { 44 | "use strict"; 45 | return function (name) { 46 | return function (nested) { 47 | return function () { 48 | var f = only ? describe.only : describe; 49 | f(name, function () { 50 | nested(); 51 | }); 52 | }; 53 | }; 54 | }; 55 | } 56 | -------------------------------------------------------------------------------- /test/Util/Mocha.purs: -------------------------------------------------------------------------------- 1 | module Test.Util.Mocha 2 | ( run 3 | , MOCHA 4 | ) where 5 | 6 | import Prelude 7 | import Data.Either (either) 8 | import Data.Traversable (traverse_) 9 | import Effect (Effect) 10 | import Effect.Aff (Aff, Error, runAff_) 11 | import Util ((×), type (×)) 12 | 13 | foreign import data MOCHA :: Type 14 | 15 | foreign import itAsync 16 | :: Boolean 17 | -> String 18 | -> ( Effect Unit 19 | -> (Error -> Effect Unit) 20 | -> Effect Unit 21 | ) 22 | -> Effect Unit 23 | 24 | foreign import itPending :: String -> Effect Unit 25 | foreign import describe :: Boolean -> String -> Effect Unit -> Effect Unit 26 | 27 | executeTest :: forall a. (String × Aff a) -> Effect Unit 28 | executeTest (name × example) = itAsync true name cb 29 | where 30 | cb :: Effect Unit -> (Error -> Effect Unit) -> Effect Unit 31 | cb onSuccess onError = 32 | runAff_ (either onError (const onSuccess)) example 33 | 34 | run :: forall a. (Array (String × Aff a)) -> Effect Unit 35 | run = traverse_ executeTest 36 | -------------------------------------------------------------------------------- /test/Util/Puppeteer.js: -------------------------------------------------------------------------------- 1 | import puppeteer from "puppeteer"; 2 | 3 | export function _launch(options) { 4 | return function() { 5 | return puppeteer.launch(options); 6 | }; 7 | } 8 | -------------------------------------------------------------------------------- /test/fluid/arithmetic.fld: -------------------------------------------------------------------------------- 1 | (1 + 5) * ((let x = 2; y = 8 in x * y) - (let y = 3 in y * y)) 2 | -------------------------------------------------------------------------------- /test/fluid/array.fld: -------------------------------------------------------------------------------- 1 | let xs = [| i * j | (i, j) in (3, 3) |] 2 | in (xs ! (1, 1), dims xs) 3 | -------------------------------------------------------------------------------- /test/fluid/comments/app.fld: -------------------------------------------------------------------------------- 1 | """This function measures the length of a list""" length [1, 2] 2 | -------------------------------------------------------------------------------- /test/fluid/comments/dicts.fld: -------------------------------------------------------------------------------- 1 | let d = {}; 2 | e = { ["a"]: 5, ["a" ++ "b"] : 6 }; 3 | f = { ["ab"]: 12 } 4 | in """We can have a docComment before a dict!""" { 5 | d: d, 6 | e: e, 7 | e_ab: e.ab, 8 | f: dict_map ((+) 1) e, 9 | g: dict_difference e f 10 | } 11 | -------------------------------------------------------------------------------- /test/fluid/comments/list-comp.fld: -------------------------------------------------------------------------------- 1 | """We can add comments to list comprehensions""" [ x + y | x <- [5, 4, 3], y <- [9, 7, 5] ] 2 | -------------------------------------------------------------------------------- /test/fluid/comments/map.fld: -------------------------------------------------------------------------------- 1 | map ((+) 1) """Takes each ${x} to ${x + 1}""" [4, 6, 12, 14, 3, 2, -4] 2 | -------------------------------------------------------------------------------- /test/fluid/comments/nested-constr.fld: -------------------------------------------------------------------------------- 1 | """This is a ${"""This is a nested docComment!""" True} docComment!""" False 2 | -------------------------------------------------------------------------------- /test/fluid/compose.fld: -------------------------------------------------------------------------------- 1 | let incr = (+) 1 in 2 | (compose incr incr) 3 3 | -------------------------------------------------------------------------------- /test/fluid/dataset/mini-non-renewables.fld: -------------------------------------------------------------------------------- 1 | [ 2 | { country: "USA", year: 2018, nuclearOut: 807.08, nuclearCap: 871.01, gasOut: 1469.13, gasCap: 4431.51, coalOut: 1149.49, coalCap: 2293.89, petrolOut: 42.68, petrolCap: 304.67} 3 | ] -------------------------------------------------------------------------------- /test/fluid/dataset/mini-renewables.fld: -------------------------------------------------------------------------------- 1 | [ 2 | { year: 2018, country: "USA", energyType: "Bio", output: 61.83, capacity: 100.74}, 3 | { year: 2018, country: "USA", energyType: "Hydro", output: 286.62, capacity: 734.79}, 4 | { year: 2018, country: "USA", energyType: "Solar", output: 93.36, capacity: 455.43}, 5 | { year: 2018, country: "USA", energyType: "Wind", output: 272.67, capacity: 829.31} 6 | ] -------------------------------------------------------------------------------- /test/fluid/desugar/list-comp-1.fld: -------------------------------------------------------------------------------- 1 | [ x + y | x <- [5, 4, 3], y <- [9, 7, 5] ] 2 | -------------------------------------------------------------------------------- /test/fluid/desugar/list-comp-10.fld: -------------------------------------------------------------------------------- 1 | [ x | [x] <- [[], [4, 6]] ] 2 | -------------------------------------------------------------------------------- /test/fluid/desugar/list-comp-2.fld: -------------------------------------------------------------------------------- 1 | [ z | x <- [5, 4, 3], y <- [9, 7, 5], let z = x + y, c <- [9, 7, 5] ] 2 | -------------------------------------------------------------------------------- /test/fluid/desugar/list-comp-3.fld: -------------------------------------------------------------------------------- 1 | [ z | x <- [5, 4, 3], y <- [9, 7, 5], let z = x + y, z < 10 ] 2 | -------------------------------------------------------------------------------- /test/fluid/desugar/list-comp-4.fld: -------------------------------------------------------------------------------- 1 | [ x | x : xs <- [[5], [4], [3], []] ] 2 | -------------------------------------------------------------------------------- /test/fluid/desugar/list-comp-5.fld: -------------------------------------------------------------------------------- 1 | [ z | x <- [5, 4, 3], let z = x ] -------------------------------------------------------------------------------- /test/fluid/desugar/list-comp-6.fld: -------------------------------------------------------------------------------- 1 | [ z | let z = 5 ] 2 | -------------------------------------------------------------------------------- /test/fluid/desugar/list-comp-7.fld: -------------------------------------------------------------------------------- 1 | [ [] | [] <- [[5], [4], [3], []] ] 2 | -------------------------------------------------------------------------------- /test/fluid/desugar/list-comp-8.fld: -------------------------------------------------------------------------------- 1 | [ x | [x] <- [[5], [4], [3], []] ] 2 | -------------------------------------------------------------------------------- /test/fluid/desugar/list-comp-9.fld: -------------------------------------------------------------------------------- 1 | [ x + y | [x, y] <- [[5], [4, 6], [3], [], [9, 10]] ] 2 | -------------------------------------------------------------------------------- /test/fluid/desugar/list-enum.fld: -------------------------------------------------------------------------------- 1 | [3..7] 2 | -------------------------------------------------------------------------------- /test/fluid/dict-list-comp.fld: -------------------------------------------------------------------------------- 1 | [ x | {fst: x, snd: []} <- [{fst: 5, third: "hello", snd: []}, {fst: 6, snd: [7, 8]}] ] 2 | -------------------------------------------------------------------------------- /test/fluid/dicts.fld: -------------------------------------------------------------------------------- 1 | let d = {}; 2 | e = { ["a"]: 5, ["a" ++ "b"] : 6 }; 3 | f = { ["ab"]: 12 } 4 | in { 5 | d: d, 6 | e: e, 7 | e_ab: e.ab, 8 | f: dict_map ((+) 1) e, 9 | g: dict_difference e f 10 | } 11 | -------------------------------------------------------------------------------- /test/fluid/div-mod-quot-rem.fld: -------------------------------------------------------------------------------- 1 | [[5 `div` 3, 5 `div` -3, -5 `div` 3, -5 `div` -3], 2 | [5 `mod` 3, 5 `mod` -3, -5 `mod` 3, -5 `mod` -3], 3 | [5 `quot` 3, 5 `quot` -3, -5 `quot` 3, -5 `quot` -3], 4 | [5 `rem` 3, 5 `rem` -3, -5 `rem` 3, -5 `rem` -3]] 5 | -------------------------------------------------------------------------------- /test/fluid/factorial.fld: -------------------------------------------------------------------------------- 1 | let fact x = 2 | if x == 0 3 | then 1 4 | else x * fact (x - 1) 5 | in 6 | fact 8 7 | -------------------------------------------------------------------------------- /test/fluid/filter.fld: -------------------------------------------------------------------------------- 1 | filter ((<) 5) [8, 4, 7, 3] 2 | -------------------------------------------------------------------------------- /test/fluid/first-class-constr.fld: -------------------------------------------------------------------------------- 1 | zipWith (:) [10, 12, 20] [[], [], [], [], []] 2 | -------------------------------------------------------------------------------- /test/fluid/flatten.fld: -------------------------------------------------------------------------------- 1 | let 2 | flatten Empty = []; 3 | flatten (NonEmpty t1 (key, val) t2) = 4 | concat2 (flatten t1) ((key, val) : flatten t2) 5 | in 6 | flatten 7 | (NonEmpty 8 | (NonEmpty Empty (3, "simon") Empty) 9 | (4, "john") 10 | (NonEmpty (NonEmpty Empty (6, "sarah") Empty) (7, "claire") Empty) 11 | ) 12 | 13 | -------------------------------------------------------------------------------- /test/fluid/foldr-sumSquares.fld: -------------------------------------------------------------------------------- 1 | foldr (fun x y -> x * x + y * y) 0 [6, 3, 2] 2 | -------------------------------------------------------------------------------- /test/fluid/graphics/background.fld: -------------------------------------------------------------------------------- 1 | let rect = Rect 0 0 1 3 "red"; 2 | g = Group [Viewport 0 0 1 3 "white" 0 (Scale 1 1) (Translate 0 0) rect]; 3 | w = 1.0; 4 | m = 0.25; 5 | x_scale = max (w - 2 * m) 0 / w; 6 | translate = Translate (m / x_scale) 0 7 | in Viewport 0 0 w 3 "gray" 0 (Scale x_scale 1) translate g 8 | -------------------------------------------------------------------------------- /test/fluid/graphics/grouped-bar-chart.fld: -------------------------------------------------------------------------------- 1 | let year = 2015; 2 | -- List (Country, List (EnergyType, Output)) -> List (Country, List (EnergyType, Output)) 3 | let exclude countries yearData = 4 | flip map yearData (second (filter (fun (country, countryData) -> not (elem country countries)))) 5 | in caption ("Renewables (GW) by country and energy type, " ++ numToStr year) 6 | (groupedBarChart True colours1 0.2 7 | (exclude 8 | [] -- ["Geothermal", "Ocean", "CSP"] 9 | (lookup year data))) 10 | -------------------------------------------------------------------------------- /test/fluid/graphics/line-chart.fld: -------------------------------------------------------------------------------- 1 | let year1 = head data; 2 | -- List (EnergyType, Output) -> List (EnergyType, Output) 3 | let addTotal kvs = 4 | ("Total", sum (map snd kvs)) : kvs; 5 | -- Country -> List (Year, List (EnergyType, Output)) 6 | let countryData country = 7 | map (second (compose addTotal (lookup country))) data in 8 | -- List Country 9 | (fun country -> 10 | caption country 11 | (lineChart True ("black" : colours1) (fst year1) 12 | (countryData country))) 13 | "China" 14 | -------------------------------------------------------------------------------- /test/fluid/graphics/stacked-bar-chart.fld: -------------------------------------------------------------------------------- 1 | let year = 2015 in 2 | caption ("Total renewables (GW) by country, " ++ numToStr year) 3 | (stackedBarChart True colours1 0.2 (lookup year data)) 4 | -------------------------------------------------------------------------------- /test/fluid/include-input-into-output.fld: -------------------------------------------------------------------------------- 1 | (x, x) 2 | -------------------------------------------------------------------------------- /test/fluid/length.fld: -------------------------------------------------------------------------------- 1 | length [2, 3] 2 | -------------------------------------------------------------------------------- /test/fluid/lexicalScoping.fld: -------------------------------------------------------------------------------- 1 | let decr = let x = 2 in flip (-) x; 2 | x = 8 in 3 | numToStr (decr x) 4 | -------------------------------------------------------------------------------- /test/fluid/lib/dtw.fld: -------------------------------------------------------------------------------- 1 | let nextIndices n m window = 2 | [(i, j) | i <- [1 .. n], 3 | j <- [max 1 (i - window) .. min m (i + window)]]; 4 | 5 | let costMatrixInit rows cols window = 6 | [| let initV = if ((n == 1) `and` (m == 1)) `or` ((abs n m <= window) `and` not ((n == 1) `or` (m == 1))) 7 | then FNum 0 8 | else Infty 9 | in initV | (n, m) in (rows, cols) |]; 10 | 11 | let minAndPrev (i, j) im1 jm1 ijm1 = 12 | let minim = minimal [im1, jm1, ijm1] in 13 | if minim `eq` im1 then 14 | ((i, j + 1), minim) 15 | else 16 | if minim `eq` jm1 then 17 | ((i + 1, j ), minim) 18 | else ((i, j), minim); 19 | 20 | let extractPath indmatrix (n, m) accum = 21 | if (n == 1) `and` (m == 1) 22 | then accum 23 | else 24 | extractPath indmatrix (indmatrix!(n, m)) ((n - 1, m - 1) : accum); 25 | 26 | let localMinUpdate seq1 seq2 cost (costmatrix, indmatrix) (i, j) = 27 | let iEntr = nth (i - 1) seq1; 28 | jEntr = nth (j - 1) seq2; 29 | dist = cost iEntr jEntr; 30 | ip = i + 1; 31 | jp = j + 1; 32 | im1 = costmatrix!(i , jp); 33 | jm1 = costmatrix!(ip, j); 34 | im1jm1 = costmatrix!(i, j); 35 | (prev, FNum minim) = minAndPrev (i, j) im1 jm1 im1jm1; 36 | newVal = FNum (dist + minim) 37 | in (matrixUpdate costmatrix (ip, jp) newVal, matrixUpdate indmatrix (ip, jp) prev); 38 | 39 | let computeDTW seq1 seq2 cost window = 40 | let n = length seq1; 41 | m = length seq2; 42 | initD = costMatrixInit (n + 1) (m + 1) window; 43 | initI = [| 0 | (i,j) in (n + 1, m + 1)|]; 44 | indexing = nextIndices n m window; 45 | (finished, indices) = foldl (localMinUpdate seq1 seq2 cost) (initD, initI) indexing 46 | in 47 | (finished, extractPath indices (n + 1, m + 1) Nil); 48 | -------------------------------------------------------------------------------- /test/fluid/lib/fnum.fld: -------------------------------------------------------------------------------- 1 | let comp Infty Infty = EQ; 2 | comp Infty (FNum y) = GT; 3 | comp (FNum x) Infty = LT; 4 | comp (FNum x) (FNum y) = compare x y; 5 | 6 | let fmin x y = 7 | match comp x y as { 8 | LT -> x; 9 | EQ -> x; 10 | GT -> y 11 | }; 12 | 13 | let minimal = foldl1 fmin; 14 | 15 | let add Infty _ = Infty; 16 | add (FNum x) Infty = Infty; 17 | add (FNum x) (FNum y) = FNum (x + y); 18 | 19 | let eq Infty Infty = True; 20 | eq Infty (FNum x) = False; 21 | eq (FNum x) Infty = False; 22 | eq (FNum x) (FNum y) = x == y; 23 | -------------------------------------------------------------------------------- /test/fluid/lib/some-constants.fld: -------------------------------------------------------------------------------- 1 | let x = 1; 2 | -------------------------------------------------------------------------------- /test/fluid/linked-inputs/energyscatter.fld: -------------------------------------------------------------------------------- 1 | let isCountry name x = name == x.country; 2 | isYear year x = year == x.year; 3 | 4 | let plot year countries = 5 | let rens = filter (isYear year) renewables; 6 | nonRens = filter (isYear year) nonRenewables; 7 | let plotCountry country = 8 | let rens' = filter (isCountry country) rens; 9 | rensOut = sum (map (fun x -> x.output) rens'); 10 | rensCap = sum (map (fun x -> x.capacity) rens'); 11 | x = head (filter (isCountry country) nonRens); 12 | nonRensCap = x.nuclearCap + x.petrolCap + x.gasCap + x.coalCap 13 | in { 14 | x: rensCap / (rensCap + nonRensCap), 15 | y: (rensOut + x.nuclearOut) / (rensCap + x.nuclearCap) 16 | } 17 | in map plotCountry countries 18 | 19 | in ScatterPlot { 20 | caption: "Clean energy efficiency vs. proportion of renewable energy capacity", 21 | points: plot 2018 [ "BRA", "CHN", "DEU", "FRA", "EGY", "IND", "JPN", "MEX", "NGA", "USA" ], 22 | labels: { x: "Renewables/TotalEnergyCap", y: "Clean Capacity Factor" } 23 | } 24 | -------------------------------------------------------------------------------- /test/fluid/linked-inputs/mini-energyscatter.fld: -------------------------------------------------------------------------------- 1 | let sumNonRenewables x = x.nuclearCap + x.petrolCap + x.gasCap + x.coalCap; 2 | let country_check name x = name == x.country; 3 | let year_check year x = year == x.year; 4 | 5 | let energy year c_names = 6 | let rens2018 = filter (year_check year) renewables; 7 | let nonrens2018 = filter (year_check year) nonRenewables; 8 | let energy_per_country c_name = 9 | let filteredRen = filter (country_check c_name) rens2018; 10 | let non_ren_x = head (filter (country_check c_name) nonrens2018); 11 | let rensOut = sum (map (fun x = x.output) filteredRen); 12 | let rensCap = sum (map (fun x = x.capacity) filteredRen); 13 | let nonRensCap = sumNonRenewables non_ren_x 14 | in { 15 | name: c_name, 16 | totalCap: rensCap + nonRensCap, 17 | nonRenewables: nonRensCap, 18 | renCapFactor: (rensOut + non_ren_x.nuclearOut) / (rensCap + non_ren_x.nuclearCap) 19 | } 20 | in 21 | map energy_per_country c_names 22 | in ScatterPlot { 23 | caption: "Clean energy efficiency against proportion of renewable energy cap", 24 | points: [{ 25 | x: country.nonRenewables / country.totalCap, 26 | y: country.renCapFactor 27 | }| country <- energy 2018 ["USA"]], 28 | labels: { x: "Renewables/TotalEnergyCap", y: "Clean Capacity Factor" } 29 | } 30 | -------------------------------------------------------------------------------- /test/fluid/linked-outputs/convolution-data.fld: -------------------------------------------------------------------------------- 1 | [[15, 13, 6 , 9, 16], 2 | [12, 5 , 15, 4, 13], 3 | [14, 9 , 20, 8, 1 ], 4 | [4 , 10, 3 , 7, 19], 5 | [3 , 11, 15, 2, 9 ]] -------------------------------------------------------------------------------- /test/fluid/linked-outputs/convolution.fld: -------------------------------------------------------------------------------- 1 | (let filter' = [[0, 0, 0], 2 | [3, 7, 1], 3 | [0, 0, 0]]; 4 | filter = [| nth2 i j filter' | (i, j) in (3, 3) |]; 5 | image = [| nth2 i j data | (i, j) in (5, 5) |] 6 | in convolve image filter wrap, 7 | let filter' = [[0, 2, 0], 8 | [0, 7, 0], 9 | [0, 5, 0]]; 10 | filter = [| nth2 i j filter' | (i, j) in (3, 3) |]; 11 | image = [| nth2 i j data | (i, j) in (5, 5) |] 12 | in convolve image filter zero) 13 | -------------------------------------------------------------------------------- /test/fluid/linked-outputs/line-chart.fld: -------------------------------------------------------------------------------- 1 | let series type country = [ 2 | { x: row.year, y: row.output } 3 | | year <- [2013..2018], row <- renewables, 4 | row.year == year, row.energyType == type, row.country == country 5 | ] in LineChart { 6 | tickLabels: { x: Default, y: Default }, 7 | caption: "Change in renewable energy output of USA relative to China", 8 | size: { width: 330, height: 285 }, 9 | plots: [ 10 | LinePlot { name: type, points: plot } 11 | | type <- ["Bio", "Hydro", "Solar", "Wind"], 12 | let plot = zipWith (fun p1 p2 -> { x: p1.x, y: p1.y / p2.y }) 13 | (series type "USA") (series type "China") 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /test/fluid/linked-outputs/moving-average.fld: -------------------------------------------------------------------------------- 1 | let nthPad n xs = 2 | nth (min (max n 0) (length xs - 1)) xs; 3 | movingAvg ys window = 4 | [ sum [ nthPad n ys | n <- [ i - window .. i + window ] ] / (1 + 2 * window) 5 | | i <- [ 0 .. length ys - 1 ] ]; 6 | movingAvg' rs window = 7 | zipWith 8 | (fun x y -> {x: x, y: y}) 9 | (map (fun r -> r.x) rs) 10 | (movingAvg (map (fun r -> r.y) rs) window); 11 | let points = 12 | [ { x: r.year, y: r.emissions } | r <- methane, r.type == "Agriculture" ] 13 | in LineChart { 14 | tickLabels: { x: Rotated, y: Default }, 15 | size: { width: 330, height: 285 }, 16 | caption: "SSP5-8.5 projected methane emissions (Agriculture)", 17 | plots: [ LinePlot { name: "Moving average", points: movingAvg' points 1 }, 18 | LinePlot { name: "Original curve", points: points } ] 19 | } 20 | -------------------------------------------------------------------------------- /test/fluid/linked-outputs/pairs-data.fld: -------------------------------------------------------------------------------- 1 | (3, 7) -------------------------------------------------------------------------------- /test/fluid/linked-outputs/pairs.fld: -------------------------------------------------------------------------------- 1 | let q = 1 in 2 | ((fun (w, x) -> (w, (w, x))) data) -------------------------------------------------------------------------------- /test/fluid/lookup.fld: -------------------------------------------------------------------------------- 1 | let lookup k Empty = None; 2 | lookup k (NonEmpty t1 (key, val) t2) = 3 | match compare k key as { 4 | LT -> lookup k t1; 5 | EQ -> Some val; 6 | GT -> lookup k t2 7 | } 8 | in 9 | lookup 10 | 6 11 | (NonEmpty 12 | (NonEmpty Empty (3, "simon") Empty) 13 | (4, "john") 14 | (NonEmpty (NonEmpty Empty (6, "sarah") Empty) (7, "claire") Empty) 15 | ) 16 | -------------------------------------------------------------------------------- /test/fluid/map.fld: -------------------------------------------------------------------------------- 1 | map ((+) 1) [4, 6, 12, 14, 3, 2, -4] 2 | -------------------------------------------------------------------------------- /test/fluid/mergeSort.fld: -------------------------------------------------------------------------------- 1 | let split [] = ([], []); 2 | split (x : xs) = 3 | let (ys, zs) = split xs in (x : zs, ys); 4 | 5 | merge xs ys = 6 | match (xs, ys) as { 7 | ([], _) -> ys; 8 | (x : xs', []) -> xs; 9 | (x : xs', y : ys') -> 10 | if x < y 11 | then x : merge xs' ys 12 | else y : merge xs ys' 13 | }; 14 | 15 | mergesort xs = 16 | if length xs < 2 17 | then xs 18 | else 19 | let (ys, zs) = split xs in 20 | merge (mergesort ys) (mergesort zs) 21 | in 22 | mergesort [3, 1, 2] 23 | -------------------------------------------------------------------------------- /test/fluid/normalise.fld: -------------------------------------------------------------------------------- 1 | let (x, y) = (6.0, 12.0); 2 | sum = x + y in 3 | (floor (x * 100.0 / sum), floor (y * 100 / sum)) 4 | -------------------------------------------------------------------------------- /test/fluid/nub.fld: -------------------------------------------------------------------------------- 1 | nub [1, 2, 3, 3, 4] 2 | -------------------------------------------------------------------------------- /test/fluid/pattern-match.fld: -------------------------------------------------------------------------------- 1 | -- Silly test of pattern-matching for list notation 2 | let 3 | zip3 [] [] = []; 4 | zip3 [x1] [y1] = (x1, y1) : zip3 [] []; 5 | zip3 [x1, x2] [y1, y2] = 6 | (x1, y1) : zip3 [x2] [y2]; 7 | zip3 [x1, x2, x3] [y1, y2, y3] = 8 | (x1 , y1) : zip3 [x2, x3] [y2, y3]; 9 | zip3 (x1 : x2 : x3 : x4 : xs) (y1 : y2 : y3 : y4 : ys) = 10 | (x1, y1) : (x2, y2) : (x3, y3) : (x4, y4) : zip3 xs ys 11 | in 12 | length (zip3 [3, 4, 5, 6] [5, 6, 7, 8]) 13 | -------------------------------------------------------------------------------- /test/fluid/percent.fld: -------------------------------------------------------------------------------- 1 | mkPercent 0.25 -------------------------------------------------------------------------------- /test/fluid/plot/methane.fld: -------------------------------------------------------------------------------- 1 | let series type = [ 2 | { x: row.year, y: row.emissions } 3 | | row <- methane, row.type == type 4 | ] in LineChart { 5 | size: { width: 450, height: 285 }, 6 | tickLabels: { x: Rotated, y: Default }, 7 | caption: "Sources of methane emissions for Africa and the Middle East (IPCC AR6)", 8 | plots: [ 9 | LinePlot { name: type, points: series type } 10 | | type <- ["Agricultural Waste Burning", "Agriculture", "Energy Sector", 11 | "Forest Burning", "Grassland Burning", "Industrial Sector", 12 | "Residential Commercial Other", "Transportation Sector", "Waste"] 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /test/fluid/plot/non-renewables.fld: -------------------------------------------------------------------------------- 1 | let countries = ["BRA", "EGY", "IND", "JPN"]; 2 | let totalFor year country = 3 | let [ row ] = [ row | row <- nonRenewables, row.year == year, row.country == country ] 4 | in row.nuclearOut + row.gasOut + row.coalOut + row.petrolOut; 5 | let stack year = [ { y: country, z: totalFor year country } | country <- countries ]; 6 | let yearData year = [ row | row <- nonRenewables, row.year == year, row.country `elem` countries ] 7 | in MultiView { 8 | barChart: BarChart { 9 | caption: "Non-renewables output", 10 | size: { width: 275, height: 185 }, 11 | stackedBars: [ { x: numToStr year, bars: stack year } | year <- [2014..2018] ] 12 | }, 13 | scatterPlot: ScatterPlot { 14 | caption: "", 15 | points: [ { 16 | x: sum [ row.nuclearOut | row <- yearData year ], 17 | y: sum [ row.nuclearCap | row <- yearData year ] 18 | } | year <- [2014..2018] ], 19 | labels: { x: "Nuclear capacity", y: "Nuclear output" } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/fluid/range.fld: -------------------------------------------------------------------------------- 1 | let range1 (m, n) = [m..n]; 2 | range2 ((m1, n1), (m2, n2)) = 3 | [ (i1, i2) | i1 <- range1 (m1, m2), i2 <- range1 (n1, n2)] 4 | in range2 ((0, 0), (1, 1)) 5 | -------------------------------------------------------------------------------- /test/fluid/record-lookup.fld: -------------------------------------------------------------------------------- 1 | let city = { name: "Germany" }; 2 | country = { cities: [ "Germany" ] } 3 | in city.name `elem` country.cities 4 | -------------------------------------------------------------------------------- /test/fluid/records.expect.fld: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/explorable-viz/fluid/e42120612f714fe85676b28538ec3698cd9b2c58/test/fluid/records.expect.fld -------------------------------------------------------------------------------- /test/fluid/records.fld: -------------------------------------------------------------------------------- 1 | let f {fst: x, snd: []} = x; 2 | f {fst: x, snd: (y: xs)} = y; 3 | 4 | g {} = 7 5 | in { 6 | a: f {snd: [], fst: 2}, 7 | b: f {fst: 4, snd: [6, 7]}, 8 | c: g {unused: 22}, 9 | d: [ x | {fst: x, snd: []} <- [{fst: 5, third: "hello", snd: []}, {fst: 6, snd: [7, 8]}] ], 10 | e: {a: 4, h: {i: 6, j: 7}}.h.j 11 | } 12 | -------------------------------------------------------------------------------- /test/fluid/reverse.fld: -------------------------------------------------------------------------------- 1 | reverse [1, 2] 2 | -------------------------------------------------------------------------------- /test/fluid/scratchpad.fld: -------------------------------------------------------------------------------- 1 | let nth2 i j xs = nth (i - 1) (nth (j - 1) xs) in 2 | let xs = [[1, 4, 8], 3 | [3, 2, 17], 4 | [0, 14, 6]]; 5 | ys = [| nth2 i j xs | (i, j) in (3, 3) |] 6 | in ys ! (3, 2) 7 | -------------------------------------------------------------------------------- /test/fluid/slicing/add.expect.fld: -------------------------------------------------------------------------------- 1 | ⸨5⸩ + (⸨0⸩ + ⸨3⸩) -------------------------------------------------------------------------------- /test/fluid/slicing/add.fld: -------------------------------------------------------------------------------- 1 | 5 + (0 + 3) 2 | -------------------------------------------------------------------------------- /test/fluid/slicing/array/array.expect.fld: -------------------------------------------------------------------------------- 1 | let xs = [[1, 4, 8], [3, _2_, 17], [0, 14, 6]] 2 | in [| nth2 i j xs | (i, j) in (3, 3) |] -------------------------------------------------------------------------------- /test/fluid/slicing/array/array.fld: -------------------------------------------------------------------------------- 1 | let xs = [[1, 4, 8], 2 | [3, 2, 17], 3 | [0, 14, 6]] 4 | in [| nth2 i j xs | (i, j) in (3, 3) |] 5 | -------------------------------------------------------------------------------- /test/fluid/slicing/array/dims.expect.fld: -------------------------------------------------------------------------------- 1 | let (x, y) = ⸨(⸨3⸩, ⸨3⸩)⸩; 2 | yss = ⸨[|0|(i, j) in (x, y)|]⸩ in 3 | dims yss -------------------------------------------------------------------------------- /test/fluid/slicing/array/dims.fld: -------------------------------------------------------------------------------- 1 | let (x, y) = (3, 3); 2 | yss = [| 0 | (i, j) in (x, y) |] 3 | in dims yss 4 | -------------------------------------------------------------------------------- /test/fluid/slicing/array/lookup.expect.fld: -------------------------------------------------------------------------------- 1 | let xs = [[1, 4, 8], [3, 2, 17], [0, ⸨14⸩, 6]]; 2 | ys = [|nth2 i j xs|(i, j) in (3, 3)|] in 3 | ys ! ((3, 2)) -------------------------------------------------------------------------------- /test/fluid/slicing/array/lookup.fld: -------------------------------------------------------------------------------- 1 | let xs = [[1, 4, 8], 2 | [3, 2, 17], 3 | [0, 14, 6]]; 4 | ys = [| nth2 i j xs | (i, j) in (3, 3) |] 5 | in ys ! (3, 2) 6 | -------------------------------------------------------------------------------- /test/fluid/slicing/convolution/edgeDetect.expect.fld: -------------------------------------------------------------------------------- 1 | convolve inputImage filter extend -------------------------------------------------------------------------------- /test/fluid/slicing/convolution/edgeDetect.fld: -------------------------------------------------------------------------------- 1 | convolve inputImage filter extend 2 | -------------------------------------------------------------------------------- /test/fluid/slicing/convolution/emboss-wrap.fld: -------------------------------------------------------------------------------- 1 | convolve inputImage filter wrap 2 | -------------------------------------------------------------------------------- /test/fluid/slicing/convolution/emboss.expect.fld: -------------------------------------------------------------------------------- 1 | convolve inputImage filter zero -------------------------------------------------------------------------------- /test/fluid/slicing/convolution/emboss.fld: -------------------------------------------------------------------------------- 1 | convolve inputImage filter zero 2 | -------------------------------------------------------------------------------- /test/fluid/slicing/convolution/filter/edge-detect.fld: -------------------------------------------------------------------------------- 1 | let filter = 2 | let edgeDetect = [[0, 1, 0], 3 | [1, -4, 1], 4 | [0, 1, 0]] in 5 | [| nth2 i j edgeDetect | (i, j) in (3, 3) |]; 6 | -------------------------------------------------------------------------------- /test/fluid/slicing/convolution/filter/emboss.fld: -------------------------------------------------------------------------------- 1 | let filter = 2 | let emboss = [[-2, -1, 0], 3 | [-1, 1, 1], 4 | [ 0, 1, 2]] in 5 | [| nth2 i j emboss | (i, j) in (3, 3) |]; 6 | -------------------------------------------------------------------------------- /test/fluid/slicing/convolution/filter/gaussian.fld: -------------------------------------------------------------------------------- 1 | let filter = 2 | let gaussian = [[1, 4, 1], 3 | [4, 16, 4], 4 | [1, 4, 1]] in 5 | [| nth2 i j gaussian | (i, j) in (3, 3) |]; 6 | -------------------------------------------------------------------------------- /test/fluid/slicing/convolution/gaussian.expect.fld: -------------------------------------------------------------------------------- 1 | convolve inputImage filter zero -------------------------------------------------------------------------------- /test/fluid/slicing/convolution/gaussian.fld: -------------------------------------------------------------------------------- 1 | convolve inputImage filter zero 2 | -------------------------------------------------------------------------------- /test/fluid/slicing/convolution/test-image.fld: -------------------------------------------------------------------------------- 1 | let inputImage = 2 | let image = [[15, 13, 6, 9, 16], 3 | [12, 5, 15, 4, 13], 4 | [14, 9, 20, 8, 1], 5 | [ 4, 10, 3, 7, 19], 6 | [ 3, 11, 15, 2, 9]] in 7 | [| nth2 i j image | (i, j) in (5, 5) |]; 8 | -------------------------------------------------------------------------------- /test/fluid/slicing/dict/create.expect.fld: -------------------------------------------------------------------------------- 1 | {["a"] : 5, 2 | [⸨"a"⸩ ++ ⸨"b"⸩] : 6} -------------------------------------------------------------------------------- /test/fluid/slicing/dict/create.fld: -------------------------------------------------------------------------------- 1 | { ["a"]: 5, ["a" ++ "b"]: 6 } 2 | -------------------------------------------------------------------------------- /test/fluid/slicing/dict/difference.expect.fld: -------------------------------------------------------------------------------- 1 | let e = ⸨{["a"] : 5, 2 | ["a" ++ "b"] : 6}⸩; 3 | f = ⸨{["ab"] : 12}⸩ in 4 | dict_difference e f -------------------------------------------------------------------------------- /test/fluid/slicing/dict/difference.fld: -------------------------------------------------------------------------------- 1 | let e = { ["a"]: 5, ["a" ++ "b"]: 6 }; 2 | f = { ["ab"]: 12 } 3 | in dict_difference e f 4 | -------------------------------------------------------------------------------- /test/fluid/slicing/dict/disjointUnion.expect.fld: -------------------------------------------------------------------------------- 1 | {⸨a⸩ : 5, 2 | b : 6} `dict_disjointUnion` {c : ⸨7⸩} -------------------------------------------------------------------------------- /test/fluid/slicing/dict/disjointUnion.fld: -------------------------------------------------------------------------------- 1 | { a: 5, b: 6 } `dict_disjointUnion` { c: 7 } 2 | -------------------------------------------------------------------------------- /test/fluid/slicing/dict/foldl.expect.fld: -------------------------------------------------------------------------------- 1 | foldl (*) 1 (dict_foldl concat2 [] {a : [5, 6], 2 | b : [⸨0⸩, 10], 3 | c : [3, 4]}) -------------------------------------------------------------------------------- /test/fluid/slicing/dict/foldl.fld: -------------------------------------------------------------------------------- 1 | foldl (*) 1 (dict_foldl concat2 [] { a: [5, 6], b: [0, 10], c: [3, 4] }) 2 | -------------------------------------------------------------------------------- /test/fluid/slicing/dict/get.expect.fld: -------------------------------------------------------------------------------- 1 | let d = {a : 5, 2 | ["a" ++ "b"] : {fst : 6, 3 | snd : ⸨0⸩}}; 4 | a = "a" in 5 | d.a * d.[(a ++ "b")].snd -------------------------------------------------------------------------------- /test/fluid/slicing/dict/get.fld: -------------------------------------------------------------------------------- 1 | let d = { a: 5, ["a" ++ "b"]: {fst: 6, snd: 0} }; 2 | a = "a" 3 | in d.a * d.[a ++ "b"].snd 4 | -------------------------------------------------------------------------------- /test/fluid/slicing/dict/intersectionWith.expect.fld: -------------------------------------------------------------------------------- 1 | dict_intersectionWith (let x = ⸨2⸩ in 2 | (fun n m = (n + m) * x)) {a : 5, 3 | b : ⸨6⸩, 4 | c : ⸨3⸩} {b : ⸨-6⸩, 5 | c : ⸨7⸩} -------------------------------------------------------------------------------- /test/fluid/slicing/dict/intersectionWith.fld: -------------------------------------------------------------------------------- 1 | dict_intersectionWith 2 | (let x = 2 in (fun n m = (( n + m ) * x )) ) 3 | { a: 5, b: 6, c: 3 } 4 | { b: -6, c: 7 } -------------------------------------------------------------------------------- /test/fluid/slicing/dict/map.expect.fld: -------------------------------------------------------------------------------- 1 | let incHead = let x = ⸨3⸩ in 2 | (fun xs = head xs + x); 3 | d = {a : [⸨5⸩, 6], 4 | b : [⸨9⸩, 10], 5 | c : [3, 4]}; 6 | e = {c : []}; 7 | d' = dict_map incHead (dict_difference d e) in 8 | d'.a + d'.b -------------------------------------------------------------------------------- /test/fluid/slicing/dict/map.fld: -------------------------------------------------------------------------------- 1 | let incHead = let x = 3 in fun xs -> head xs + x; 2 | d = { a: [5, 6], b: [9, 10], c: [3, 4] }; 3 | e = { c: [] }; 4 | d' = dict_map incHead (dict_difference d e) 5 | in d'.a + d'.b 6 | -------------------------------------------------------------------------------- /test/fluid/slicing/dict/match.expect.fld: -------------------------------------------------------------------------------- 1 | let f {fst : x, 2 | snd : []} = x; 3 | f {fst : x, 4 | snd : (y : ys)} = y in 5 | ⸨{⸨a⸩ : f {snd : [], 6 | fst : ⸨2⸩}}⸩ -------------------------------------------------------------------------------- /test/fluid/slicing/dict/match.fld: -------------------------------------------------------------------------------- 1 | let f {fst: x, snd: []} = x; 2 | f {fst: x, snd: (y: ys)} = y 3 | in { 4 | a: f {snd: [], fst: 2 } 5 | } 6 | -------------------------------------------------------------------------------- /test/fluid/slicing/divide.expect.fld: -------------------------------------------------------------------------------- 1 | ⸨362⸩ / ⸨9⸩ -------------------------------------------------------------------------------- /test/fluid/slicing/divide.fld: -------------------------------------------------------------------------------- 1 | 362 / 9 2 | -------------------------------------------------------------------------------- /test/fluid/slicing/dtw/average-series.expect.fld: -------------------------------------------------------------------------------- 1 | let d x y = (x - y) * (x - y) in 2 | let avg x y = (x + y) / ⸨2⸩ in 3 | let avgSeq seq1 seq2 f = map (fun (i, j) = f (nth (i - 1) seq1) (nth (j - 1) seq2)) in 4 | let seq1 = [3, ⸨1⸩, 2, 2, 1]; 5 | seq2 = [2, 0, ⸨0⸩, 3, 3, 1, 0]; 6 | window = 2; 7 | (costs, matched) = computeDTW seq1 seq2 d window in 8 | avgSeq seq1 seq2 avg matched -------------------------------------------------------------------------------- /test/fluid/slicing/dtw/average-series.fld: -------------------------------------------------------------------------------- 1 | let d x y = (x - y) * (x - y); 2 | let avg x y = (x + y) / 2; 3 | let avgSeq seq1 seq2 f = map (fun (i, j) -> f (nth (i - 1) seq1) (nth (j - 1) seq2)); 4 | let seq1 = [3,1,2,2,1]; 5 | seq2 = [2,0,0,3,3,1,0]; 6 | window = 2; 7 | (costs, matched) = computeDTW seq1 seq2 d window 8 | in avgSeq seq1 seq2 avg matched 9 | -------------------------------------------------------------------------------- /test/fluid/slicing/dtw/compute-dtw.expect.fld: -------------------------------------------------------------------------------- 1 | let d x y = (x - y) * (x - y) in 2 | let seq1 = ⸨[⸩ ⸨3⸩ ⸨,⸩ ⸨1⸩ ⸨,⸩ 2 ⸨,⸩ 2 ⸨,⸩ 1 ⸨]⸩; 3 | seq2 = ⸨[⸩ ⸨2⸩ ⸨,⸩ ⸨0⸩ ⸨,⸩ ⸨0⸩ ⸨,⸩ 3 ⸨,⸩ 3 ⸨,⸩ 1 ⸨,⸩ 0 ⸨]⸩; 4 | window = ⸨2⸩; 5 | (costs, matched) = computeDTW seq1 seq2 d window in 6 | matched -------------------------------------------------------------------------------- /test/fluid/slicing/dtw/compute-dtw.fld: -------------------------------------------------------------------------------- 1 | let d x y = (x - y) * (x - y) in 2 | let seq1 = [3, 1, 2, 2, 1]; 3 | seq2 = [2, 0, 0, 3, 3, 1, 0]; 4 | window = 2; 5 | (costs, matched) = computeDTW seq1 seq2 d window in 6 | matched 7 | -------------------------------------------------------------------------------- /test/fluid/slicing/explained.expect.fld: -------------------------------------------------------------------------------- 1 | @ (1 + 1) @ -------------------------------------------------------------------------------- /test/fluid/slicing/explained.fld: -------------------------------------------------------------------------------- 1 | @ (1 + 1) @ -------------------------------------------------------------------------------- /test/fluid/slicing/filter.expect.fld: -------------------------------------------------------------------------------- 1 | let filter p [] = []; 2 | filter p (x : xs) = let ys = filter p xs in 3 | if p x then ⸨(x : ys)⸩ else ys in 4 | filter ((<) ⸨5⸩) [⸨8⸩, 4, 7, 3] -------------------------------------------------------------------------------- /test/fluid/slicing/filter.fld: -------------------------------------------------------------------------------- 1 | let filter p [] = []; 2 | filter p (x : xs) = 3 | let ys = filter p xs in 4 | if p x then x : ys else ys 5 | in 6 | filter ((<) 5) [8, 4, 7, 3] 7 | -------------------------------------------------------------------------------- /test/fluid/slicing/intersperse-1.expect.fld: -------------------------------------------------------------------------------- 1 | let intersperse [] _ = []; 2 | intersperse [x] _ = [x]; 3 | intersperse (x : (y : ys)) sep = (x : ⸨(sep : intersperse ((y : ys)) sep)⸩) in 4 | intersperse ⸨[⸩ 1 ⸨,⸩ 2, 3] 0 -------------------------------------------------------------------------------- /test/fluid/slicing/intersperse-2.expect.fld: -------------------------------------------------------------------------------- 1 | let intersperse [] _ = []; 2 | intersperse [x] _ = [x]; 3 | intersperse (x : (y : ys)) sep = ⸨(x : (sep : intersperse (⸨(y : ys)⸩) sep))⸩ in 4 | intersperse ⸨[⸩ 1 ⸨,⸩ 2 ⸨,⸩ 3] 0 -------------------------------------------------------------------------------- /test/fluid/slicing/intersperse.fld: -------------------------------------------------------------------------------- 1 | let intersperse [] _ = []; 2 | intersperse [x] _ = [x]; 3 | intersperse (x : y : ys) sep = x : sep : intersperse (y : ys) sep 4 | in 5 | intersperse [1, 2, 3] 0 6 | -------------------------------------------------------------------------------- /test/fluid/slicing/length.expect.fld: -------------------------------------------------------------------------------- 1 | length ⸨[⸩ 1 ⸨,⸩ 2 ⸨,⸩ 3 ⸨,⸩ 4 ⸨,⸩ 5 ⸨]⸩ -------------------------------------------------------------------------------- /test/fluid/slicing/length.fld: -------------------------------------------------------------------------------- 1 | length [1, 2, 3, 4, 5] 2 | -------------------------------------------------------------------------------- /test/fluid/slicing/linked-outputs/bar-chart-line-chart.expect.fld: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/explorable-viz/fluid/e42120612f714fe85676b28538ec3698cd9b2c58/test/fluid/slicing/linked-outputs/bar-chart-line-chart.expect.fld -------------------------------------------------------------------------------- /test/fluid/slicing/linked-outputs/bar-chart-line-chart.fld: -------------------------------------------------------------------------------- 1 | MultiView { 2 | barChart: 3 | let totalFor c rows = 4 | sum [ row.output | row <- rows, row.country == c ]; 5 | let data2015 = [ row | row <- renewables, row.year == 2015 ]; 6 | countryData = [ { x: c, bars: [ { y: "output", z: totalFor c data2015 } ] } 7 | | c <- ["China", "USA", "Germany"] ] 8 | in BarChart { 9 | caption: "Total output by country", 10 | size: { width: 275, height: 185 }, 11 | stackedBars: countryData 12 | }, 13 | lineChart: 14 | let series type country = [ 15 | { x: row.year, y: row.output } 16 | | year <- [2013..2018], row <- renewables, 17 | row.year == year, row.energyType == type, row.country == country 18 | ] in LineChart { 19 | tickLabels: { x: Default, y: Default }, 20 | size: { width: 330, height: 285 }, 21 | caption: "Output of USA relative to China", 22 | plots: [ 23 | LinePlot { name: type, points: plot } 24 | | type <- ["Bio", "Hydro", "Solar", "Wind"], 25 | let plot = zipWith (fun p1 p2 -> { x: p1.x, y: p1.y / p2.y }) 26 | (series type "USA") (series type "China") 27 | ] 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/fluid/slicing/linked-outputs/stacked-bar-scatter-plot.expect.fld: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/explorable-viz/fluid/e42120612f714fe85676b28538ec3698cd9b2c58/test/fluid/slicing/linked-outputs/stacked-bar-scatter-plot.expect.fld -------------------------------------------------------------------------------- /test/fluid/slicing/linked-outputs/stacked-bar-scatter-plot.fld: -------------------------------------------------------------------------------- 1 | MultiView { 2 | stackedBarChart: 3 | let totalFor year country rows = 4 | let [ row ] = [ row | row <- rows, row.year == year, row.country == country ] 5 | in row.nuclearOut + row.gasOut + row.coalOut + row.petrolOut; 6 | let stack year = [ { y: country, z: totalFor year country nonRenewables } 7 | | country <- ["BRA", "EGY", "IND", "JPN"] ] 8 | in BarChart { 9 | caption: "Non-renewables by country", 10 | size: { width: 275, height: 185 }, 11 | stackedBars: [ { x: numToStr year, bars: stack year } 12 | | year <- [2014..2018] ] 13 | }, 14 | scatterPlot: 15 | let isCountry name x = name == x.country; 16 | isYear year x = year == x.year; 17 | 18 | let plot year countries = 19 | let rens = filter (isYear year) renewables; 20 | nonRens = filter (isYear year) nonRenewables; 21 | let plotCountry country = 22 | let rens' = filter (isCountry country) rens; 23 | rensOut = sum (map (fun x -> x.output) rens'); 24 | rensCap = sum (map (fun x -> x.capacity) rens'); 25 | x = head (filter (isCountry country) nonRens); 26 | nonRensCap = x.nuclearCap + x.petrolCap + x.gasCap + x.coalCap 27 | in { 28 | x: rensCap / (rensCap + nonRensCap), 29 | y: (rensOut + x.nuclearOut) / (rensCap + x.nuclearCap) 30 | } 31 | in map plotCountry countries 32 | 33 | in ScatterPlot { 34 | caption: "Clean energy efficiency vs proportion of renewable energy capacity", 35 | points: plot 2018 [ "BRA", "CHN", "DEU", "FRA", "EGY", "IND", "JPN", "MEX", "NGA", "USA" ], 36 | labels: { x: "Renewables/TotalEnergyCap", y: "Clean Capacity Factor" } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /test/fluid/slicing/list-comp-1.expect.fld: -------------------------------------------------------------------------------- 1 | let data = [{year : 2013, country : "China", energyType : "Bio", output : 6.2} 2 | , {year : 2013, country : "China", energyType : ⸨"Hydro"⸩, output : 260} 3 | , {year : 2013, country : "China", energyType : "Solar", output : 19.9} 4 | , {year : 2013, country : "China", energyType : "Wind", output : 91} 5 | ] in 6 | ⸨[row.output|type <- ["Bio", ⸨"Hydro"⸩, "Solar", "Wind"], row <- data, row.energyType == type]⸩ -------------------------------------------------------------------------------- /test/fluid/slicing/list-comp-2.expect.fld: -------------------------------------------------------------------------------- 1 | let data = [{year : 2013, country : "China", energyType : "Bio", output : 6.2} 2 | , {year : 2013, country : "China", energyType : "Hydro", output : 260} 3 | , {year : 2013, country : "China", energyType : ⸨"Solar"⸩, output : 19.9} 4 | , {year : 2013, country : "China", energyType : "Wind", output : 91} 5 | ] in 6 | ⸨[row.output|type <- ["Bio", "Hydro", ⸨"Solar"⸩, "Wind"], row <- data, row.energyType == type]⸩ -------------------------------------------------------------------------------- /test/fluid/slicing/list-comp.fld: -------------------------------------------------------------------------------- 1 | let data = [ 2 | { year: 2013, country: "China", energyType: "Bio", output: 6.2 }, 3 | { year: 2013, country: "China", energyType: "Hydro", output: 260 }, 4 | { year: 2013, country: "China", energyType: "Solar", output: 19.9 }, 5 | { year: 2013, country: "China", energyType: "Wind", output: 91 } 6 | ] in 7 | [ row.output | type <- ["Bio", "Hydro", "Solar", "Wind"], 8 | row <- data, row.energyType == type ] 9 | -------------------------------------------------------------------------------- /test/fluid/slicing/lookup.expect.fld: -------------------------------------------------------------------------------- 1 | let lookup k (Empty) = None; 2 | lookup k (NonEmpty t1 (key, val) t2) = match compare k key as 3 | {(LT) -> lookup k t1; 4 | (EQ) -> ⸨Some val⸩; 5 | (GT) -> lookup k t2} in 6 | lookup ⸨6⸩ (NonEmpty (NonEmpty (Empty) ((3, "USA")) (Empty)) ((4, "China")) (NonEmpty (⸨NonEmpty (Empty) (⸨(⸨6⸩, "Germany")⸩) (Empty)⸩) ((7, "UK")) (Empty))) -------------------------------------------------------------------------------- /test/fluid/slicing/lookup.fld: -------------------------------------------------------------------------------- 1 | let lookup k Empty = None; 2 | lookup k (NonEmpty t1 (key, val) t2) = 3 | match compare k key as { 4 | LT -> lookup k t1; 5 | EQ -> Some val; 6 | GT -> lookup k t2 7 | } 8 | in 9 | lookup 6 10 | (NonEmpty 11 | (NonEmpty Empty (3, "USA") Empty) 12 | (4, "China") 13 | (NonEmpty (NonEmpty Empty (6, "Germany") Empty) (7, "UK") Empty) 14 | ) 15 | -------------------------------------------------------------------------------- /test/fluid/slicing/map.expect.fld: -------------------------------------------------------------------------------- 1 | map ((+) 2) ⸨[⸩ 3 ⸨,⸩ 4] -------------------------------------------------------------------------------- /test/fluid/slicing/map.fld: -------------------------------------------------------------------------------- 1 | map ((+) 2) [3, 4] 2 | -------------------------------------------------------------------------------- /test/fluid/slicing/matrix-update.expect.fld: -------------------------------------------------------------------------------- 1 | let image' = [[15, 13, 6, 9, 16], [12, 5, 15, 4, 13], [14, 9, 20, 8, 1], [4, 10, 3, 7, 19], [3, 11, 15, 2, 9]]; 2 | image = [|nth2 i j image'|(i, j) in (5, 5)|]; 3 | index = (2, 2); 4 | pair = ⸨4000⸩ in 5 | matrixUpdate image index pair -------------------------------------------------------------------------------- /test/fluid/slicing/matrix-update.fld: -------------------------------------------------------------------------------- 1 | let image' = [[15, 13, 6, 9, 16], 2 | [12, 5, 15, 4, 13], 3 | [14, 9, 20, 8, 1], 4 | [ 4, 10, 3, 7, 19], 5 | [ 3, 11, 15, 2, 9]]; 6 | image = [| nth2 i j image' 7 | | (i, j) in (5, 5) |]; 8 | index = (2, 2); 9 | pair = 4000 10 | in matrixUpdate image index pair 11 | -------------------------------------------------------------------------------- /test/fluid/slicing/multiply.expect.fld: -------------------------------------------------------------------------------- 1 | 5 * (⸨0⸩ * 3) -------------------------------------------------------------------------------- /test/fluid/slicing/multiply.fld: -------------------------------------------------------------------------------- 1 | 5 * (0 * 3) 2 | -------------------------------------------------------------------------------- /test/fluid/slicing/nth.expect.fld: -------------------------------------------------------------------------------- 1 | nth 1 [3, ⸨4⸩, 5] -------------------------------------------------------------------------------- /test/fluid/slicing/nth.fld: -------------------------------------------------------------------------------- 1 | nth 1 [3, 4, 5] 2 | -------------------------------------------------------------------------------- /test/fluid/slicing/output-not-source.expect.fld: -------------------------------------------------------------------------------- 1 | let x = ⸨3⸩ in 2 | (x, x < ⸨5⸩) -------------------------------------------------------------------------------- /test/fluid/slicing/output-not-source.fld: -------------------------------------------------------------------------------- 1 | let x = 3 in (x, x < 5) 2 | -------------------------------------------------------------------------------- /test/fluid/slicing/qcut.expect.fld: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/explorable-viz/fluid/e42120612f714fe85676b28538ec3698cd9b2c58/test/fluid/slicing/qcut.expect.fld -------------------------------------------------------------------------------- /test/fluid/slicing/qcut.fld: -------------------------------------------------------------------------------- 1 | let data = mergesort [ x.changed | x <- ssp126] 2 | in qcut data [ 5, 50, 95 ] 3 | -------------------------------------------------------------------------------- /test/fluid/slicing/section-5-example-1.expect.fld: -------------------------------------------------------------------------------- 1 | let map f [] = []; 2 | map f (x : xs) = ⸨(f x : map f xs)⸩ in 3 | let data = [{energyType : "Bio", output : 6.2} 4 | , {energyType : ⸨"Hydro"⸩, output : 260} 5 | , {energyType : "Solar", output : 19.9} 6 | , {energyType : "Wind", output : 91} 7 | , {energyType : "Geo", output : 14.4} 8 | ]; 9 | output = ⸨[row.output|type <- [⸨"Hydro"⸩, "Solar", "Geo"], row <- data, row.energyType == type]⸩ in 10 | map (fun x = floor ((x / sum output) * 100)) output -------------------------------------------------------------------------------- /test/fluid/slicing/section-5-example-2.expect.fld: -------------------------------------------------------------------------------- 1 | let map f [] = []; 2 | map f (x : xs) = (f x : map f xs) in 3 | let data = [{energyType : "Bio", output : 6.2} 4 | , {energyType : "Hydro", output : ⸨260⸩} 5 | , {energyType : "Solar", output : ⸨19.9⸩} 6 | , {energyType : "Wind", output : 91} 7 | , {energyType : "Geo", output : ⸨14.4⸩} 8 | ]; 9 | output = [row.output|type <- ["Hydro", "Solar", "Geo"], row <- data, row.energyType == type] in 10 | map (fun x = floor ((x / sum output) * ⸨100⸩)) output -------------------------------------------------------------------------------- /test/fluid/slicing/section-5-example-3.expect.fld: -------------------------------------------------------------------------------- 1 | let map f [] = []; 2 | map f (x : xs) = ⸨(f x : map f xs)⸩ in 3 | let data = [{energyType : "Bio", output : 6.2} 4 | , {energyType : "Hydro", output : 260} 5 | , {energyType : "Solar", output : 19.9} 6 | , {energyType : "Wind", output : 91} 7 | , {energyType : ⸨"Geo"⸩, output : 14.4} 8 | ]; 9 | output = ⸨[row.output|type <- ["Hydro", "Solar", ⸨"Geo"⸩], row <- data, row.energyType == type]⸩ in 10 | map (fun x = floor ((x / sum output) * 100)) output -------------------------------------------------------------------------------- /test/fluid/slicing/section-5-example.fld: -------------------------------------------------------------------------------- 1 | let map f [] = []; 2 | map f (x : xs) = f x : map f xs; 3 | let data = [ 4 | { energyType: "Bio", output: 6.2 }, 5 | { energyType: "Hydro", output: 260 }, 6 | { energyType: "Solar", output: 19.9 }, 7 | { energyType: "Wind", output: 91 }, 8 | { energyType: "Geo", output: 14.4 } 9 | ]; 10 | output = [ 11 | row.output | type <- ["Hydro", "Solar", "Geo"], 12 | row <- data, row.energyType == type 13 | ] in 14 | map (fun x -> floor (x / sum output * 100)) output 15 | -------------------------------------------------------------------------------- /test/fluid/slicing/zeros-1.expect.fld: -------------------------------------------------------------------------------- 1 | let zeros [] = ⸨[]⸩; 2 | zeros (x : xs) = ⸨(0 : zeros xs)⸩ in 3 | zeros ⸨[⸩ 1, 2 ⸨]⸩ -------------------------------------------------------------------------------- /test/fluid/slicing/zeros-2.expect.fld: -------------------------------------------------------------------------------- 1 | let zeros [] = ⸨[]⸩; 2 | zeros (x : xs) = (0 : zeros xs) in 3 | zeros [1, 2 ⸨]⸩ -------------------------------------------------------------------------------- /test/fluid/slicing/zeros.fld: -------------------------------------------------------------------------------- 1 | let zeros [] = []; 2 | zeros (x : xs) = 0 : zeros xs 3 | in 4 | zeros [1, 2] 5 | -------------------------------------------------------------------------------- /test/fluid/slicing/zipWith-1.expect.fld: -------------------------------------------------------------------------------- 1 | let zipWith op [] ys = []; 2 | zipWith op (x : xs) [] = []; 3 | zipWith op (x : xs) (y : ys) = (op x y : zipWith op xs ys) in 4 | zipWith (fun x y = x ** ⸨2⸩ + y ** ⸨2⸩) [2, ⸨3⸩, 4] [3, ⸨4⸩, 5, 6] -------------------------------------------------------------------------------- /test/fluid/slicing/zipWith.fld: -------------------------------------------------------------------------------- 1 | let zipWith op [] ys = []; 2 | zipWith op (x : xs) [] = []; 3 | zipWith op (x : xs) (y : ys) = op x y : zipWith op xs ys 4 | in zipWith (fun x y -> x ** 2 + y ** 2) [2, 3, 4] [3, 4, 5, 6] 5 | -------------------------------------------------------------------------------- /vscode-notes.md: -------------------------------------------------------------------------------- 1 | - notification icon (bell in bottom-right corner) can give clues if things aren't working 2 | - ensure "Add NPM Path" is selected in VS Code PureScript settings (to add ./node_modules/.bin to path) 3 | -------------------------------------------------------------------------------- /website-test.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const serve = require('express-static'); 3 | require('http-shutdown').extend(); 4 | 5 | const app = express(); 6 | 7 | const root = process.cwd() + '/dist/' + process.argv[2]; 8 | app.use(serve(root)); 9 | 10 | const server = app.listen(8080, function() { 11 | console.log("Serving content from " + root); 12 | }).withShutdown(); 13 | 14 | (async () => { 15 | try { 16 | if (process.argv.length == 4) { 17 | module = process.cwd() + process.argv[3]; 18 | } else { 19 | module = root + '/test.mjs'; 20 | } 21 | console.log('Loading Puppeteer test module:', module); 22 | import(module).then(({ main }) => { 23 | main().then(serverDown); 24 | }).catch(err => { 25 | console.error("Failed to load PureScript output:", err); 26 | }); 27 | } catch (error) { 28 | console.error('Error:', error); 29 | } 30 | })(); 31 | 32 | function serverDown() { 33 | console.log('Shutting down server') 34 | server.shutdown(function(err) { 35 | if (err) { 36 | return console.log('shutdown failed', err.message); 37 | } 38 | console.log('Everything is cleanly shut down.'); 39 | }); 40 | } 41 | -------------------------------------------------------------------------------- /website/README.md: -------------------------------------------------------------------------------- 1 | # README 2 | 3 | - See script/build-website.sh and related scripts for how these folders are processed 4 | - To create a new website, emulate the structure of an existing one (the symlinks in particular) 5 | -------------------------------------------------------------------------------- /website/Test/FluidOrg/Convolution.purs: -------------------------------------------------------------------------------- 1 | module Website.Test.FluidOrg.Convolution where 2 | 3 | import Prelude hiding (absurd) 4 | 5 | import Control.Promise (Promise, fromAff) 6 | import Data.Foldable (sequence_) 7 | import Effect (Effect) 8 | import Effect.Aff (Aff) 9 | import Test.Util.Puppeteer (clickToggle, testURL, waitForFigure) 10 | import Toppokki as T 11 | 12 | main :: Effect (Promise Unit) 13 | main = fromAff $ sequence_ $ testURL "convolution" 14 | [ testFig 15 | ] 16 | 17 | testFig :: T.Page -> Aff Unit 18 | testFig page = do 19 | let figId = "fig" 20 | waitForFigure page (figId <> "-output") 21 | clickToggle page figId 22 | -------------------------------------------------------------------------------- /website/Test/Misc/EnergyScatter.purs: -------------------------------------------------------------------------------- 1 | module Website.Test.Misc.EnergyScatter where 2 | 3 | import Prelude 4 | 5 | import Control.Promise (Promise, fromAff) 6 | import Data.Foldable (sequence_) 7 | import Effect (Effect) 8 | import Effect.Aff (Aff) 9 | import Test.Util.Puppeteer (checkAttribute, checkAttributeContains, checkTextContent, click, clickToggle, testURL, waitFor, waitForFigure) 10 | import Toppokki as T 11 | 12 | main :: Effect (Promise Unit) 13 | main = fromAff $ sequence_ $ testURL "energy-scatter" 14 | [ testFig 15 | ] 16 | 17 | testFig :: T.Page -> Aff Unit 18 | testFig page = do 19 | waitForFigure page (fig <> "-output") 20 | clickToggle page fig 21 | clickScatterPlotPoint 22 | 23 | where 24 | fig = "fig" 25 | 26 | clickScatterPlotPoint :: Aff Unit 27 | clickScatterPlotPoint = do 28 | let point = T.Selector ("div#" <> fig <> " .scatterplot-point") 29 | waitFor point page 30 | click point page 31 | checkAttributeContains page point "class" "selected-primary-persistent" 32 | checkAttribute page point "r" "3.2" 33 | let caption = T.Selector ("table#" <> fig <> "-input-renewables > caption.table-caption") 34 | checkTextContent page caption "renewables (40 of 240)" 35 | -------------------------------------------------------------------------------- /website/Test/Misc/RenewablesLinked.purs: -------------------------------------------------------------------------------- 1 | module Website.Test.Misc.RenewablesLinked where 2 | 3 | import Prelude 4 | 5 | import Control.Promise (Promise, fromAff) 6 | import Data.Foldable (sequence_) 7 | import Effect (Effect) 8 | import Effect.Aff (Aff) 9 | import Test.Util.Puppeteer (checkAttribute, click, clickToggle, testURL, waitFor, waitForFigure) 10 | import Toppokki as T 11 | 12 | main :: Effect (Promise Unit) 13 | main = fromAff $ sequence_ $ testURL "renewables-linked" 14 | [ testFig 15 | ] 16 | 17 | testFig :: T.Page -> Aff Unit 18 | testFig page = do 19 | waitForFigure page barChart 20 | waitForFigure page lineChart 21 | checkXTicks 22 | checkPointRadius 23 | 24 | clickToggle page fig 25 | clickBarChart 26 | where 27 | fig = "fig" 28 | barChart = fig <> "-barChart" 29 | lineChart = fig <> "-lineChart" 30 | 31 | clickBarChart :: Aff Unit 32 | clickBarChart = do 33 | let bar = T.Selector ("svg#" <> barChart <> " rect.bar") 34 | waitFor bar page 35 | click bar page 36 | checkAttribute page bar "fill" "#57a157" 37 | 38 | checkXTicks :: Aff Unit 39 | checkXTicks = 40 | waitFor (T.Selector ("svg#" <> lineChart <> " g.x-axis")) page 41 | 42 | checkPointRadius :: Aff Unit 43 | checkPointRadius = do 44 | let point = T.Selector ("svg#" <> lineChart <> " circle.linechart-point") 45 | waitFor point page 46 | checkAttribute page point "r" "2.0" 47 | -------------------------------------------------------------------------------- /website/esop2025-artifact/css: -------------------------------------------------------------------------------- 1 | ../css -------------------------------------------------------------------------------- /website/esop2025-artifact/favicon.ico: -------------------------------------------------------------------------------- 1 | ../favicon.ico -------------------------------------------------------------------------------- /website/esop2025-artifact/fig2/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Fluid: Data-Linked Visualisations 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 |
16 |
17 |
18 |

Moving Average

19 |

Essentially a 1-dimensional convolution with a uniform (boxcar) filter. Click the grey toggle to reveal the underlying data set.

20 |
21 | 22 |
23 |

How are the year values used? 24 |
What about emissions? 25 |
(Mousing over type will be slow for now) 26 |

27 |
28 |
29 |
30 | 31 |
32 |
33 |
34 |
loading figure(s)
35 |
36 |
37 |
38 | 39 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /website/esop2025-artifact/fig2/spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "fluidSrcPath": ["../fluid"], 3 | "file": "moving-average", 4 | "imports": [], 5 | "inputs": ["methane"], 6 | "datasets": [["methane", "dataset/methane-emissions"]] 7 | } 8 | -------------------------------------------------------------------------------- /website/esop2025-artifact/fig4/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Fluid: Data-Linked Visualisations 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 |
16 |
17 |
18 |

Non-renewables energy output

19 |

Click the grey toggle and mouse over the bars. 20 |
21 | Clicking makes a selection “persistent”.

22 |
23 | 24 |
25 |
26 |

What about inputs? (Unused rows are hidden.)

27 |
28 |
29 | 30 |
31 |
32 |
33 |
loading figure(s)
34 |
35 |
36 |
37 | 38 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /website/esop2025-artifact/fig4/spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "fluidSrcPath": ["../fluid"], 3 | "file": "non-renewables", 4 | "imports": [], 5 | "inputs": ["nonRenewables"], 6 | "datasets": [["renewables", "dataset/renewables-new"], ["nonRenewables", "dataset/non-renewables"]] 7 | } 8 | -------------------------------------------------------------------------------- /website/esop2025-artifact/fluid/moving-average.fld: -------------------------------------------------------------------------------- 1 | let nthPad n xs = 2 | nth (min (max n 0) (length xs - 1)) xs; 3 | movingAvg ys window = 4 | [ sum [ nthPad n ys | n <- [ i - window .. i + window ] ] / (1 + 2 * window) 5 | | i <- [ 0 .. length ys - 1 ] ]; 6 | movingAvg' rs window = 7 | zipWith 8 | (fun x y -> {x: x, y: y}) 9 | (map (fun r -> r.x) rs) 10 | (movingAvg (map (fun r -> r.y) rs) window); 11 | let points = 12 | [ { x: r.year, y: r.emissions } | r <- methane, r.type == "Agriculture" ] 13 | in LineChart { 14 | tickLabels: { x: Rotated, y: Default }, 15 | size: { width: 330, height: 285 }, 16 | caption: "SSP5-8.5 projected methane emissions (Agriculture)", 17 | plots: [ LinePlot { name: "Moving average", points: movingAvg' points 1 }, 18 | LinePlot { name: "Original curve", points: points } ] 19 | } 20 | -------------------------------------------------------------------------------- /website/esop2025-artifact/fluid/non-renewables.fld: -------------------------------------------------------------------------------- 1 | let countries = ["BRA", "EGY", "IND", "JPN"]; 2 | let totalFor year country = 3 | let [ row ] = [ row | row <- nonRenewables, row.year == year, row.country == country ] 4 | in row.nuclearOut + row.gasOut + row.coalOut + row.petrolOut; 5 | let stack year = [ { y: country, z: totalFor year country } | country <- countries ]; 6 | let yearData year = [ row | row <- nonRenewables, row.year == year, row.country `elem` countries ] 7 | in MultiView { 8 | barChart: BarChart { 9 | caption: "Non-renewables output", 10 | size: { width: 275, height: 185 }, 11 | stackedBars: [ { x: numToStr year, bars: stack year } | year <- [2014..2018] ] 12 | }, 13 | scatterPlot: ScatterPlot { 14 | caption: "", 15 | points: [ { 16 | x: sum [ row.nuclearOut | row <- yearData year ], 17 | y: sum [ row.nuclearCap | row <- yearData year ] 18 | } | year <- [2014..2018] ], 19 | labels: { x: "Nuclear capacity", y: "Nuclear output" } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /website/esop2025-artifact/font: -------------------------------------------------------------------------------- 1 | ../font -------------------------------------------------------------------------------- /website/esop2025-artifact/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | ESOP 2025 Submission 89 Anonymised Web Demo 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |
16 |
17 |
18 |

ESOP 2025 Submission 89 Anonymised Web Demo

19 | 20 |

This is an anonymised reviewer artifact to support our ESOP 2025 submission. It will be documented 21 | in more detail for the formal Artifact Evaluation process.

22 | 23 |

Click on the following to see interactive versions of figures in the paper. (Each figure may take a 24 | few seconds to load.)

25 | 26 |

Figure 2

27 |

Figure 4

28 |
29 |
30 | 31 | 32 | -------------------------------------------------------------------------------- /website/esop2025-artifact/shared: -------------------------------------------------------------------------------- 1 | ../shared -------------------------------------------------------------------------------- /website/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/explorable-viz/fluid/e42120612f714fe85676b28538ec3698cd9b2c58/website/favicon.ico -------------------------------------------------------------------------------- /website/fluid-org/.well-known/atproto-did: -------------------------------------------------------------------------------- 1 | did:plc:cdnivlelderzbmo46wi7bok2 2 | -------------------------------------------------------------------------------- /website/fluid-org/convolution-wrapped/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Fluid: Data-Linked Visualisations 9 | 10 | 11 | 12 | 13 | 17 | 18 | 19 |
20 |
21 |
22 |
23 |

Contrast the previous example, which used zero as the 24 | boundary method, with this one, which uses wrap. Toggle the data 25 | pane again; then select an output cell near the edge. This implementation has a quite different 26 | behaviour: instead of some of the filter elements becoming irrelevant, we can see visually 27 | that the inputImage behaves as though opposite sides were connected. These examples show 28 | how just making the input-output relationships fine-grained rather than monolithic already reveals quite 29 | a lot about what a computation actually does.

30 |
31 |
32 |
33 |
34 |
35 |
36 | 37 |
38 |
39 |
40 |
loading figure(s)
41 |
42 |

What happens now at the corners of the output matrix?

43 |

previous..

44 |
45 |
46 | 47 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /website/fluid-org/convolution-wrapped/spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "fluidSrcPath": ["../fluid"], 3 | "file": "convolution/emboss-wrap", 4 | "imports": [ "lib/convolution", "convolution/test-image", "convolution/filter/emboss" ], 5 | "datasets": [], 6 | "inputs": [ "inputImage", "filter" ], 7 | "query": false 8 | } 9 | -------------------------------------------------------------------------------- /website/fluid-org/convolution/spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "fluidSrcPath": ["../fluid"], 3 | "file": "convolution/emboss", 4 | "imports": [ "lib/convolution", "convolution/test-image", "convolution/filter/emboss" ], 5 | "datasets": [], 6 | "inputs": [ "inputImage", "filter" ], 7 | "query": false 8 | } 9 | -------------------------------------------------------------------------------- /website/fluid-org/css: -------------------------------------------------------------------------------- 1 | ../css -------------------------------------------------------------------------------- /website/fluid-org/favicon.ico: -------------------------------------------------------------------------------- 1 | ../favicon.ico -------------------------------------------------------------------------------- /website/fluid-org/fluid-poster.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/explorable-viz/fluid/e42120612f714fe85676b28538ec3698cd9b2c58/website/fluid-org/fluid-poster.pdf -------------------------------------------------------------------------------- /website/fluid-org/fluid/convolution/emboss-wrap.fld: -------------------------------------------------------------------------------- 1 | convolve inputImage filter wrap 2 | -------------------------------------------------------------------------------- /website/fluid-org/fluid/convolution/emboss.fld: -------------------------------------------------------------------------------- 1 | convolve inputImage filter zero 2 | -------------------------------------------------------------------------------- /website/fluid-org/fluid/convolution/filter/emboss.fld: -------------------------------------------------------------------------------- 1 | let filter = 2 | let emboss = [[-2, -1, 0], 3 | [-1, 1, 1], 4 | [ 0, 1, 2]] in 5 | [| nth2 i j emboss | (i, j) in (3, 3) |]; 6 | -------------------------------------------------------------------------------- /website/fluid-org/fluid/convolution/test-image.fld: -------------------------------------------------------------------------------- 1 | let inputImage = 2 | let image = [[15, 13, 6, 9, 16], 3 | [12, 5, 15, 4, 13], 4 | [14, 9, 20, 8, 1], 5 | [ 4, 10, 3, 7, 19], 6 | [ 3, 11, 15, 2, 9]] in 7 | [| nth2 i j image | (i, j) in (5, 5) |]; 8 | -------------------------------------------------------------------------------- /website/fluid-org/fluid/moving-average.fld: -------------------------------------------------------------------------------- 1 | let nthPad n xs = 2 | nth (min (max n 0) (length xs - 1)) xs; 3 | movingAvg ys window = 4 | [ sum [ nthPad n ys | n <- [ i - window .. i + window ] ] / (1 + 2 * window) 5 | | i <- [ 0 .. length ys - 1 ] ]; 6 | movingAvg' rs window = 7 | zipWith 8 | (fun x y -> {x: x, y: y}) 9 | (map (fun r -> r.x) rs) 10 | (movingAvg (map (fun r -> r.y) rs) window); 11 | let points = 12 | [ { x: r.year, y: r.emissions } | r <- methane, r.type == "Agriculture" ] 13 | in LineChart { 14 | tickLabels: { x: Rotated, y: Default }, 15 | size: { width: 330, height: 285 }, 16 | caption: "SSP5-8.5 projected methane emissions (Agriculture)", 17 | plots: [ LinePlot { name: "Moving average", points: movingAvg' points 1 }, 18 | LinePlot { name: "Original curve", points: points } ] 19 | } 20 | -------------------------------------------------------------------------------- /website/fluid-org/fluid/non-renewables.fld: -------------------------------------------------------------------------------- 1 | let countries = ["BRA", "EGY", "IND", "JPN"]; 2 | let totalFor year country = 3 | let [ row ] = [ row | row <- nonRenewables, row.year == year, row.country == country ] 4 | in row.nuclearOut + row.gasOut + row.coalOut + row.petrolOut; 5 | let stack year = [ { y: country, z: totalFor year country } | country <- countries ]; 6 | let yearData year = [ row | row <- nonRenewables, row.year == year, row.country `elem` countries ] 7 | in MultiView { 8 | barChart: BarChart { 9 | caption: "Non-renewables output", 10 | size: { width: 275, height: 185 }, 11 | stackedBars: [ { x: numToStr year, bars: stack year } | year <- [2014..2018] ] 12 | }, 13 | scatterPlot: ScatterPlot { 14 | caption: "", 15 | points: [ { 16 | x: sum [ row.nuclearOut | row <- yearData year ], 17 | y: sum [ row.nuclearCap | row <- yearData year ] 18 | } | year <- [2014..2018] ], 19 | labels: { x: "Nuclear capacity", y: "Nuclear output" } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /website/fluid-org/font: -------------------------------------------------------------------------------- 1 | ../font -------------------------------------------------------------------------------- /website/fluid-org/image/fluid-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/explorable-viz/fluid/e42120612f714fe85676b28538ec3698cd9b2c58/website/fluid-org/image/fluid-logo.png -------------------------------------------------------------------------------- /website/fluid-org/image/iccs-full-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/explorable-viz/fluid/e42120612f714fe85676b28538ec3698cd9b2c58/website/fluid-org/image/iccs-full-logo.png -------------------------------------------------------------------------------- /website/fluid-org/image/schmidtsciences_primary_color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/explorable-viz/fluid/e42120612f714fe85676b28538ec3698cd9b2c58/website/fluid-org/image/schmidtsciences_primary_color.png -------------------------------------------------------------------------------- /website/fluid-org/moving-average/spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "fluidSrcPath": ["../fluid"], 3 | "file": "moving-average", 4 | "imports": [], 5 | "datasets": [["methane", "dataset/methane-emissions"]], 6 | "inputs": ["methane"], 7 | "query": false 8 | } 9 | -------------------------------------------------------------------------------- /website/fluid-org/pdf/Turing-2023.09.19.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/explorable-viz/fluid/e42120612f714fe85676b28538ec3698cd9b2c58/website/fluid-org/pdf/Turing-2023.09.19.pdf -------------------------------------------------------------------------------- /website/fluid-org/shared: -------------------------------------------------------------------------------- 1 | ../shared -------------------------------------------------------------------------------- /website/fluid-org/spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "fluidSrcPath": ["fluid"], 3 | "datasets": [["renewables", "dataset/renewables-new"], ["nonRenewables", "dataset/non-renewables"]], 4 | "imports": [], 5 | "file": "non-renewables", 6 | "inputs": ["nonRenewables"], 7 | "query": false 8 | } 9 | -------------------------------------------------------------------------------- /website/font/GraphikLight.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/explorable-viz/fluid/e42120612f714fe85676b28538ec3698cd9b2c58/website/font/GraphikLight.woff2 -------------------------------------------------------------------------------- /website/font/GraphikLightItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/explorable-viz/fluid/e42120612f714fe85676b28538ec3698cd9b2c58/website/font/GraphikLightItalic.woff2 -------------------------------------------------------------------------------- /website/font/GraphikMedium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/explorable-viz/fluid/e42120612f714fe85676b28538ec3698cd9b2c58/website/font/GraphikMedium.woff2 -------------------------------------------------------------------------------- /website/font/GraphikMediumItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/explorable-viz/fluid/e42120612f714fe85676b28538ec3698cd9b2c58/website/font/GraphikMediumItalic.woff2 -------------------------------------------------------------------------------- /website/font/OdiseanTech.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/explorable-viz/fluid/e42120612f714fe85676b28538ec3698cd9b2c58/website/font/OdiseanTech.woff2 -------------------------------------------------------------------------------- /website/literate-execution/ar6-spm/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Fluid: Data-Linked Visualisations 9 | 10 | 11 | 12 | 13 | 18 | 19 | 20 |
21 |
22 |
23 |
24 |

Figure SPM.4b

25 |

Recreating the bar plots from IPCC AR6 Box SPM.4b

26 |
27 | 28 |
29 |
30 |
31 |
32 | 33 |
34 |
35 |
loading figure(s)
36 |
37 |
38 | figure-spm-4.fld 39 |
40 |
41 |
42 | 43 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /website/literate-execution/ar6-spm/spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "fluidSrcPath": ["../fluid"], 3 | "datasets": [["likelihoods", "ar6-spm/likelihoods"], ["ssp126Source", "ar6-spm/ssp126"], ["ssp126preds", "ar6-spm/ssp126preds"], ["ssp245Source", "ar6-spm/ssp245"], ["ssp245preds", "ar6-spm/ssp245preds"]], 4 | "imports": [ "lib/stats" ], 5 | "file": "ar6-spm/figure-spm-4", 6 | "inputs": [ "ssp126Source", "ssp245Source", "ssp126preds", "ssp245preds" ], 7 | "query": false 8 | } 9 | -------------------------------------------------------------------------------- /website/literate-execution/convolution/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Fluid: Data-Linked Visualisations 9 | 10 | 11 | 12 | 13 | 54 | 55 | 56 |
57 |
58 |
59 |
60 |

Matrix convolution

61 |
62 | 63 |
64 |
65 |
66 |
67 |
68 | 69 |
70 |
71 |
72 |
73 |
loading figure(s)
74 |
75 |
76 |
77 |
78 | convolution.fld 79 |
80 |
81 |
82 |
83 | 84 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /website/literate-execution/convolution/spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "fluidSrcPath": ["../fluid"], 3 | "file": "convolution/emboss", 4 | "imports": [ "lib/convolution", "convolution/test-image", "convolution/filter/emboss" ], 5 | "datasets": [], 6 | "inputs": [ "inputImage", "filter" ], 7 | "query": true 8 | } 9 | -------------------------------------------------------------------------------- /website/literate-execution/css: -------------------------------------------------------------------------------- 1 | ../css -------------------------------------------------------------------------------- /website/literate-execution/favicon.ico: -------------------------------------------------------------------------------- 1 | ../favicon.ico -------------------------------------------------------------------------------- /website/literate-execution/fluid/ar6-spm/figure-spm-4.fld: -------------------------------------------------------------------------------- 1 | let stackedBarHeight stackedBar = sum [ bar.z | bar <- stackedBar.bars ]; 2 | 3 | let get x (BarChart record) = fromSome (findWithKey "x" x record.stackedBars); 4 | 5 | let mkBarChart scenName table = 6 | BarChart { 7 | caption: "Example bar chart for scenario " ++ scenName, 8 | size: { width: 275, height: 185 }, 9 | stackedBars: map (fun record -> { x: record.type, bars: [ { y: "emissions", z: record.emissions } ]}) table 10 | }; 11 | 12 | let getHeight offset bar = (head bar.bars).z + offset; 13 | 14 | let changed126 = map (fun r -> r.changed) ssp126preds; 15 | changed245 = map (fun r -> r.changed) ssp245preds; 16 | ssp126table = 17 | """ 18 | We compute the ${this.type} emissions by taking the median of predicted values, 19 | which are distributed according to ${mkHistogram changed126 this.type 10} 20 | """ 21 | { type: "Total", emissions: findPercentile 50 changed126 } : ssp126Source; 22 | ssp245table = 23 | """ 24 | We build this table analagously to the ssp126 25 | """ 26 | { type: "Total", emissions: findPercentile 50 changed245 } : ssp245Source; 27 | ssp126 = mkBarChart "SSP1-2.6" ssp126table; 28 | ssp245 = mkBarChart "SSP2-4.5" ssp245table; 29 | totals = map stackedBarHeight (map (get "Total") [ssp126, ssp245]); 30 | co2s = map stackedBarHeight (map (get "CO2") [ssp126, ssp245]); 31 | nonco2 = map (get "Non-CO2") [ssp126, ssp245] 32 | in MultiView { 33 | leftBarChart: ssp126, 34 | rightBarChart: ssp245, 35 | explanation: 36 | Paragraph [ Text "Within each scenario bar plot, the bars represent:", Link totals " total warming (°C),", 37 | Link co2s " warming from CO2 (°C) ", Text " and from ", Link (map stackedBarHeight nonco2) " Non-CO2 GHG's (°C)." 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /website/literate-execution/fluid/ar6-spm/likelihoods.fld: -------------------------------------------------------------------------------- 1 | [ 2 | { prob: 0.99, msg: "virtually certain" }, 3 | { prob: 0.9, msg: "very likely"}, 4 | { prob: 0.66, msg: "likely"}, 5 | { prob: 0.33, msg: "about as likely as not"}, 6 | { prob: 0.1, msg: "unlikely"}, 7 | { prob: 0.01, msg: "very unlikely"}, 8 | { prob: 0.0, msg: "exceptionally unlikely"} 9 | ] 10 | -------------------------------------------------------------------------------- /website/literate-execution/fluid/ar6-spm/ssp126.fld: -------------------------------------------------------------------------------- 1 | [ 2 | { type: "CO2", emissions: 1.4 }, 3 | { type: "Non-CO2", emissions: 0.41 } 4 | ] 5 | -------------------------------------------------------------------------------- /website/literate-execution/fluid/ar6-spm/ssp126preds.fld: -------------------------------------------------------------------------------- 1 | [ 2 | { model: "NorESM2-LM", period: "2081-2100", world: 15.76, world_historical: 14.5, changed: 1.01 }, 3 | { model: "NorESM2-MM", period: "2081-2100", world: 15.22, world_historical: 13.92, changed: 1.05 }, 4 | { model: "GFDL-ESM4", period: "2081-2100", world: 14.82, world_historical: 13.5, changed: 1.07 }, 5 | { model: "CAMS-CSM1-0", period: "2081-2100", world: 15.13, world_historical: 13.8, changed: 1.09 }, 6 | { model: "BCC-CSM2-MR", period: "2081-2100", world: 16.29, world_historical: 14.82, changed: 1.22 }, 7 | { model: "INM-CM4-8", period: "2081-2100", world: 14.79, world_historical: 13.31, changed: 1.23 }, 8 | { model: "FGOALS-g3", period: "2081-2100", world: 14.3, world_historical: 12.81, changed: 1.24 }, 9 | { model: "MPI-ESM1-2-LR", period: "2081-2100", world: 15.03, world_historical: 13.54, changed: 1.24 }, 10 | { model: "INM-CM5-0", period: "2081-2100", world: 14.64, world_historical: 13.14, changed: 1.25 }, 11 | { model: "MPI-ESM1-2-HR", period: "2081-2100", world: 15.51, world_historical: 13.94, changed: 1.32 }, 12 | { model: "IITM-ESM", period: "2081-2100", world: 15.56, world_historical: 13.99, changed: 1.32 }, 13 | { model: "MIROC6", period: "2081-2100", world: 16.72, world_historical: 15.12, changed: 1.35 }, 14 | { model: "MIROC-ES2L", period: "2081-2100", world: 16.6, world_historical: 14.96, changed: 1.39 }, 15 | { model: "MRI-ESM2-0", period: "2081-2100", world: 15.47, world_historical: 13.74, changed: 1.47 }, 16 | { model: "KIOST-ESM", period: "2081-2100", world: 14.22, world_historical: 12.4, changed: 1.57 }, 17 | { model: "AWI-CM-1-1-MR", period: "2081-2100", world: 15.72, world_historical: 13.76, changed: 1.72 }, 18 | { model: "NESM3", period: "2081-2100", world: 15.64, world_historical: 13.66, changed: 1.73 }, 19 | { model: "ACCESS-ESM1-5", period: "2081-2100", world: 16.54, world_historical: 14.54, changed: 1.75 }, 20 | { model: "CNRM-ESM2-1", period: "2081-2100", world: 15.65, world_historical: 13.64, changed: 1.76 }, 21 | { model: "EC-Earth3", period: "2081-2100", world: 16.02, world_historical: 13.94, changed: 1.83 }, 22 | { model: "CNRM-CM6-1", period: "2081-2100", world: 15, world_historical: 12.88, changed: 1.87 }, 23 | { model: "CESM2", period: "2081-2100", world: 16.21, world_historical: 14.02, changed: 1.94 }, 24 | { model: "IPSL-CM6A-LR", period: "2081-2100", world: 15.17, world_historical: 12.88, changed: 2.04 }, 25 | { model: "CESM2-WACCM", period: "2081-2100", world: 16.23, world_historical: 13.84, changed: 2.14 }, 26 | { model: "ACCESS-CM2", period: "2081-2100", world: 16.27, world_historical: 13.84, changed: 2.18 }, 27 | { model: "UKESM1-0-LL", period: "2081-2100", world: 16.06, world_historical: 13.45, changed: 2.36 }, 28 | { model: "EC-Earth3-Veg", period: "2081-2100", world: 16.15, world_historical: 13.53, changed: 2.37 }, 29 | { model: "HadGEM3-GC31-LL", period: "2081-2100", world: 16.34, world_historical: 13.71, changed: 2.38 }, 30 | { model: "CMCC-CM2-SR5", period: "2081-2100", world: 16.87, world_historical: 14.1, changed: 2.52 }, 31 | { model: "CNRM-CM6-1-HR", period: "2081-2100", world: 15.12, world_historical: 12.32, changed: 2.54 }, 32 | { model: "CanESM5", period: "2081-2100", world: 16.39, world_historical: 13.54, changed: 2.61 }, 33 | { model: "KACE-1-0-G", period: "2081-2100", world: 16.47, world_historical: 13.55, changed: 2.67 } 34 | ] 35 | -------------------------------------------------------------------------------- /website/literate-execution/fluid/ar6-spm/ssp245.fld: -------------------------------------------------------------------------------- 1 | [ 2 | { type: "CO2", emissions: 2.1 }, 3 | { type: "Non-CO2", emissions: 0.6 } 4 | ] 5 | -------------------------------------------------------------------------------- /website/literate-execution/fluid/convolution/emboss-wrap.fld: -------------------------------------------------------------------------------- 1 | convolve inputImage filter wrap 2 | -------------------------------------------------------------------------------- /website/literate-execution/fluid/convolution/emboss.fld: -------------------------------------------------------------------------------- 1 | convolve inputImage filter zero 2 | -------------------------------------------------------------------------------- /website/literate-execution/fluid/convolution/filter/emboss.fld: -------------------------------------------------------------------------------- 1 | let filter = 2 | let emboss = [[-2, -1, 0], 3 | [-1, 1, 1], 4 | [ 0, 1, 2]] in 5 | [| nth2 i j emboss | (i, j) in (3, 3) |]; 6 | -------------------------------------------------------------------------------- /website/literate-execution/fluid/convolution/test-image.fld: -------------------------------------------------------------------------------- 1 | let inputImage = 2 | let image = [[15, 13, 6, 9, 16], 3 | [12, 5, 15, 4, 13], 4 | [14, 9, 20, 8, 1], 5 | [ 4, 10, 3, 7, 19], 6 | [ 3, 11, 15, 2, 9]] in 7 | [| nth2 i j image | (i, j) in (5, 5) |]; 8 | -------------------------------------------------------------------------------- /website/literate-execution/font: -------------------------------------------------------------------------------- 1 | ../font -------------------------------------------------------------------------------- /website/literate-execution/image: -------------------------------------------------------------------------------- 1 | ../fluid-org/image -------------------------------------------------------------------------------- /website/literate-execution/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | ESOP 2025 Submission 89 Anonymised Web Demo 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |
16 |
17 |
18 |

Literate Execution Web Demo

19 | 20 |

This is a web demo of the currently WIP literate execution implementation work.

21 | 22 |

Click on the following to see the WIP examples (Each figure may take a 23 | few seconds to load.)

24 | 25 |

Convolution

26 |

Ar6-SPM (WIP)

27 |
28 |
29 | 30 | 31 | -------------------------------------------------------------------------------- /website/literate-execution/shared: -------------------------------------------------------------------------------- 1 | ../shared -------------------------------------------------------------------------------- /website/misc/css: -------------------------------------------------------------------------------- 1 | ../css -------------------------------------------------------------------------------- /website/misc/energy-scatter/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Fluid: Data-Linked Visualisations 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 |
18 |
19 |

Clean energy efficiency scatter plot

20 |
21 | 22 |
23 |
24 |
25 |
26 | 27 |
28 |
29 |
30 |
loading figure(s)
31 |
32 |
33 |
34 | 35 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /website/misc/energy-scatter/spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "fluidSrcPath": ["../fluid"], 3 | "datasets": [["renewables", "dataset/renewables-new"], ["nonRenewables", "dataset/non-renewables"]], 4 | "imports": [], 5 | "file": "energyscatter", 6 | "inputs": [ "renewables", "nonRenewables" ], 7 | "query": false 8 | } 9 | -------------------------------------------------------------------------------- /website/misc/favicon.ico: -------------------------------------------------------------------------------- 1 | ../favicon.ico -------------------------------------------------------------------------------- /website/misc/fluid/bar-chart-line-chart.fld: -------------------------------------------------------------------------------- 1 | MultiView { 2 | barChart: 3 | let totalFor c rows = 4 | sum [ row.output | row <- rows, row.country == c ]; 5 | let data2015 = [ row | row <- renewables, row.year == 2015 ]; 6 | countryData = [ { x: c, bars: [ { y: "output", z: totalFor c data2015 } ] } 7 | | c <- ["China", "USA", "Germany"] ] 8 | in BarChart { 9 | caption: "Total output by country", 10 | size: { width: 275, height: 185 }, 11 | stackedBars: countryData 12 | }, 13 | lineChart: 14 | let series type country = [ 15 | { x: row.year, y: row.output } 16 | | year <- [2013..2018], row <- renewables, 17 | row.year == year, row.energyType == type, row.country == country 18 | ] in LineChart { 19 | tickLabels: { x: Default, y: Default }, 20 | size: { width: 330, height: 285 }, 21 | caption: "Output of USA relative to China", 22 | plots: [ 23 | LinePlot { name: type, points: plot } 24 | | type <- ["Bio", "Hydro", "Solar", "Wind"], 25 | let plot = zipWith (fun p1 p2 -> { x: p1.x, y: p1.y / p2.y }) 26 | (series type "USA") (series type "China") 27 | ] 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /website/misc/fluid/energyscatter.fld: -------------------------------------------------------------------------------- 1 | let isCountry name x = name == x.country; 2 | isYear year x = year == x.year; 3 | 4 | let plot year countries = 5 | let rens = filter (isYear year) renewables; 6 | nonRens = filter (isYear year) nonRenewables; 7 | let plotCountry country = 8 | let rens' = filter (isCountry country) rens; 9 | rensOut = sum (map (fun x -> x.output) rens'); 10 | rensCap = sum (map (fun x -> x.capacity) rens'); 11 | x = head (filter (isCountry country) nonRens); 12 | nonRensCap = x.nuclearCap + x.petrolCap + x.gasCap + x.coalCap 13 | in { 14 | x: rensCap / (rensCap + nonRensCap), 15 | y: (rensOut + x.nuclearOut) / (rensCap + x.nuclearCap) 16 | } 17 | in map plotCountry countries 18 | 19 | in ScatterPlot { 20 | caption: "Clean energy efficiency vs. proportion of renewable energy capacity", 21 | points: plot 2018 [ "BRA", "CHN", "DEU", "FRA", "EGY", "IND", "JPN", "MEX", "NGA", "USA" ], 22 | labels: { x: "Renewables/TotalEnergyCap", y: "Clean Capacity Factor" } 23 | } 24 | -------------------------------------------------------------------------------- /website/misc/fluid/line-chart.fld: -------------------------------------------------------------------------------- 1 | let series type country = [ 2 | { x: row.year, y: row.output } 3 | | year <- [2013..2018], row <- renewables, 4 | row.year == year, row.energyType == type, row.country == country 5 | ] in LineChart { 6 | tickLabels: { x: Default, y: Default }, 7 | caption: "Change in renewable energy output of USA relative to China", 8 | size: { width: 330, height: 285 }, 9 | plots: [ 10 | LinePlot { name: type, points: plot } 11 | | type <- ["Bio", "Hydro", "Solar", "Wind"], 12 | let plot = zipWith (fun p1 p2 -> { x: p1.x, y: p1.y / p2.y }) 13 | (series type "USA") (series type "China") 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /website/misc/fluid/methane.fld: -------------------------------------------------------------------------------- 1 | let series type = [ 2 | { x: row.year, y: row.emissions } 3 | | row <- methane, row.type == type 4 | ] in LineChart { 5 | size: { width: 450, height: 285 }, 6 | tickLabels: { x: Rotated, y: Default }, 7 | caption: "Sources of methane emissions for Africa and the Middle East (IPCC AR6)", 8 | plots: [ 9 | LinePlot { name: type, points: series type } 10 | | type <- ["Agricultural Waste Burning", "Agriculture", "Energy Sector", 11 | "Forest Burning", "Grassland Burning", "Industrial Sector", 12 | "Residential Commercial Other", "Transportation Sector", "Waste"] 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /website/misc/fluid/moving-average.fld: -------------------------------------------------------------------------------- 1 | let nthPad n xs = 2 | nth (min (max n 0) (length xs - 1)) xs; 3 | movingAvg ys window = 4 | [ sum [ nthPad n ys | n <- [ i - window .. i + window ] ] / (1 + 2 * window) 5 | | i <- [ 0 .. length ys - 1 ] ]; 6 | movingAvg' rs window = 7 | zipWith 8 | (fun x y -> {x: x, y: y}) 9 | (map (fun r -> r.x) rs) 10 | (movingAvg (map (fun r -> r.y) rs) window); 11 | let points = 12 | [ { x: r.year, y: r.emissions } | r <- methane, r.type == "Agriculture" ] 13 | in LineChart { 14 | tickLabels: { x: Rotated, y: Default }, 15 | size: { width: 330, height: 285 }, 16 | caption: "SSP5-8.5 projected methane emissions (Agriculture)", 17 | plots: [ LinePlot { name: "Moving average", points: movingAvg' points 1 }, 18 | LinePlot { name: "Original curve", points: points } ] 19 | } 20 | -------------------------------------------------------------------------------- /website/misc/fluid/non-renewables.fld: -------------------------------------------------------------------------------- 1 | let countries = ["BRA", "EGY", "IND", "JPN"]; 2 | let totalFor year country = 3 | let [ row ] = [ row | row <- nonRenewables, row.year == year, row.country == country ] 4 | in row.nuclearOut + row.gasOut + row.coalOut + row.petrolOut; 5 | let stack year = [ { y: country, z: totalFor year country } | country <- countries ]; 6 | let yearData year = [ row | row <- nonRenewables, row.year == year, row.country `elem` countries ] 7 | in MultiView { 8 | barChart: BarChart { 9 | caption: "Non-renewables output", 10 | size: { width: 275, height: 185 }, 11 | stackedBars: [ { x: numToStr year, bars: stack year } | year <- [2014..2018] ] 12 | }, 13 | scatterPlot: ScatterPlot { 14 | caption: "", 15 | points: [ { 16 | x: sum [ row.nuclearOut | row <- yearData year ], 17 | y: sum [ row.nuclearCap | row <- yearData year ] 18 | } | year <- [2014..2018] ], 19 | labels: { x: "Nuclear capacity", y: "Nuclear output" } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /website/misc/font: -------------------------------------------------------------------------------- 1 | ../font -------------------------------------------------------------------------------- /website/misc/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Fluid: Data-Linked Visualisations 9 | 10 | 11 | 12 | 13 | 14 |
15 |
16 |
17 |
18 |
19 |

Miscellaneous examples

20 |

See subfolders for individual pages

21 |
22 |
23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /website/misc/methane/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Fluid: Data-Linked Visualisations 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 |
18 |
19 |

Methane

20 |

Methane emissions example

21 |
22 | 23 |
24 |
25 |
26 |
27 | 28 |
29 |
30 |
loading figure(s)
31 |
32 |
33 | 34 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /website/misc/methane/spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "fluidSrcPath": ["../fluid"], 3 | "datasets": [["methane", "dataset/methane-emissions"]], 4 | "imports": [], 5 | "file": "methane", 6 | "inputs": [ "methane" ], 7 | "query": false 8 | } 9 | -------------------------------------------------------------------------------- /website/misc/non-renewables/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Fluid: Data-Linked Visualisations 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 |
18 |
19 |

Non-renewables energy output

20 |

Click the grey toggle and mouse over the bars. 21 |
22 | Clicking makes a selection “persistent”.

23 |
24 | 25 |
26 |
27 |

What about inputs? (Unused rows are hidden.)

28 |
29 |
30 | 31 |
32 |
33 |
34 |
loading figure(s)
35 |
36 |
37 |
38 | 39 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /website/misc/non-renewables/spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "fluidSrcPath": ["../fluid"], 3 | "datasets": [["renewables", "dataset/renewables-new"], ["nonRenewables", "dataset/non-renewables"]], 4 | "imports": [], 5 | "file": "non-renewables", 6 | "inputs": ["nonRenewables"], 7 | "query": false 8 | } 9 | -------------------------------------------------------------------------------- /website/misc/renewables-linked/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Fluid: Data-Linked Visualisations 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 |
18 |
19 |

Title

20 |

Explain your figure here

21 |
22 | 23 |
24 |
25 |
26 |
27 | 28 |
29 |
30 |
31 |
loading figure(s)
32 |
33 |

Source code:

34 |
35 | bar-chart-line-chart.fld 36 |
37 |
38 |
39 | renewables.fld 40 |
41 |
42 |
43 |
44 | 45 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /website/misc/renewables-linked/spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "fluidSrcPath": ["../fluid"], 3 | "datasets": [["renewables", "dataset/renewables"]], 4 | "imports": [], 5 | "file": "bar-chart-line-chart", 6 | "inputs": ["renewables"], 7 | "query": false 8 | } 9 | -------------------------------------------------------------------------------- /website/misc/shared: -------------------------------------------------------------------------------- 1 | ../shared -------------------------------------------------------------------------------- /website/shared/footer.html: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /website/shared/header.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |

explorable, self-explanatory research outputs

5 |
6 |
7 | 17 | 25 |
26 |
27 |
28 | -------------------------------------------------------------------------------- /website/shared/sub-header.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

Overview

5 | 12 |
13 | -------------------------------------------------------------------------------- /website/shared/util.js: -------------------------------------------------------------------------------- 1 | function loadHeader () { 2 | fetch('/shared/header.html') 3 | .then(response => response.text()) 4 | .then(data => { 5 | const header = document.createElement('div') 6 | header.innerHTML = data 7 | activateCurrentLink(header) 8 | const divElement = header.children[0] 9 | const grid = document.getElementById('grid') 10 | grid.parentNode.insertBefore(divElement, grid) 11 | }) 12 | .catch(error => console.error('Error loading shared HTML:', error)) 13 | 14 | fetch('/shared/footer.html') 15 | .then(response => response.text()) 16 | .then(data => { 17 | const footer = document.createElement('div') 18 | footer.innerHTML = data 19 | const divElement = footer.children[0] 20 | const grid = document.getElementById('grid') 21 | grid.parentNode.appendChild(divElement) 22 | }) 23 | .catch(error => console.error('Error loading shared HTML:', error)) 24 | } 25 | 26 | function eqPaths (path1, path2) { 27 | const trailingSlashes = /\/+$/ 28 | return path1.replace(trailingSlashes, '') === path2.replace(trailingSlashes, '') 29 | } 30 | 31 | function activateCurrentLink (el) { 32 | const listItems = el.querySelectorAll('li') 33 | const n_ = Array.from(listItems).findIndex(li => { 34 | const link = li.querySelector('a') 35 | return link && eqPaths(link.getAttribute('href'), window.location.pathname) 36 | }) 37 | if (n_ !== -1) { 38 | listItems[n_].classList.add('active-page') 39 | } 40 | } 41 | 42 | function loadSubHeader () { 43 | fetch('/shared/sub-header.html') 44 | .then(response => response.text()) 45 | .then(data => { 46 | const header = document.createElement('div') 47 | header.innerHTML = data 48 | activateCurrentLink(header) 49 | const divElements = header.children 50 | const grid = document.getElementById('grid') 51 | for (let i = Math.min(4, divElements.length - 1); i >= 0; --i) { 52 | grid.insertBefore(divElements[i], grid.firstChild) 53 | } 54 | }) 55 | .catch(error => console.error('Error loading shared HTML:', error)) 56 | } 57 | 58 | function toggleDataPane(gridId) { 59 | const grid = document.getElementById(gridId) 60 | const hidden = grid.classList.contains('data-pane-hidden') 61 | const dataPaneButton = document.querySelector('.data-pane-button') 62 | 63 | if (hidden) { 64 | grid.classList.remove('data-pane-hidden') 65 | dataPaneButton.classList.remove('fa-eye-slash') 66 | dataPaneButton.classList.add('fa-eye') 67 | } else { 68 | grid.classList.add('data-pane-hidden') 69 | dataPaneButton.classList.remove('fa-eye') 70 | dataPaneButton.classList.add('fa-eye-slash') 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /website/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Fluid: Data-Linked Visualisations 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 |
18 |
19 |

Title

20 |

Explain your figure here

21 |
22 | 23 |
24 |
25 | 26 |
27 |
28 |
loading figure(s)
29 |
30 |
31 | 32 | 33 | 34 | 35 | --------------------------------------------------------------------------------