From 7c6526de9196266b0ab599a6e58390cfe53ea7b6 Mon Sep 17 00:00:00 2001 From: SIDANWhatever Date: Wed, 4 Sep 2024 19:25:31 +0800 Subject: [PATCH] feat: complete examples --- example-web/aiken/README.md | 55 + example-web/aiken/aiken.lock | 26 + example-web/aiken/aiken.toml | 21 + example-web/aiken/build/aiken-compile.lock | 0 .../packages/aiken-lang-stdlib/.editorconfig | 9 + .../packages/aiken-lang-stdlib/.gitattributes | 2 + .../workflows/continuous-integration.yml | 64 + .../packages/aiken-lang-stdlib/.gitignore | 3 + .../packages/aiken-lang-stdlib/CHANGELOG.md | 185 +++ .../build/packages/aiken-lang-stdlib/LICENSE | 201 +++ .../packages/aiken-lang-stdlib/README.md | 62 + .../packages/aiken-lang-stdlib/aiken.lock | 7 + .../packages/aiken-lang-stdlib/aiken.toml | 11 + .../aiken-lang-stdlib/lib/aiken/bytearray.ak | 529 +++++++ .../aiken-lang-stdlib/lib/aiken/cbor.ak | 354 +++++ .../aiken-lang-stdlib/lib/aiken/dict.ak | 1117 ++++++++++++++ .../aiken-lang-stdlib/lib/aiken/hash.ak | 83 + .../aiken-lang-stdlib/lib/aiken/int.ak | 80 + .../aiken-lang-stdlib/lib/aiken/interval.ak | 680 +++++++++ .../aiken-lang-stdlib/lib/aiken/list.ak | 1337 +++++++++++++++++ .../aiken-lang-stdlib/lib/aiken/math.ak | 372 +++++ .../lib/aiken/math/rational.ak | 795 ++++++++++ .../aiken-lang-stdlib/lib/aiken/option.ak | 301 ++++ .../aiken-lang-stdlib/lib/aiken/pairs.ak | 589 ++++++++ .../aiken-lang-stdlib/lib/aiken/string.ak | 134 ++ .../aiken-lang-stdlib/lib/aiken/time.ak | 3 + .../lib/aiken/transaction.ak | 224 +++ .../lib/aiken/transaction/certificate.ak | 15 + .../lib/aiken/transaction/credential.ak | 112 ++ .../lib/aiken/transaction/value.ak | 686 +++++++++ .../aiken/build/packages/packages.toml | 9 + .../.github/workflows/build_docs.yml | 50 + .../.github/workflows/release.yml | 80 + .../build/packages/sidan-lab-vodka/.gitignore | 16 + .../build/packages/sidan-lab-vodka/LICENSE | 201 +++ .../build/packages/sidan-lab-vodka/README.md | 87 ++ .../build/packages/sidan-lab-vodka/aiken.lock | 15 + .../build/packages/sidan-lab-vodka/aiken.toml | 14 + .../sidan-lab-vodka/build/aiken-compile.lock | 0 .../packages/aiken-lang-stdlib/.editorconfig | 9 + .../packages/aiken-lang-stdlib/.gitattributes | 2 + .../workflows/continuous-integration.yml | 64 + .../packages/aiken-lang-stdlib/.gitignore | 3 + .../packages/aiken-lang-stdlib/CHANGELOG.md | 185 +++ .../build/packages/aiken-lang-stdlib/LICENSE | 201 +++ .../packages/aiken-lang-stdlib/README.md | 62 + .../packages/aiken-lang-stdlib/aiken.lock | 7 + .../packages/aiken-lang-stdlib/aiken.toml | 11 + .../aiken-lang-stdlib/lib/aiken/bytearray.ak | 529 +++++++ .../aiken-lang-stdlib/lib/aiken/cbor.ak | 354 +++++ .../aiken-lang-stdlib/lib/aiken/dict.ak | 1117 ++++++++++++++ .../aiken-lang-stdlib/lib/aiken/hash.ak | 83 + .../aiken-lang-stdlib/lib/aiken/int.ak | 80 + .../aiken-lang-stdlib/lib/aiken/interval.ak | 680 +++++++++ .../aiken-lang-stdlib/lib/aiken/list.ak | 1337 +++++++++++++++++ .../aiken-lang-stdlib/lib/aiken/math.ak | 372 +++++ .../lib/aiken/math/rational.ak | 795 ++++++++++ .../aiken-lang-stdlib/lib/aiken/option.ak | 301 ++++ .../aiken-lang-stdlib/lib/aiken/pairs.ak | 589 ++++++++ .../aiken-lang-stdlib/lib/aiken/string.ak | 134 ++ .../aiken-lang-stdlib/lib/aiken/time.ak | 3 + .../lib/aiken/transaction.ak | 224 +++ .../lib/aiken/transaction/certificate.ak | 15 + .../lib/aiken/transaction/credential.ak | 112 ++ .../lib/aiken/transaction/value.ak | 686 +++++++++ .../build/packages/packages.toml | 4 + .../packages/sidan-lab-vodka/lib/mocktail.ak | 476 ++++++ .../lib/mocktail/virgin_address.ak | 43 + .../lib/mocktail/virgin_key_hex.ak | 160 ++ .../lib/mocktail/virgin_output_reference.ak | 20 + .../lib/mocktail/virgin_outputs.ak | 30 + .../lib/mocktail/virgin_validity_range.ak | 28 + .../sidan-lab-vodka/lib/vodka_address.ak | 55 + .../sidan-lab-vodka/lib/vodka_converter.ak | 36 + .../lib/vodka_extra_signatories.ak | 19 + .../sidan-lab-vodka/lib/vodka_inputs.ak | 99 ++ .../sidan-lab-vodka/lib/vodka_mints.ak | 45 + .../sidan-lab-vodka/lib/vodka_outputs.ak | 65 + .../sidan-lab-vodka/lib/vodka_redeemers.ak | 44 + .../lib/vodka_validity_range.ak | 28 + .../sidan-lab-vodka/lib/vodka_value.ak | 55 + .../packages/sidan-lab-vodka/plutus.json | 14 + example-web/aiken/plutus.json | 84 ++ example-web/aiken/validators/hello-world.ak | 61 + example-web/aiken/validators/minting.ak | 14 + example-web/src/pages/index.tsx | 205 ++- packages/Cargo.lock | 23 +- packages/Cargo.toml | 2 +- packages/sidan-csl-rs/Cargo.toml | 2 +- .../sidan-csl-rs/src/core/utils/script.rs | 5 + .../src/model/tx_builder_types/tx_in.rs | 58 +- packages/whisky-examples/Cargo.toml | 5 +- packages/whisky-examples/src/server.rs | 99 +- .../src/tx/complex_transaction.rs | 1 + packages/whisky-examples/src/tx/lock_fund.rs | 7 +- .../whisky-examples/src/tx/unlock_fund.rs | 5 + packages/whisky/Cargo.toml | 4 +- packages/whisky/src/builder/complete.rs | 2 +- packages/whisky/src/builder/mod.rs | 7 +- packages/whisky/src/builder/tx_eval.rs | 18 +- 100 files changed, 18240 insertions(+), 67 deletions(-) create mode 100644 example-web/aiken/README.md create mode 100644 example-web/aiken/aiken.lock create mode 100644 example-web/aiken/aiken.toml create mode 100644 example-web/aiken/build/aiken-compile.lock create mode 100644 example-web/aiken/build/packages/aiken-lang-stdlib/.editorconfig create mode 100644 example-web/aiken/build/packages/aiken-lang-stdlib/.gitattributes create mode 100644 example-web/aiken/build/packages/aiken-lang-stdlib/.github/workflows/continuous-integration.yml create mode 100644 example-web/aiken/build/packages/aiken-lang-stdlib/.gitignore create mode 100644 example-web/aiken/build/packages/aiken-lang-stdlib/CHANGELOG.md create mode 100644 example-web/aiken/build/packages/aiken-lang-stdlib/LICENSE create mode 100644 example-web/aiken/build/packages/aiken-lang-stdlib/README.md create mode 100644 example-web/aiken/build/packages/aiken-lang-stdlib/aiken.lock create mode 100644 example-web/aiken/build/packages/aiken-lang-stdlib/aiken.toml create mode 100644 example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/bytearray.ak create mode 100644 example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/cbor.ak create mode 100644 example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/dict.ak create mode 100644 example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/hash.ak create mode 100644 example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/int.ak create mode 100644 example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/interval.ak create mode 100644 example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/list.ak create mode 100644 example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/math.ak create mode 100644 example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/math/rational.ak create mode 100644 example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/option.ak create mode 100644 example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/pairs.ak create mode 100644 example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/string.ak create mode 100644 example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/time.ak create mode 100644 example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/transaction.ak create mode 100644 example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/transaction/certificate.ak create mode 100644 example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/transaction/credential.ak create mode 100644 example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/transaction/value.ak create mode 100644 example-web/aiken/build/packages/packages.toml create mode 100644 example-web/aiken/build/packages/sidan-lab-vodka/.github/workflows/build_docs.yml create mode 100644 example-web/aiken/build/packages/sidan-lab-vodka/.github/workflows/release.yml create mode 100644 example-web/aiken/build/packages/sidan-lab-vodka/.gitignore create mode 100644 example-web/aiken/build/packages/sidan-lab-vodka/LICENSE create mode 100644 example-web/aiken/build/packages/sidan-lab-vodka/README.md create mode 100644 example-web/aiken/build/packages/sidan-lab-vodka/aiken.lock create mode 100644 example-web/aiken/build/packages/sidan-lab-vodka/aiken.toml create mode 100644 example-web/aiken/build/packages/sidan-lab-vodka/build/aiken-compile.lock create mode 100644 example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/.editorconfig create mode 100644 example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/.gitattributes create mode 100644 example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/.github/workflows/continuous-integration.yml create mode 100644 example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/.gitignore create mode 100644 example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/CHANGELOG.md create mode 100644 example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/LICENSE create mode 100644 example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/README.md create mode 100644 example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/aiken.lock create mode 100644 example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/aiken.toml create mode 100644 example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/bytearray.ak create mode 100644 example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/cbor.ak create mode 100644 example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/dict.ak create mode 100644 example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/hash.ak create mode 100644 example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/int.ak create mode 100644 example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/interval.ak create mode 100644 example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/list.ak create mode 100644 example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/math.ak create mode 100644 example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/math/rational.ak create mode 100644 example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/option.ak create mode 100644 example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/pairs.ak create mode 100644 example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/string.ak create mode 100644 example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/time.ak create mode 100644 example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/transaction.ak create mode 100644 example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/transaction/certificate.ak create mode 100644 example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/transaction/credential.ak create mode 100644 example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/transaction/value.ak create mode 100644 example-web/aiken/build/packages/sidan-lab-vodka/build/packages/packages.toml create mode 100644 example-web/aiken/build/packages/sidan-lab-vodka/lib/mocktail.ak create mode 100644 example-web/aiken/build/packages/sidan-lab-vodka/lib/mocktail/virgin_address.ak create mode 100644 example-web/aiken/build/packages/sidan-lab-vodka/lib/mocktail/virgin_key_hex.ak create mode 100644 example-web/aiken/build/packages/sidan-lab-vodka/lib/mocktail/virgin_output_reference.ak create mode 100644 example-web/aiken/build/packages/sidan-lab-vodka/lib/mocktail/virgin_outputs.ak create mode 100644 example-web/aiken/build/packages/sidan-lab-vodka/lib/mocktail/virgin_validity_range.ak create mode 100644 example-web/aiken/build/packages/sidan-lab-vodka/lib/vodka_address.ak create mode 100644 example-web/aiken/build/packages/sidan-lab-vodka/lib/vodka_converter.ak create mode 100644 example-web/aiken/build/packages/sidan-lab-vodka/lib/vodka_extra_signatories.ak create mode 100644 example-web/aiken/build/packages/sidan-lab-vodka/lib/vodka_inputs.ak create mode 100644 example-web/aiken/build/packages/sidan-lab-vodka/lib/vodka_mints.ak create mode 100644 example-web/aiken/build/packages/sidan-lab-vodka/lib/vodka_outputs.ak create mode 100644 example-web/aiken/build/packages/sidan-lab-vodka/lib/vodka_redeemers.ak create mode 100644 example-web/aiken/build/packages/sidan-lab-vodka/lib/vodka_validity_range.ak create mode 100644 example-web/aiken/build/packages/sidan-lab-vodka/lib/vodka_value.ak create mode 100644 example-web/aiken/build/packages/sidan-lab-vodka/plutus.json create mode 100644 example-web/aiken/plutus.json create mode 100644 example-web/aiken/validators/hello-world.ak create mode 100644 example-web/aiken/validators/minting.ak diff --git a/example-web/aiken/README.md b/example-web/aiken/README.md new file mode 100644 index 00000000..50f13a42 --- /dev/null +++ b/example-web/aiken/README.md @@ -0,0 +1,55 @@ +# aiken-template + +Write validators in the `validators` folder, and supporting functions in the `lib` folder using `.ak` as a file extension. + +For example, as `validators/always_true.ak` + +```gleam +validator { + fn spend(_datum: Data, _redeemer: Data, _context: Data) -> Bool { + True + } +} +``` + +## Building + +```sh +aiken build +``` + +## Testing + +You can write tests in any module using the `test` keyword. For example: + +```gleam +test foo() { + 1 + 1 == 2 +} +``` + +To run all tests, simply do: + +```sh +aiken check +``` + +To run only tests matching the string `foo`, do: + +```sh +aiken check -m foo +``` + +## Documentation + +If you're writing a library, you might want to generate an HTML documentation for it. + +Use: + +```sh +aiken docs +``` + +## Resources + +Find more on the [Aiken's user manual](https://aiken-lang.org). diff --git a/example-web/aiken/aiken.lock b/example-web/aiken/aiken.lock new file mode 100644 index 00000000..76891e3c --- /dev/null +++ b/example-web/aiken/aiken.lock @@ -0,0 +1,26 @@ +# This file was generated by Aiken +# You typically do not need to edit this file + +[[requirements]] +name = "aiken-lang/stdlib" +version = "1.9.0" +source = "github" + +[[requirements]] +name = "sidan-lab/vodka" +version = "0.0.1-beta" +source = "github" + +[[packages]] +name = "aiken-lang/stdlib" +version = "1.9.0" +requirements = [] +source = "github" + +[[packages]] +name = "sidan-lab/vodka" +version = "0.0.1-beta" +requirements = [] +source = "github" + +[etags] diff --git a/example-web/aiken/aiken.toml b/example-web/aiken/aiken.toml new file mode 100644 index 00000000..fbbb8694 --- /dev/null +++ b/example-web/aiken/aiken.toml @@ -0,0 +1,21 @@ +name = "meshsdk/aiken-template" +version = "0.0.0" +compiler = "v1.0.31-alpha" +plutus = "v2" +license = "Apache-2.0" +description = "Aiken contracts for project 'meshsdk/aiken-template'" + +[repository] +user = "meshsdk" +project = "aiken-template" +platform = "github" + +[[dependencies]] +name = "aiken-lang/stdlib" +version = "1.9.0" +source = "github" + +[[dependencies]] +name = "sidan-lab/vodka" +version = "0.0.1-beta" +source = "github" diff --git a/example-web/aiken/build/aiken-compile.lock b/example-web/aiken/build/aiken-compile.lock new file mode 100644 index 00000000..e69de29b diff --git a/example-web/aiken/build/packages/aiken-lang-stdlib/.editorconfig b/example-web/aiken/build/packages/aiken-lang-stdlib/.editorconfig new file mode 100644 index 00000000..0759674c --- /dev/null +++ b/example-web/aiken/build/packages/aiken-lang-stdlib/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*.ak] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/example-web/aiken/build/packages/aiken-lang-stdlib/.gitattributes b/example-web/aiken/build/packages/aiken-lang-stdlib/.gitattributes new file mode 100644 index 00000000..99fefcf4 --- /dev/null +++ b/example-web/aiken/build/packages/aiken-lang-stdlib/.gitattributes @@ -0,0 +1,2 @@ +# Temp hack to get some syntax highlighting on github +*.ak linguist-language=Gleam diff --git a/example-web/aiken/build/packages/aiken-lang-stdlib/.github/workflows/continuous-integration.yml b/example-web/aiken/build/packages/aiken-lang-stdlib/.github/workflows/continuous-integration.yml new file mode 100644 index 00000000..29d6f498 --- /dev/null +++ b/example-web/aiken/build/packages/aiken-lang-stdlib/.github/workflows/continuous-integration.yml @@ -0,0 +1,64 @@ +name: Continuous Integration + +on: + workflow_dispatch: + push: + branches: ["main"] + tags: ["*.*.*"] + pull_request: + branches: ["main"] + +env: + CARGO_TERM_COLOR: always + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: "pages" + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: 📥 Checkout repository + uses: actions/checkout@v3 + + - name: 🧰 Setup Pages + uses: actions/configure-pages@v2 + + - name: 🧰 Install Aiken + uses: aiken-lang/setup-aiken@v1 + with: + version: v1.0.28-alpha + + - name: 📝 Run fmt + run: aiken fmt --check + + - name: 🔬 Run tests + run: aiken check + + - name: 📘 Generate documentation + shell: bash + working-directory: . + run: aiken docs -o docs + + - name: 📦 Upload artifact + uses: actions/upload-pages-artifact@v2 + with: + path: "docs/" + + deploy: + if: ${{ startsWith(github.ref, 'refs/tags') }} + needs: build + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: 🚀 Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v1 diff --git a/example-web/aiken/build/packages/aiken-lang-stdlib/.gitignore b/example-web/aiken/build/packages/aiken-lang-stdlib/.gitignore new file mode 100644 index 00000000..3a3d38e6 --- /dev/null +++ b/example-web/aiken/build/packages/aiken-lang-stdlib/.gitignore @@ -0,0 +1,3 @@ +build/ +docs/ +.DS_Store \ No newline at end of file diff --git a/example-web/aiken/build/packages/aiken-lang-stdlib/CHANGELOG.md b/example-web/aiken/build/packages/aiken-lang-stdlib/CHANGELOG.md new file mode 100644 index 00000000..2bec225d --- /dev/null +++ b/example-web/aiken/build/packages/aiken-lang-stdlib/CHANGELOG.md @@ -0,0 +1,185 @@ +# Changelog + +## v1.9.0 - 2024-05-24 + +### Added + +- A new module [`aiken/pairs`](https://aiken-lang.github.io/stdlib/aiken/pairs.html) to work with associative lists (a.k.a. `Pairs`). + +### Changed + +- **BREAKING-CHANGE**
+ Specialized all `Dict`'s key to `ByteArray`, and thus remove the need for passing an extra comparison function in many functions. `Dict` are however still specialized with a phantom type for keys. + +- **BREAKING-CHANGE**
+ Few functions from `Dict` have been renamed for consistency: + - `from_list` -> `from_pairs` + - `from_ascending_list` -> `from_ascending_pairs` + - `to_list` -> `to_pairs` + +### Removed + +N/A + +## v1.8.0 - 2024-03-28 + +### Added + +- [`value.reduce`](https://aiken-lang.github.io/stdlib/aiken/transaction/value.html#reduce) to efficiently fold over a value and its elements. + +- [`value.from_asset_list`](https://aiken-lang.github.io/stdlib/aiken/transaction/value.html#from_asset_list) to turn an asset list into a Value while enforcing invariants expected of `Value`. + +- [`math.is_sqrt`](https://aiken-lang.github.io/stdlib/aiken/math.html#is_sqrt) as a more efficient alternative to `sqrt`. + +### Changed + +- Disclaimers in documentation to [`bytearray.to_string`](https://aiken-lang.github.io/stdlib/aiken/bytearray.html#to_string) and [`string.from_bytearray`](https://aiken-lang.github.io/stdlib/aiken/string.html#from_bytearray) regarding UTF-8 encoding. + +### Removed + +N/A + +## v1.7.0 - 2023-11-07 + +### Added + +- [`list.index_of`](https://aiken-lang.github.io/stdlib/aiken/list.html#index_of): For getting a values index in a list. +- [`transaction.placeholder`](https://aiken-lang.github.io/stdlib/aiken/transaction.html#placeholder): For constructing test transactions. +- [`transaction.value.is_zero`](https://aiken-lang.github.io/stdlib/aiken/transaction/value.html#is_zero): For checking whether a value is null. + +### Changed + +- [`value.to_minted_value`](https://aiken-lang.github.io/stdlib/aiken/transaction/value.html#to_minted_value) now correctly preserves the invariant of `MintedValue`: it always contain a null quantity of Ada. + +### Removed + +N/A + +## v1.6.0 - 2023-09-08 + +### Added + +- [`math.pow2`](https://aiken-lang.github.io/stdlib/aiken/math.html#pow2): For faster exponentions for powers of two. +- [`bytearray.test_bit`](https://aiken-lang.github.io/stdlib/aiken/bytearray.html#test_bit): For testing if a bit is set in a bytearray (MSB). + +## v1.5.0 - 2023-08-16 + +### Removed + +- retired `list.and` and `list.or` because of the new keywords for logical op chaining. + +## v1.4.0 - 2023-07-21 + +### Changed + +- Fixed missing null-check on `value.add`. Adding a null quantity of token is now correctly a no-op. + +## v1.3.0 - 2023-06-30 + +### Added + +- [`math.sqrt`](https://aiken-lang.github.io/stdlib/aiken/math.html#sqrt): For calculating integer square roots using a quadratically convergent method. +- [`math/rational.numerator`](https://aiken-lang.github.io/stdlib/aiken/math/rational.html#numerator) & [`math/rational.denominator`](https://aiken-lang.github.io/stdlib/aiken/math/rational.html#numerator): For accessing parts of a rational value. +- [`math/rational.arithmetic_mean`](https://aiken-lang.github.io/stdlib/aiken/math/rational.html#arithmetic_mean): For computing [arithmetic mean](https://en.wikipedia.org/wiki/Arithmetic_mean) of rational values. +- [`math/rational.geometric_mean`](https://aiken-lang.github.io/stdlib/aiken/math/rational.html#geometric_mean): For computing [geometric mean](https://en.wikipedia.org/wiki/Geometric_mean) of two rational values. + +### Changed + +- Clear empty asset lists in [`Value`](https://aiken-lang.github.io/stdlib/aiken/transaction/value.html#Value) on various operations. Before that fix, it could happen that removing all assets from a given policy would lead to an empty dictionnary of assets still be present in the `Value`. + +## v1.2.0 - 2023-06-17 + +### Added + +- [`transaction/value.MintedValue`](https://aiken-lang.github.io/stdlib/aiken/transaction/value.html#MintedValue) +- [`transaction/value.from_minted_value`](https://aiken-lang.github.io/stdlib/aiken/transaction/value.html#from_minted_value): Convert from `MintedValue` to `Value` +- [`transaction/value.to_minted_value`](https://aiken-lang.github.io/stdlib/aiken/transaction/value.html#to_minted_value): Convert from `Value` to `MintedValue` +- [`transaction/bytearray.to_hex`](https://aiken-lang.github.io/stdlib/aiken/bytearray.html#to_hex): Convert a `ByteArray` to a hex encoded `String` +- [`math/rational`](https://aiken-lang.github.io/stdlib/aiken/math/rational.html): Working with rational numbers. + - [x] `abs` + - [x] `add` + - [x] `ceil` + - [x] `compare` + - [x] `compare_with` + - [x] `div` + - [x] `floor` + - [x] `from_int` + - [x] `mul` + - [x] `negate` + - [x] `new` + - [x] `proper_fraction` + - [x] `reciprocal` + - [x] `reduce` + - [x] `round` + - [x] `round_even` + - [x] `sub` + - [x] `truncate` + - [x] `zero` + +### Removed + +- module `MintedValue` was merged with `Value` + +## v1.1.0 - 2023-06-06 + +### Added + +- [`list.count`](https://aiken-lang.github.io/stdlib/aiken/list.html#count): Count how many items in the list satisfy the given predicate. + +- [`int.from_utf8`](https://aiken-lang.github.io/stdlib/aiken/int.html#from_utf8): Parse an integer from a utf-8 encoded `ByteArray`, when possible. + +- [`dict.foldl`](https://aiken-lang.github.io/stdlib/aiken/dict.html#foldl) & [`dict.foldr`](https://aiken-lang.github.io/stdlib/aiken/dict.html#foldr): for left and right folds over dictionnary elements in ascending key order. + +- [`dict.insert_with`](https://aiken-lang.github.io/stdlib/aiken/dict.html#insert_with): Insert a value in the dictionary at a given key. When the key already exist, the provided merge function is called. + +- [`transaction/value.add`](https://aiken-lang.github.io/stdlib/aiken/transaction/value.html#add): Add a (positive or negative) quantity of a single token to a value. This is more efficient than `merge` for a single asset. + +- [`transaction/value.to_dict`](https://aiken-lang.github.io/stdlib/aiken/transaction/value.html#to_dict): Convert a `Value` into a dictionnary of dictionnaries. + +- A new module [`transaction/minted_value`](https://aiken-lang.github.io/stdlib/aiken/transaction/minted_value.html): This is used exclusively for representing values present in the `mint` field of transactions. This allows to simplify some of the implementation for `Value` which no longer needs to handle the special case where null-quantity tokens would be present. It isn't possible to construct `MintedValue` by hand, they come from the script context entirely and are 'read-only'. + +- More documentation for `dict` and `interval` modules. + +### Changed + +> **Warning** +> +> Most of those changes are breaking-changes. Though, given we're still in an +> alpha state, only the `minor` component is bumped from the version number. +> Please forgive us. + +- Rework `list.{foldl, foldr, reduce, indexed_foldr}`, `dict.{fold}`, `bytearray.{foldl, foldr, reduce}` to take the iterator as last argument. For example: + + ``` + fn foldl(self: List, with: fn(a, b) -> b, zero: b) -> b + + ↓ becomes + + fn foldl(self: List, zero: b, with: fn(a, b) -> b) -> b + ``` + +- Fixed implementation of `bytearray.slice`; `slice` would otherwise behave as if the second argument were an offset. + +- Rename `transaction/value.add` into `transaction/value.merge`. + +- Swap arguments of the merge function in `dict.union_with`; the first value received now corresponds to the value already present in the dictionnary. + +- Fixed various examples from the documentation + +### Removed + +- Removed `dict.fold`; replaced with `dict.foldl` and `dict.foldr` to remove ambiguity. + +## v1.0.0 - 2023-04-13 + +### Added + +N/A + +### Changed + +N/A + +### Removed + +N/A diff --git a/example-web/aiken/build/packages/aiken-lang-stdlib/LICENSE b/example-web/aiken/build/packages/aiken-lang-stdlib/LICENSE new file mode 100644 index 00000000..4a1de273 --- /dev/null +++ b/example-web/aiken/build/packages/aiken-lang-stdlib/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2022 Lucas Rosa + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/example-web/aiken/build/packages/aiken-lang-stdlib/README.md b/example-web/aiken/build/packages/aiken-lang-stdlib/README.md new file mode 100644 index 00000000..b3a4c5ff --- /dev/null +++ b/example-web/aiken/build/packages/aiken-lang-stdlib/README.md @@ -0,0 +1,62 @@ +
+
+

Aiken Aiken Standard Library

+ +[![Licence](https://img.shields.io/github/license/aiken-lang/stdlib)](https://github.com/aiken-lang/stdlib/blob/main/LICENSE) +[![Continuous Integration](https://github.com/aiken-lang/stdlib/actions/workflows/continuous-integration.yml/badge.svg?branch=main)](https://github.com/aiken-lang/stdlib/actions/workflows/continuous-integration.yml) + +
+
+ +## Compatibility + +aiken's version | stdlib's version(s) +--- | --- +`v1.0.28-alpha` | `1.9.0` +`v1.0.26-alpha` | `1.8.0` + +## Overview + +The official standard library for the [Aiken](https://aiken-lang.org) Cardano +smart-contract language. + +It extends the language builtins with useful data-types, functions, constants +and aliases that make using Aiken a bliss. + +```aiken +use aiken/hash.{Blake2b_224, Hash} +use aiken/list +use aiken/transaction.{ScriptContext} +use aiken/transaction/credential.{VerificationKey} + +pub type Datum { + owner: Hash, +} + +pub type Redeemer { + msg: ByteArray, +} + +/// A simple validator which replicates a basic public/private signature lock. +/// +/// - The key (hash) is set as datum when the funds are sent to the script address. +/// - The spender is expected to provide a signature, and the string 'Hello, World!' as message +/// - The signature is implicitly verified by the ledger, and included as 'extra_signatories' +/// +validator { + fn spend(datum: Datum, redeemer: Redeemer, context: ScriptContext) -> Bool { + let must_say_hello = + redeemer.msg == "Hello, World!" + + let must_be_signed = + context.transaction.extra_signatories + |> list.any(fn(vkh: ByteArray) { vkh == datum.owner }) + + must_say_hello && must_be_signed + } +} +``` + +## Stats + +![Alt](https://repobeats.axiom.co/api/embed/f0a17e7f6133630e165b9e56ec5447bef32fe831.svg "Repobeats analytics image") diff --git a/example-web/aiken/build/packages/aiken-lang-stdlib/aiken.lock b/example-web/aiken/build/packages/aiken-lang-stdlib/aiken.lock new file mode 100644 index 00000000..6e350cda --- /dev/null +++ b/example-web/aiken/build/packages/aiken-lang-stdlib/aiken.lock @@ -0,0 +1,7 @@ +# This file was generated by Aiken +# You typically do not need to edit this file + +requirements = [] +packages = [] + +[etags] diff --git a/example-web/aiken/build/packages/aiken-lang-stdlib/aiken.toml b/example-web/aiken/build/packages/aiken-lang-stdlib/aiken.toml new file mode 100644 index 00000000..5d5dc776 --- /dev/null +++ b/example-web/aiken/build/packages/aiken-lang-stdlib/aiken.toml @@ -0,0 +1,11 @@ +name = "aiken-lang/stdlib" +version = "1.9.0" +licences = ["Apache-2.0"] +description = "The Aiken Standard Library" + +dependencies = [] + +[repository] +user = "aiken-lang" +project = "stdlib" +platform = "github" diff --git a/example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/bytearray.ak b/example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/bytearray.ak new file mode 100644 index 00000000..840079eb --- /dev/null +++ b/example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/bytearray.ak @@ -0,0 +1,529 @@ +use aiken/builtin +use aiken/math +use aiken/option + +/// Compare two bytearrays lexicographically. +/// +/// ```aiken +/// bytearray.compare(#"00", #"FF") == Less +/// bytearray.compare(#"42", #"42") == Equal +/// bytearray.compare(#"FF", #"00") == Greater +/// ``` +pub fn compare(left: ByteArray, right: ByteArray) -> Ordering { + if builtin.less_than_bytearray(left, right) { + Less + } else if builtin.equals_bytearray(left, right) { + Equal + } else { + Greater + } +} + +/// Combine two `ByteArray` together. +/// +/// ```aiken +/// bytearray.concat(left: #[1, 2, 3], right: #[4, 5, 6]) == #[1, 2, 3, 4, 5, 6] +/// ``` +pub fn concat(left: ByteArray, right: ByteArray) -> ByteArray { + builtin.append_bytearray(left, right) +} + +test concat_1() { + concat(#"", #"") == #"" +} + +test concat_2() { + concat(#"", #"01") == #"01" +} + +test concat_3() { + concat(#"0102", #"") == #"0102" +} + +test concat_4() { + concat(#"0102", #"0304") == #"01020304" +} + +/// Returns the suffix of a `ByteArray` after `n` elements. +/// +/// ```aiken +/// bytearray.drop(#[1, 2, 3], n: 2) == #[3] +/// ``` +pub fn drop(self: ByteArray, n: Int) -> ByteArray { + builtin.slice_bytearray(n, builtin.length_of_bytearray(self) - n, self) +} + +test drop_1() { + let x = #"01020304050607" + drop(x, 2) == #"0304050607" +} + +test drop_2() { + let x = #"01020304050607" + drop(x, 0) == x +} + +test drop_3() { + let x = #"01" + drop(x, 1) == #"" +} + +test drop_4() { + let x = #"" + drop(x, 2) == #"" +} + +/// Left-fold over bytes of a [`ByteArray`](https://aiken-lang.github.io/prelude/aiken.html#ByteArray). Note that every byte given to the callback function is comprised between 0 and 255. +/// +/// ```aiken +/// bytearray.foldl(#"acab", 0, fn(byte, acc) { acc * 256 + byte }) == 44203 +/// bytearray.foldl(#[1, 2, 3], #"", flip(bytearray.push)) == #[3, 2, 1] +/// ``` +pub fn foldl( + self: ByteArray, + zero: result, + with: fn(Int, result) -> result, +) -> result { + do_foldl(self, zero, builtin.length_of_bytearray(self), 0, with) +} + +fn do_foldl( + self: ByteArray, + zero: result, + len: Int, + cursor: Int, + with: fn(Int, result) -> result, +) -> result { + if cursor == len { + zero + } else { + do_foldl( + self, + with(builtin.index_bytearray(self, cursor), zero), + len, + cursor + 1, + with, + ) + } +} + +test foldl_1() { + foldl(#[], 42, fn(byte, acc) { byte + acc }) == 42 +} + +test foldl_2() { + foldl(#"acab", 0, fn(byte, acc) { acc * 256 + byte }) == 44203 +} + +test foldl_3() { + foldl( + #"356cf088720a169dae0ce0bb1df8588944389fa43322f0d6ef4ed8c069bfd405", + 0, + fn(byte, acc) { acc * 256 + byte }, + ) == 24165060555594911913195642527692216679757672038384202527929620681761931383813 +} + +test foldl_4() { + foldl(#[1, 2, 3, 4, 5], #"", flip(push)) == #[5, 4, 3, 2, 1] +} + +/// Right-fold over bytes of a [`ByteArray`](https://aiken-lang.github.io/prelude/aiken.html#ByteArray). Note that every byte given to the callback function is comprised between 0 and 255. +/// +/// ```aiken +/// bytearray.foldr(#"acab", 0, fn(byte, acc) { acc * 256 + byte }) == 43948 +/// bytearray.foldl(#[1, 2, 3], #"", flip(bytearray.push)) == #[1, 2, 3] +/// ``` +pub fn foldr( + self: ByteArray, + zero: result, + with: fn(Int, result) -> result, +) -> result { + do_foldr(self, zero, builtin.length_of_bytearray(self) - 1, with) +} + +fn do_foldr( + self: ByteArray, + zero: result, + cursor: Int, + with: fn(Int, result) -> result, +) -> result { + if cursor < 0 { + zero + } else { + do_foldr( + self, + with(builtin.index_bytearray(self, cursor), zero), + cursor - 1, + with, + ) + } +} + +test foldr_1() { + foldr(#[], 42, fn(byte, acc) { byte + acc }) == 42 +} + +test foldr_2() { + foldr(#"acab", 0, fn(byte, acc) { acc * 256 + byte }) == 43948 +} + +test foldr_3() { + foldr(#[1, 2, 3, 4, 5], #"", flip(push)) == #[1, 2, 3, 4, 5] +} + +/// Search the start and end positions of a sub-array in a `ByteArray`. +/// +/// ```aiken +/// bytearray.index_of("Hello, World!", "World") == Some((7, 11)) +/// bytearray.index_of("Hello, World!", "foo") == None +/// bytearray.index_of("Hello, World!", "!") == Some((12, 12)) +/// bytearray.index_of("Hello, World!", "o") == Some((4, 4)) +/// bytearray.index_of("Hello, World!", "Hello, World!") == Some((0, 12)) +/// ``` +pub fn index_of(self: ByteArray, bytes: ByteArray) -> Option<(Int, Int)> { + let offset = length(bytes) + + do_index_of(self, bytes, 0, offset, length(self)) + |> option.map(fn(ix) { (ix, ix + offset - 1) }) +} + +fn do_index_of( + self: ByteArray, + bytes: ByteArray, + cursor: Int, + offset: Int, + size: Int, +) -> Option { + if cursor + offset > size { + None + } else { + if builtin.slice_bytearray(cursor, offset, self) == bytes { + Some(cursor) + } else { + do_index_of(self, bytes, cursor + 1, offset, size) + } + } +} + +test index_of_1() { + index_of("Hello, World!", "World") == Some((7, 11)) +} + +test index_of_2() { + index_of("Hello, World!", "foo") == None +} + +test index_of_3() { + index_of("Hello, World!", "!") == Some((12, 12)) +} + +test index_of_4() { + index_of("Hello, World!", "o") == Some((4, 4)) +} + +test index_of_5() { + index_of("Hello, World!", "Hello, World!") == Some((0, 12)) +} + +/// Returns the number of bytes in a `ByteArray`. +/// +/// ```aiken +/// bytearray.length(#[1, 2, 3]) == 3 +/// ``` +pub fn length(self: ByteArray) -> Int { + builtin.length_of_bytearray(self) +} + +test length_1() { + length(#"") == 0 +} + +test length_2() { + length(#"010203") == 3 +} + +/// Returns `True` when the given `ByteArray` is empty. +/// +/// ```aiken +/// bytearray.is_empty(#"") == True +/// bytearray.is_empty(#"00ff") == False +/// ``` +pub fn is_empty(self: ByteArray) -> Bool { + builtin.length_of_bytearray(self) == 0 +} + +test is_empty_1() { + is_empty(#"") == True +} + +test is_empty_2() { + is_empty(#"01") == False +} + +/// Convert a `String` into a `ByteArray`. +/// +/// ```aiken +/// bytearray.from_string(@"ABC") == #"414243" +/// ``` +pub fn from_string(str: String) -> ByteArray { + builtin.encode_utf8(str) +} + +test from_string_1() { + from_string(@"") == "" +} + +test from_string_2() { + from_string(@"ABC") == #"414243" +} + +/// Add a byte element in front of a `ByteArray`. When the given byte is +/// greater than 255, it wraps-around. **PlutusV2 behavior** So 256 is mapped to 0, 257 to 1, and so +/// forth. +/// In PlutusV3 this will error instead of wrapping around. +/// +/// ```aiken +/// bytearray.push(#"", 0) == #"00" +/// bytearray.push(#"0203", 1) == #"010203" +/// bytearray.push(#"0203", 257) == #"010203" +/// ``` +pub fn push(self: ByteArray, byte: Int) -> ByteArray { + builtin.cons_bytearray(byte, self) +} + +test push_1() { + push(#[], 0) == #[0] +} + +test push_2() { + push(#[2, 3], 1) == #[1, 2, 3] +} + +test push_3() { + let x = 257 + push(#[2, 3], x) == #[1, 2, 3] +} + +/// Reduce bytes in a ByteArray from left to right using the accumulator as left operand. +/// Said differently, this is [`foldl`](#foldl) with callback arguments swapped. +/// +/// ```aiken +/// bytearray.reduce(#[1,2,3], #[], bytearray.push) == #[3, 2, 1] +/// ``` +pub fn reduce( + self: ByteArray, + zero: result, + with: fn(result, Int) -> result, +) -> result { + foldl(self, zero, flip(with)) +} + +test reduce_1() { + reduce(#[], #[], push) == #[] +} + +test reduce_2() { + reduce(#[1, 2, 3], #[], push) == #[3, 2, 1] +} + +/// Extract a `ByteArray` as a slice of another `ByteArray`. +/// +/// Indexes are 0-based and inclusive. +/// +/// ```aiken +/// bytearray.slice(#[0, 1, 2, 3, 4, 5, 6], start: 1, end: 3) == #[1, 2, 3] +/// ``` +pub fn slice(self: ByteArray, start: Int, end: Int) -> ByteArray { + builtin.slice_bytearray(start, end - start + 1, self) +} + +test slice_1() { + slice(#"", 1, 2) == #"" +} + +test slice_2() { + slice(#"010203", 1, 2) == #"0203" +} + +test slice_3() { + slice(#"010203", 0, 42) == #"010203" +} + +test slice_4() { + slice(#[0, 1, 2, 3, 4], 0, 3) == #[0, 1, 2, 3] +} + +test slice_5() { + slice(#[0, 1, 2, 3, 4], 1, 2) == #[1, 2] +} + +/// Returns the n-length prefix of a `ByteArray`. +/// +/// ```aiken +/// bytearray.take(#[1, 2, 3], n: 2) == #[1, 2] +/// ``` +pub fn take(self: ByteArray, n: Int) -> ByteArray { + builtin.slice_bytearray(0, n, self) +} + +test take_1() { + let x = #"01020304050607" + take(x, 2) == #"0102" +} + +test take_2() { + let x = #"01020304050607" + take(x, 0) == #"" +} + +test take_3() { + let x = #"01" + take(x, 1) == x +} + +test take_4() { + let x = #"010203" + take(x, 0) == #"" +} + +/// Convert a `ByteArray` into a `String`. +/// +///
⚠️
WARNING
| This functions fails if the underlying `ByteArray` isn't UTF-8-encoded.
In particular, you cannot convert arbitrary hash digests using this function.
For converting arbitrary `ByteArray`s, use [bytearray.to_hex](#to_hex). +/// --- | --- +/// +/// +/// ```aiken +/// bytearray.to_string(#"414243") == "ABC" +/// +/// bytearray.to_string(some_hash) -> fail +/// ``` +pub fn to_string(self: ByteArray) -> String { + builtin.decode_utf8(self) +} + +test to_string_1() { + to_string("") == @"" +} + +test to_string_2() { + to_string("ABC") == @"ABC" +} + +/// Encode a `ByteArray` as a hexidecimal `String`. +/// +/// ```aiken +/// use aiken/bytearray +/// +/// bytearray.to_hex("Hello world!") == @"48656c6c6f20776f726c6421" +/// ``` +pub fn to_hex(self: ByteArray) -> String { + self + |> encode_base16(builtin.length_of_bytearray(self) - 1, "") + |> builtin.decode_utf8 +} + +// Construct an hex string in reverse order, from the back. The 'builder' is an +// accumulator. It works fast because `index_bytearray` follows a constant-time cost +// model +fn encode_base16(bytes: ByteArray, ix: Int, builder: ByteArray) -> ByteArray { + if ix < 0 { + builder + } else { + let byte = builtin.index_bytearray(bytes, ix) + let msb = byte / 16 + let lsb = byte % 16 + encode_base16( + bytes, + ix - 1, + builtin.cons_bytearray( + msb + if msb < 10 { + 48 + } else { + 87 + }, + builtin.cons_bytearray( + lsb + if lsb < 10 { + 48 + } else { + 87 + }, + builder, + ), + ), + ) + } +} + +test to_hex_1() { + to_hex("Hello world!") == @"48656c6c6f20776f726c6421" +} + +test to_hex_2() { + to_hex("The quick brown fox jumps over the lazy dog") == @"54686520717569636b2062726f776e20666f78206a756d7073206f76657220746865206c617a7920646f67" +} + +/// Checks whether a bit (Most-Significant-Bit first) is set in the given 'ByteArray'. +/// +/// For example, consider the following bytearray: `#"8b765f"`. It can also be written as the +/// following bits sequence: +/// +/// `8` | `b` | `7` | `6` | `5` | `f` +/// --- | --- | --- | --- | --- | --- +/// `1000` | `1011` | `0111` | `0110` | `0101` | `1111` +/// +/// And thus, we have: +/// +/// ```aiken +/// test_bit(#"8b765f", 0) == True +/// test_bit(#"8b765f", 1) == False +/// test_bit(#"8b765f", 2) == False +/// test_bit(#"8b765f", 3) == False +/// test_bit(#"8b765f", 7) == True +/// test_bit(#"8b765f", 8) == False +/// test_bit(#"8b765f", 20) == True +/// test_bit(#"8b765f", 21) == True +/// test_bit(#"8b765f", 22) == True +/// test_bit(#"8b765f", 23) == True +/// ``` +pub fn test_bit(self: ByteArray, ix: Int) -> Bool { + builtin.less_than_equals_bytearray( + #[128], + builtin.cons_bytearray( + builtin.index_bytearray(self, ix / 8) * math.pow2(ix % 8), + "", + ), + ) +} + +test test_bit_0() { + test_bit(#"8b765f", 0) +} + +test test_bit_1() { + !test_bit(#"8b765f", 1) +} + +test test_bit_2() { + !test_bit(#"8b765f", 2) +} + +test test_bit_3() { + !test_bit(#"8b765f", 3) +} + +test test_bit_7() { + test_bit(#"8b765f", 7) +} + +test test_bit_8() { + !test_bit(#"8b765f", 8) +} + +test test_bit_20_21_22_23() { + and { + test_bit(#"8b765f", 20), + test_bit(#"8b765f", 21), + test_bit(#"8b765f", 22), + test_bit(#"8b765f", 23), + } +} diff --git a/example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/cbor.ak b/example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/cbor.ak new file mode 100644 index 00000000..5a5ec2e6 --- /dev/null +++ b/example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/cbor.ak @@ -0,0 +1,354 @@ +use aiken/builtin.{ + append_bytearray, choose_data, cons_bytearray, decode_utf8, index_bytearray, + length_of_bytearray, quotient_integer, remainder_integer, serialise_data, + un_b_data, un_constr_data, un_i_data, un_list_data, un_map_data, +} +use aiken/list + +/// Serialise any value to binary, encoding using [CBOR](https://www.rfc-editor.org/rfc/rfc8949). +/// +/// This is particularly useful in combination with hashing functions, as a way +/// to obtain a byte representation that matches the serialised representation +/// used by the ledger in the context of on-chain code. +/// +/// Note that the output matches the output of [`diagnostic`](#diagnostic), +/// though with a different encoding. [`diagnostic`](#diagnostic) is merely a +/// textual representation of the CBOR encoding that is human friendly and +/// useful for debugging. +/// +/// ```aiken +/// serialise(42) == #"182a" +/// serialise(#"a1b2") == #"42a1b2" +/// serialise([]) == #"80" +/// serialise((1, 2)) == #"9f0102ff" +/// serialise((1, #"ff", 3)) == #"9f0141ff03ff" +/// serialise([(1, #"ff")]) == #"a10141ff" +/// serialise(Some(42)) == #"d8799f182aff" +/// serialise(None) == #"d87a80" +/// ``` +pub fn serialise(self: Data) -> ByteArray { + serialise_data(self) +} + +test serialise_1() { + serialise(42) == #"182a" +} + +test serialise_2() { + serialise(#"a1b2") == #"42a1b2" +} + +test serialise_3() { + serialise([]) == #"80" +} + +test serialise_4() { + serialise((1, 2)) == #"9f0102ff" +} + +test serialise_5() { + serialise((1, #"ff", 3)) == #"9f0141ff03ff" +} + +test serialise_6() { + serialise([(1, #"ff")]) == #"9f9f0141ffffff" +} + +test serialise_7() { + serialise(Some(42)) == #"d8799f182aff" +} + +test serialise_8() { + serialise(None) == #"d87a80" +} + +test serialise_9() { + serialise([Pair(1, #"ff")]) == #"a10141ff" +} + +/// Obtain a String representation of _anything_. This is particularly (and only) useful for tracing +/// and debugging. This function is expensive and should not be used in any production code as it +/// will very likely explodes the validator's budget. +/// +/// The output is a [CBOR diagnostic](https://www.rfc-editor.org/rfc/rfc8949#name-diagnostic-notation) +/// of the underlying on-chain binary representation of the data. It's not as +/// easy to read as plain Aiken code, but it is handy for troubleshooting values +/// _at runtime_. Incidentally, getting familiar with reading CBOR diagnostic is +/// a good idea in the Cardano world. +/// +/// ```aiken +/// diagnostic(42) == "42" +/// diagnostic(#"a1b2") == "h'A1B2'" +/// diagnostic([1, 2, 3]) == "[_ 1, 2, 3]" +/// diagnostic([]) == "[]" +/// diagnostic((1, 2)) == "[_ 1, 2]" +/// diagnostic((1, #"ff", 3)) == "[_ 1, h'FF', 3]" +/// diagnostic([(1, #"ff")]) == "{_ 1: h'FF' }" +/// diagnostic(Some(42)) == "121([_ 42])" +/// diagnostic(None) == "122([])" +/// ``` +pub fn diagnostic(self: Data) -> String { + do_diagnostic(self, #"") + |> decode_utf8 +} + +/// UTF-8 lookup table. Comes in handy to decipher the code below. +/// +/// | Symbol | Decimal | Hex | +/// | --- | --- | --- | +/// | | 32 | 0x20 | +/// | ' | 39 | 0x27 | +/// | ( | 40 | 0x28 | +/// | ) | 41 | 0x29 | +/// | , | 44 | 0x2c | +/// | 0 | 48 | 0x30 | +/// | : | 58 | 0x3a | +/// | A | 65 | 0x41 | +/// | [ | 91 | 0x5b | +/// | ] | 93 | 0x5d | +/// | _ | 95 | 0x5f | +/// | h | 104 | 0x68 | +/// | { | 123 | 0x7b | +/// | } | 125 | 0x7d | +fn do_diagnostic(self: Data, builder: ByteArray) -> ByteArray { + choose_data( + self, + { + // -------- Constr + let Pair(constr, fields) = un_constr_data(self) + + // NOTE: This is fundamentally the same logic for serializing list. However, the compiler + // doesn't support mutual recursion just yet, so we can't extract that logic in a separate + // function. + // + // See [aiken-lang/aiken#389](https://github.com/aiken-lang/aiken/pull/389) + let builder = + when fields is { + [] -> append_bytearray(#"5b5d29", builder) + _ -> { + let (_, bytes) = + list.foldr( + fields, + (#"5d", append_bytearray(#"29", builder)), + fn(e: Data, st: (ByteArray, ByteArray)) { + (#"2c20", do_diagnostic(e, append_bytearray(st.1st, st.2nd))) + }, + ) + append_bytearray(#"5b5f20", bytes) + } + } + + let constr_tag = + if constr < 7 { + 121 + constr + } else if constr < 128 { + 1280 + constr - 7 + } else { + fail @"What are you doing? No I mean, seriously." + } + + builder + |> append_bytearray(#"28", _) + |> from_int(constr_tag, _) + }, + { + // -------- Map + + let elems = un_map_data(self) + when elems is { + [] -> append_bytearray(#"7b7d", builder) + _ -> { + let (_, bytes) = + list.foldr( + elems, + (#"207d", builder), + fn(e: Pair, st: (ByteArray, ByteArray)) { + let value = + do_diagnostic(e.2nd, append_bytearray(st.1st, st.2nd)) + ( + #"2c20", + do_diagnostic(e.1st, append_bytearray(#"3a20", value)), + ) + }, + ) + append_bytearray(#"7b5f20", bytes) + } + } + }, + { + // -------- List + + let elems = un_list_data(self) + when elems is { + [] -> append_bytearray(#"5b5d", builder) + _ -> { + let (_, bytes) = + list.foldr( + elems, + (#"5d", builder), + fn(e: Data, st: (ByteArray, ByteArray)) { + (#"2c20", do_diagnostic(e, append_bytearray(st.1st, st.2nd))) + }, + ) + append_bytearray(#"5b5f20", bytes) + } + } + }, + // -------- Integer + self + |> un_i_data + |> from_int(builder), + { + // -------- ByteArray + let bytes = un_b_data(self) + + bytes + |> encode_base16( + length_of_bytearray(bytes) - 1, + append_bytearray(#"27", builder), + ) + |> append_bytearray(#"6827", _) + }, + ) +} + +fn encode_base16(bytes: ByteArray, ix: Int, builder: ByteArray) -> ByteArray { + if ix < 0 { + builder + } else { + let byte = index_bytearray(bytes, ix) + let msb = byte / 16 + let lsb = byte % 16 + let builder = + cons_bytearray( + msb + if msb < 10 { + 48 + } else { + 55 + }, + cons_bytearray( + lsb + if lsb < 10 { + 48 + } else { + 55 + }, + builder, + ), + ) + encode_base16(bytes, ix - 1, builder) + } +} + +fn from_int(i: Int, digits: ByteArray) -> ByteArray { + if i == 0 { + append_bytearray(#"30", digits) + } else if i < 0 { + append_bytearray(#"2d", from_int(-i, digits)) + } else { + do_from_int( + quotient_integer(i, 10), + cons_bytearray(remainder_integer(i, 10) + 48, digits), + ) + } +} + +fn do_from_int(i: Int, digits: ByteArray) -> ByteArray { + if i <= 0 { + digits + } else { + do_from_int( + quotient_integer(i, 10), + cons_bytearray(remainder_integer(i, 10) + 48, digits), + ) + } +} + +test diagnostic_1() { + diagnostic(42) == @"42" +} + +test diagnostic_2() { + diagnostic(#"a1b2") == @"h'A1B2'" +} + +test diagnostic_3() { + diagnostic([1, 2, 3]) == @"[_ 1, 2, 3]" +} + +test diagnostic_4() { + diagnostic([]) == @"[]" +} + +test diagnostic_5() { + diagnostic((1, 2)) == @"[_ 1, 2]" +} + +test diagnostic_6() { + diagnostic((1, #"ff", 3)) == @"[_ 1, h'FF', 3]" +} + +test diagnostic_7() { + diagnostic([(1, #"ff")]) == @"[_ [_ 1, h'FF']]" +} + +test diagnostic_7_alt() { + diagnostic([Pair(1, #"ff")]) == @"{_ 1: h'FF' }" +} + +test diagnostic_8() { + diagnostic(Some(42)) == @"121([_ 42])" +} + +test diagnostic_9() { + diagnostic(None) == @"122([])" +} + +test diagnostic_10() { + let xs: List<(Int, Int)> = + [] + diagnostic(xs) == @"[]" +} + +test diagnostic_10_alt() { + let xs: Pairs = + [] + diagnostic(xs) == @"{}" +} + +type Foo { + foo: Bar, +} + +type Bar { + A + B(Int) +} + +test diagnostic_11() { + diagnostic(Foo { foo: A }) == @"121([_ 121([])])" +} + +test diagnostic_12() { + diagnostic(Foo { foo: B(42) }) == @"121([_ 122([_ 42])])" +} + +type Baz { + a0: Int, + b0: ByteArray, +} + +test diagnostic_13() { + diagnostic(Baz { a0: 14, b0: #"ff" }) == @"121([_ 14, h'FF'])" +} + +test diagnostic_14() { + diagnostic([0]) == @"[_ 0]" +} + +test diagnostic_15() { + diagnostic(-42) == @"-42" +} + +test diagnostic_16() { + diagnostic([-1, 0, 1]) == @"[_ -1, 0, 1]" +} diff --git a/example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/dict.ak b/example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/dict.ak new file mode 100644 index 00000000..d2804437 --- /dev/null +++ b/example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/dict.ak @@ -0,0 +1,1117 @@ +//// A module for working with bytearray dictionaries. +//// +//// ### Important +//// +//// Dictionaries are **ordered sets** of key-value pairs, which thus +//// preserve some invariants. Specifically, each key is only present once in +//// the dictionary and all keys are stored in ascending lexicographic order. +//// +//// These invariants allow for more optimized functions to operate on `Dict`, +//// but as a trade-offs, prevent `Dict` from being serializable. To recover a `Dict` +//// from an unknown `Data`, you must first recover an `Pairs` and use +//// `dict.from_ascending_list`. + +use aiken/builtin + +/// An opaque `Dict`. The type is opaque because the module maintains some +/// invariant, namely: there's only one occurrence of a given key in the dictionary. +/// +/// Note that the `key` parameter is a phantom-type, and only present as a +/// means of documentation. Keys can be any type, yet will need to comparable +/// to use functions like `insert`. +/// +/// See for example: +/// +/// ```aiken +/// pub type Value = +/// Dict> +/// ``` +pub opaque type Dict { + inner: Pairs, +} + +/// Create a new empty Dict +/// ```aiken +/// dict.to_pairs(dict.new()) == [] +/// ``` +pub fn new() -> Dict { + Dict { inner: [] } +} + +const foo = #"666f6f" + +const bar = #"626172" + +const baz = #"62617a" + +fn fixture_1() { + new() + |> insert(foo, 42) + |> insert(bar, 14) +} + +/// Remove a key-value pair from the dictionary. If the key is not found, no changes are made. +/// +/// ```aiken +/// let result = +/// dict.new() +/// |> dict.insert(key: "a", value: 100) +/// |> dict.insert(key: "b", value: 200) +/// |> dict.delete(key: "a") +/// |> dict.to_pairs() +/// +/// result == [Pair("b", 200)] +/// ``` +pub fn delete(self: Dict, key: ByteArray) -> Dict { + Dict { inner: do_delete(self.inner, key) } +} + +fn do_delete( + self: Pairs, + key k: ByteArray, +) -> Pairs { + when self is { + [] -> + [] + [Pair(k2, v2), ..rest] -> + if builtin.less_than_equals_bytearray(k, k2) { + if k == k2 { + rest + } else { + self + } + } else { + [Pair(k2, v2), ..do_delete(rest, k)] + } + } +} + +test delete_1() { + delete(new(), foo) == new() +} + +test delete_2() { + let m = + new() + |> insert(foo, 14) + delete(m, foo) == new() +} + +test delete_3() { + let m = + new() + |> insert(foo, 14) + delete(m, bar) == m +} + +test delete_4() { + let m = + new() + |> insert(foo, 14) + |> insert(bar, 14) + !has_key(delete(m, foo), foo) +} + +test delete_5() { + let m = + new() + |> insert(foo, 14) + |> insert(bar, 14) + has_key(delete(m, bar), foo) +} + +test delete_6() { + let m = + new() + |> insert("aaa", 1) + |> insert("bbb", 2) + |> insert("ccc", 3) + |> insert("ddd", 4) + |> insert("eee", 5) + |> insert("fff", 6) + |> insert("ggg", 7) + |> insert("hhh", 8) + |> insert("iii", 9) + |> insert("jjj", 10) + + delete(m, "bcd") == m +} + +/// Keep only the key-value pairs that pass the given predicate. +/// +/// ```aiken +/// let result = +/// dict.new() +/// |> dict.insert(key: "a", value: 100) +/// |> dict.insert(key: "b", value: 200) +/// |> dict.insert(key: "c", value: 300) +/// |> dict.filter(fn(k, _v) { k != "a" }) +/// |> dict.to_pairs() +/// +/// result == [Pair("b", 200), Pair("c", 300)] +/// ``` +pub fn filter( + self: Dict, + with: fn(ByteArray, value) -> Bool, +) -> Dict { + Dict { inner: do_filter(self.inner, with) } +} + +fn do_filter( + self: Pairs, + with: fn(ByteArray, value) -> Bool, +) -> Pairs { + when self is { + [] -> + [] + [Pair(k, v), ..rest] -> + if with(k, v) { + [Pair(k, v), ..do_filter(rest, with)] + } else { + do_filter(rest, with) + } + } +} + +test filter_1() { + filter(new(), fn(_, _) { True }) == new() +} + +test filter_2() { + let expected = + new() + |> insert(foo, 42) + filter(fixture_1(), fn(_, v) { v > 14 }) == expected +} + +test filter_3() { + let expected = + new() + |> insert(bar, 14) + filter(fixture_1(), fn(k, _) { k == bar }) == expected +} + +/// Finds a value in the dictionary, and returns the first key found to have that value. +/// +/// ```aiken +/// let result = +/// dict.new() +/// |> dict.insert(key: "a", value: 42) +/// |> dict.insert(key: "b", value: 14) +/// |> dict.insert(key: "c", value: 42) +/// |> dict.find(42) +/// +/// result == Some("a") +/// ``` +pub fn find(self: Dict, value v: value) -> Option { + do_find(self.inner, v) +} + +fn do_find(self: Pairs, value v: value) -> Option { + when self is { + [] -> None + [Pair(k2, v2), ..rest] -> + if v == v2 { + Some(k2) + } else { + do_find(rest, v) + } + } +} + +test find_1() { + find(new(), foo) == None +} + +test find_2() { + find( + new() + |> insert(foo, 14), + 14, + ) == Some(foo) +} + +test find_3() { + find( + new() + |> insert(foo, 14), + 42, + ) == None +} + +test find_4() { + find( + new() + |> insert(foo, 14) + |> insert(bar, 42) + |> insert(baz, 14), + 14, + ) == Some(baz) +} + +/// Fold over the key-value pairs in a dictionary. The fold direction follows keys +/// in ascending order and is done from right-to-left. +/// +/// ```aiken +/// let result = +/// dict.new() +/// |> dict.insert(key: "a", value: 100) +/// |> dict.insert(key: "b", value: 200) +/// |> dict.insert(key: "c", value: 300) +/// |> dict.foldr(0, fn(_k, v, r) { v + r }) +/// +/// result == 600 +/// ``` +pub fn foldr( + self: Dict, + zero: result, + with: fn(ByteArray, value, result) -> result, +) -> result { + do_foldr(self.inner, zero, with) +} + +fn do_foldr( + self: Pairs, + zero: result, + with: fn(ByteArray, value, result) -> result, +) -> result { + when self is { + [] -> zero + [Pair(k, v), ..rest] -> with(k, v, do_foldr(rest, zero, with)) + } +} + +test foldr_1() { + foldr(new(), 14, fn(_, _, _) { 42 }) == 14 +} + +test foldr_2() { + foldr(fixture_1(), zero: 0, with: fn(_, v, total) { v + total }) == 56 +} + +/// Fold over the key-value pairs in a dictionary. The fold direction follows keys +/// in ascending order and is done from left-to-right. +/// +/// ```aiken +/// let result = +/// dict.new() +/// |> dict.insert(key: "a", value: 100) +/// |> dict.insert(key: "b", value: 200) +/// |> dict.insert(key: "c", value: 300) +/// |> dict.foldl(0, fn(_k, v, r) { v + r }) +/// +/// result == 600 +/// ``` +pub fn foldl( + self: Dict, + zero: result, + with: fn(ByteArray, value, result) -> result, +) -> result { + do_foldl(self.inner, zero, with) +} + +fn do_foldl( + self: Pairs, + zero: result, + with: fn(ByteArray, value, result) -> result, +) -> result { + when self is { + [] -> zero + [Pair(k, v), ..rest] -> do_foldl(rest, with(k, v, zero), with) + } +} + +test fold_1() { + foldl(new(), 14, fn(_, _, _) { 42 }) == 14 +} + +test fold_2() { + foldl(fixture_1(), zero: 0, with: fn(_, v, total) { v + total }) == 56 +} + +/// Construct a dictionary from a list of key-value pairs. Note that when a key is present +/// multiple times, the first occurrence prevails. +/// +/// ```aiken +/// let pairs = [Pair("a", 100), Pair("c", 300), Pair("b", 200)] +/// +/// let result = +/// dict.from_pairs(pairs) +/// |> dict.to_pairs() +/// +/// result == [Pair("a", 100), Pair("b", 200), Pair("c", 300)] +/// ``` +pub fn from_pairs(self: Pairs) -> Dict { + Dict { inner: do_from_pairs(self) } +} + +fn do_from_pairs(xs: Pairs) -> Pairs { + when xs is { + [] -> + [] + [Pair(k, v), ..rest] -> do_insert(do_from_pairs(rest), k, v) + } +} + +test from_list_1() { + from_pairs([]) == new() +} + +test from_list_2() { + from_pairs([Pair(foo, 42), Pair(bar, 14)]) == from_pairs( + [Pair(bar, 14), Pair(foo, 42)], + ) +} + +test from_list_3() { + from_pairs([Pair(foo, 42), Pair(bar, 14)]) == fixture_1() +} + +test from_list_4() { + from_pairs([Pair(foo, 42), Pair(bar, 14), Pair(foo, 1337)]) == fixture_1() +} + +test bench_from_pairs() { + let dict = + from_pairs( + [ + Pair("bbba", 8), + Pair("bbab", 12), + Pair("aabb", 13), + Pair("aaab", 9), + Pair("bbbb", 16), + Pair("aaaa", 1), + Pair("aaba", 5), + Pair("abab", 10), + Pair("baba", 7), + Pair("baab", 11), + Pair("abaa", 2), + Pair("baaa", 3), + Pair("bbaa", 4), + Pair("babb", 15), + Pair("abbb", 14), + Pair("abba", 6), + ], + ) + + size(dict) == 16 +} + +/// Like ['from_list'](from_list), but from an already sorted list by ascending +/// keys. This function fails (i.e. halt the program execution) if the list isn't +/// sorted. +/// +/// ```aiken +/// let pairs = [Pair("a", 100), Pair("b", 200), Pair("c", 300)] +/// +/// let result = +/// dict.from_ascending_pairs(pairs) +/// |> dict.to_pairs() +/// +/// result == [Pair("a", 100), Pair("b", 200), Pair("c", 300)] +/// ``` +/// +/// This is meant to be used to turn a list constructed off-chain into a `Dict` +/// which has taken care of maintaining interval invariants. This function still +/// performs a sanity check on all keys to avoid silly mistakes. It is, however, +/// considerably faster than ['from_list'](from_list) +pub fn from_ascending_pairs(xs: Pairs) -> Dict { + let Void = check_ascending_list(xs) + Dict { inner: xs } +} + +fn check_ascending_list(xs: Pairs) { + when xs is { + [] -> Void + [_] -> Void + [Pair(x0, _), Pair(x1, _) as e, ..rest] -> + if builtin.less_than_bytearray(x0, x1) { + check_ascending_list([e, ..rest]) + } else { + fail @"keys in associative list aren't in ascending order" + } + } +} + +/// Like [`from_ascending_pairs`](#from_ascending_list) but fails if **any** +/// value doesn't satisfy the predicate. +/// +/// ```aiken +/// let pairs = [Pair("a", 100), Pair("b", 200), Pair("c", 300)] +/// +/// dict.from_ascending_pairs_with(pairs, fn(x) { x <= 250 }) // fail +/// ``` +pub fn from_ascending_pairs_with( + xs: Pairs, + predicate: fn(value) -> Bool, +) -> Dict { + let Void = check_ascending_pairs_with(xs, predicate) + Dict { inner: xs } +} + +fn check_ascending_pairs_with( + xs: Pairs, + predicate: fn(value) -> Bool, +) { + when xs is { + [] -> Void + [Pair(_, v)] -> + if predicate(v) { + Void + } else { + fail @"value doesn't satisfy predicate" + } + [Pair(x0, v0), Pair(x1, _) as e, ..rest] -> + if builtin.less_than_bytearray(x0, x1) { + if predicate(v0) { + check_ascending_pairs_with([e, ..rest], predicate) + } else { + fail @"value doesn't satisfy predicate" + } + } else { + fail @"keys in pairs aren't in ascending order" + } + } +} + +test bench_from_ascending_pairs() { + let dict = + from_ascending_pairs( + [ + Pair("aaaa", 1), + Pair("aaab", 9), + Pair("aaba", 5), + Pair("aabb", 13), + Pair("abaa", 2), + Pair("abab", 10), + Pair("abba", 6), + Pair("abbb", 14), + Pair("baaa", 3), + Pair("baab", 11), + Pair("baba", 7), + Pair("babb", 15), + Pair("bbaa", 4), + Pair("bbab", 12), + Pair("bbba", 8), + Pair("bbbb", 16), + ], + ) + + size(dict) == 16 +} + +/// Get a value in the dict by its key. +/// +/// ```aiken +/// let result = +/// dict.new() +/// |> dict.insert(key: "a", value: "Aiken") +/// |> dict.get(key: "a") +/// +/// result == Some("Aiken") +/// ``` +pub fn get(self: Dict, key: ByteArray) -> Option { + do_get(self.inner, key) +} + +fn do_get(self: Pairs, key k: ByteArray) -> Option { + when self is { + [] -> None + [Pair(k2, v), ..rest] -> + if builtin.less_than_equals_bytearray(k, k2) { + if k == k2 { + Some(v) + } else { + None + } + } else { + do_get(rest, k) + } + } +} + +test get_1() { + get(new(), foo) == None +} + +test get_2() { + let m = + new() + |> insert(foo, "Aiken") + |> insert(bar, "awesome") + get(m, key: foo) == Some("Aiken") +} + +test get_3() { + let m = + new() + |> insert(foo, "Aiken") + |> insert(bar, "awesome") + get(m, key: baz) == None +} + +test get_4() { + let m = + new() + |> insert("aaa", "1") + |> insert("bbb", "2") + |> insert("ccc", "3") + |> insert("ddd", "4") + |> insert("eee", "5") + |> insert("fff", "6") + |> insert("ggg", "7") + |> insert("hhh", "8") + |> insert("iii", "9") + |> insert("jjj", "10") + + get(m, "bcd") == None +} + +test get_5() { + let m = + new() + |> insert("aaa", "1") + |> insert("bbb", "2") + |> insert("ccc", "3") + |> insert("ddd", "4") + |> insert("eee", "5") + |> insert("fff", "6") + |> insert("ggg", "7") + |> insert("hhh", "8") + |> insert("iii", "9") + |> insert("jjj", "10") + + get(m, "kkk") == None +} + +/// Check if a key exists in the dictionary. +/// +/// ```aiken +/// let result = +/// dict.new() +/// |> dict.insert(key: "a", value: "Aiken") +/// |> dict.has_key("a") +/// +/// result == True +/// ``` +pub fn has_key(self: Dict, key k: ByteArray) -> Bool { + do_has_key(self.inner, k) +} + +fn do_has_key(self: Pairs, key k: ByteArray) -> Bool { + when self is { + [] -> False + [Pair(k2, _), ..rest] -> + if builtin.less_than_equals_bytearray(k, k2) { + k == k2 + } else { + do_has_key(rest, k) + } + } +} + +test has_key_1() { + !has_key(new(), foo) +} + +test has_key_2() { + has_key( + new() + |> insert(foo, 14), + foo, + ) +} + +test has_key_3() { + !has_key( + new() + |> insert(foo, 14), + bar, + ) +} + +test has_key_4() { + has_key( + new() + |> insert(foo, 14) + |> insert(bar, 42), + bar, + ) +} + +/// Insert a value in the dictionary at a given key. If the key already exists, its value is **overridden**. If you need ways to combine keys together, use (`insert_with`)[#insert_with]. +/// +/// ```aiken +/// let result = +/// dict.new() +/// |> dict.insert(key: "a", value: 1) +/// |> dict.insert(key: "b", value: 2) +/// |> dict.insert(key: "a", value: 3) +/// |> dict.to_pairs() +/// +/// result == [Pair("a", 3), Pair("b", 2)] +/// ``` +pub fn insert( + self: Dict, + key k: ByteArray, + value v: value, +) -> Dict { + Dict { inner: do_insert(self.inner, k, v) } +} + +fn do_insert( + self: Pairs, + key k: ByteArray, + value v: value, +) -> Pairs { + when self is { + [] -> + [Pair(k, v)] + [Pair(k2, v2), ..rest] -> + if builtin.less_than_bytearray(k, k2) { + [Pair(k, v), ..self] + } else { + if k == k2 { + [Pair(k, v), ..rest] + } else { + [Pair(k2, v2), ..do_insert(rest, k, v)] + } + } + } +} + +test insert_1() { + let m1 = + new() + |> insert(foo, 42) + let m2 = + new() + |> insert(foo, 14) + insert(m1, foo, 14) == m2 +} + +test insert_2() { + let m1 = + new() + |> insert(foo, 42) + let m2 = + new() + |> insert(bar, 14) + insert(m1, bar, 14) == insert(m2, foo, 42) +} + +/// Insert a value in the dictionary at a given key. When the key already exist, the provided +/// merge function is called. The value existing in the dictionary is passed as the second argument +/// to the merge function, and the new value is passed as the third argument. +/// +/// ```aiken +/// let sum = +/// fn (_k, a, b) { Some(a + b) } +/// +/// let result = +/// dict.new() +/// |> dict.insert_with(key: "a", value: 1, with: sum) +/// |> dict.insert_with(key: "b", value: 2, with: sum) +/// |> dict.insert_with(key: "a", value: 3, with: sum) +/// |> dict.to_pairs() +/// +/// result == [Pair("a", 4), Pair("b", 2)] +/// ``` +pub fn insert_with( + self: Dict, + key k: ByteArray, + value v: value, + with: fn(ByteArray, value, value) -> Option, +) -> Dict { + Dict { + inner: do_insert_with(self.inner, k, v, fn(k, v1, v2) { with(k, v2, v1) }), + } +} + +test insert_with_1() { + let sum = + fn(_k, a, b) { Some(a + b) } + + let result = + new() + |> insert_with(key: "foo", value: 1, with: sum) + |> insert_with(key: "bar", value: 2, with: sum) + |> to_pairs() + + result == [Pair("bar", 2), Pair("foo", 1)] +} + +test insert_with_2() { + let sum = + fn(_k, a, b) { Some(a + b) } + + let result = + new() + |> insert_with(key: "foo", value: 1, with: sum) + |> insert_with(key: "bar", value: 2, with: sum) + |> insert_with(key: "foo", value: 3, with: sum) + |> to_pairs() + + result == [Pair("bar", 2), Pair("foo", 4)] +} + +test insert_with_3() { + let with = + fn(k, a, _b) { + if k == "foo" { + Some(a) + } else { + None + } + } + + let result = + new() + |> insert_with(key: "foo", value: 1, with: with) + |> insert_with(key: "bar", value: 2, with: with) + |> insert_with(key: "foo", value: 3, with: with) + |> insert_with(key: "bar", value: 4, with: with) + |> to_pairs() + + result == [Pair("foo", 1)] +} + +/// Efficiently checks whether a dictionary is empty. +/// ```aiken +/// dict.is_empty(dict.new()) == True +/// ``` +pub fn is_empty(self: Dict) -> Bool { + when self.inner is { + [] -> True + _ -> False + } +} + +test is_empty_1() { + is_empty(new()) +} + +/// Extract all the keys present in a given `Dict`. +/// +/// ```aiken +/// let result = +/// dict.new() +/// |> dict.insert("a", 14) +/// |> dict.insert("b", 42) +/// |> dict.insert("a", 1337) +/// |> dict.keys() +/// +/// result == ["a", "b"] +/// ``` +pub fn keys(self: Dict) -> List { + do_keys(self.inner) +} + +fn do_keys(self: Pairs) -> List { + when self is { + [] -> + [] + [Pair(k, _), ..rest] -> + [k, ..do_keys(rest)] + } +} + +test keys_1() { + keys(new()) == [] +} + +test keys_2() { + keys( + new() + |> insert(foo, 0) + |> insert(bar, 0), + ) == [bar, foo] +} + +/// Apply a function to all key-value pairs in a Dict. +/// +/// ```aiken +/// let result = +/// dict.new() +/// |> dict.insert("a", 100) +/// |> dict.insert("b", 200) +/// |> dict.insert("c", 300) +/// |> dict.map(fn(_k, v) { v * 2 }) +/// |> dict.to_pairs() +/// +/// result == [Pair("a", 200), Pair("b", 400), Pair("c", 600)] +/// ``` +pub fn map(self: Dict, with: fn(ByteArray, a) -> b) -> Dict { + Dict { inner: do_map(self.inner, with) } +} + +fn do_map( + self: Pairs, + with: fn(ByteArray, a) -> b, +) -> Pairs { + when self is { + [] -> + [] + [Pair(k, v), ..rest] -> + [Pair(k, with(k, v)), ..do_map(rest, with)] + } +} + +test map_1() { + let result = + fixture_1() + |> map(with: fn(k, _) { k }) + get(result, foo) == Some(foo) +} + +test map_2() { + let result = + fixture_1() + |> map(with: fn(_, v) { v + 1 }) + get(result, foo) == Some(43) && size(result) == size(fixture_1()) +} + +/// Get the inner list holding the dictionary data. +/// +/// ```aiken +/// let result = +/// dict.new() +/// |> dict.insert("a", 100) +/// |> dict.insert("b", 200) +/// |> dict.insert("c", 300) +/// |> dict.to_pairs() +/// +/// result == [Pair("a", 100), Pair("b", 200), Pair("c", 300)] +/// ``` +pub fn to_pairs(self: Dict) -> Pairs { + self.inner +} + +test to_list_1() { + to_pairs(new()) == [] +} + +test to_list_2() { + to_pairs(fixture_1()) == [Pair(bar, 14), Pair(foo, 42)] +} + +/// Return the number of key-value pairs in the dictionary. +/// +/// ```aiken +/// let result = +/// dict.new() +/// |> dict.insert("a", 100) +/// |> dict.insert("b", 200) +/// |> dict.insert("c", 300) +/// |> dict.size() +/// +/// result == 3 +/// ``` +pub fn size(self: Dict) -> Int { + do_size(self.inner) +} + +fn do_size(self: Pairs) -> Int { + when self is { + [] -> 0 + [_, ..rest] -> 1 + do_size(rest) + } +} + +test size_1() { + size(new()) == 0 +} + +test size_2() { + size( + new() + |> insert(foo, 14), + ) == 1 +} + +test size_3() { + size( + new() + |> insert(foo, 14) + |> insert(bar, 42), + ) == 2 +} + +/// Combine two dictionaries. If the same key exist in both the left and +/// right dictionary, values from the left are preferred (i.e. left-biaised). +/// +/// ```aiken +/// let left_dict = dict.from_pairs([Pair("a", 100), Pair("b", 200)]) +/// let right_dict = dict.from_pairs([Pair("a", 150), Pair("c", 300)]) +/// +/// let result = +/// dict.union(left_dict, right_dict) |> dict.to_pairs() +/// +/// result == [Pair("a", 100), Pair("b", 200), Pair("c", 300)] +/// ``` +pub fn union( + left: Dict, + right: Dict, +) -> Dict { + Dict { inner: do_union(left.inner, right.inner) } +} + +fn do_union( + left: Pairs, + right: Pairs, +) -> Pairs { + when left is { + [] -> right + [Pair(k, v), ..rest] -> do_union(rest, do_insert(right, k, v)) + } +} + +test union_1() { + union(fixture_1(), new()) == fixture_1() +} + +test union_2() { + union(new(), fixture_1()) == fixture_1() +} + +test union_3() { + let left = + new() + |> insert(foo, 14) + let right = + new() + |> insert(bar, 42) + |> insert(baz, 1337) + union(left, right) == from_pairs( + [Pair(foo, 14), Pair(baz, 1337), Pair(bar, 42)], + ) +} + +test union_4() { + let left = + new() + |> insert(foo, 14) + let right = + new() + |> insert(bar, 42) + |> insert(foo, 1337) + union(left, right) == from_pairs([Pair(foo, 14), Pair(bar, 42)]) +} + +/// Like [`union`](#union) but allows specifying the behavior to adopt when a key is present +/// in both dictionaries. The first value received correspond to the value in the left +/// dictionnary, whereas the second argument corresponds to the value in the right dictionnary. +/// +/// When passing `None`, the value is removed and not present in the union. +/// +/// ```aiken +/// let left_dict = dict.from_pairs([Pair("a", 100), Pair("b", 200)]) +/// let right_dict = dict.from_pairs([Pair("a", 150), Pair("c", 300)]) +/// +/// let result = +/// dict.union_with( +/// left_dict, +/// right_dict, +/// fn(_k, v1, v2) { Some(v1 + v2) }, +/// ) +/// |> dict.to_pairs() +/// +/// result == [Pair("a", 250), Pair("b", 200), Pair("c", 300)] +/// ``` +pub fn union_with( + left: Dict, + right: Dict, + with: fn(ByteArray, value, value) -> Option, +) -> Dict { + Dict { inner: do_union_with(left.inner, right.inner, with) } +} + +fn do_union_with( + left: Pairs, + right: Pairs, + with: fn(ByteArray, value, value) -> Option, +) -> Pairs { + when left is { + [] -> right + [Pair(k, v), ..rest] -> + do_union_with(rest, do_insert_with(right, k, v, with), with) + } +} + +fn do_insert_with( + self: Pairs, + key k: ByteArray, + value v: value, + with: fn(ByteArray, value, value) -> Option, +) -> Pairs { + when self is { + [] -> + [Pair(k, v)] + [Pair(k2, v2), ..rest] -> + if builtin.less_than_bytearray(k, k2) { + [Pair(k, v), ..self] + } else { + if k == k2 { + when with(k, v, v2) is { + Some(combined) -> + [Pair(k, combined), ..rest] + None -> rest + } + } else { + [Pair(k2, v2), ..do_insert_with(rest, k, v, with)] + } + } + } +} + +test union_with_1() { + let left = + new() + |> insert(foo, 14) + + let right = + new() + |> insert(bar, 42) + |> insert(foo, 1337) + + let result = union_with(left, right, with: fn(_, l, r) { Some(l + r) }) + + result == from_pairs([Pair(foo, 1351), Pair(bar, 42)]) +} + +/// Extract all the values present in a given `Dict`. +/// +/// ```aiken +/// let result = +/// dict.new() +/// |> dict.insert("a", 14) +/// |> dict.insert("b", 42) +/// |> dict.insert("c", 1337) +/// |> dict.values() +/// +/// result == [1337, 42] +/// ``` +pub fn values(self: Dict) -> List { + do_values(self.inner) +} + +fn do_values(self: Pairs) -> List { + when self is { + [] -> + [] + [Pair(_, v), ..rest] -> + [v, ..do_values(rest)] + } +} + +test values_1() { + values(new()) == [] +} + +test values_2() { + values( + new() + |> insert(foo, 3) + |> insert(bar, 4), + ) == [4, 3] +} diff --git a/example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/hash.ak b/example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/hash.ak new file mode 100644 index 00000000..4f860271 --- /dev/null +++ b/example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/hash.ak @@ -0,0 +1,83 @@ +//// This module defines `Hash`, a self-documenting type-alias with a +//// phantom-type for readability. +//// +//// On-chain, any hash digest value is represented as a plain 'ByteArray'. +//// Though in practice, hashes come from different sources and have +//// different semantics. +//// +//// Hence, while this type-alias doesn't provide any strong type-guarantees, +//// it helps writing functions signatures with more meaningful types than mere +//// 'ByteArray'. +//// +//// Compare for example: +//// +//// ```aiken +//// pub type Credential { +//// VerificationKeyCredential(ByteArray) +//// ScriptCredential(ByteArray) +//// } +//// ``` +//// +//// with +//// +//// ```aiken +//// pub type Credential { +//// VerificationKeyCredential(Hash) +//// ScriptCredential(Hash) +//// } +//// ``` +//// +//// Both are strictly equivalent, but the second reads much better. + +use aiken/builtin + +/// A `Hash` is nothing more than a `ByteArray`, but it carries extra +/// information for readability. +pub type Hash = + ByteArray + +/// A blake2b-224 hash algorithm. +/// +/// Typically used for: +/// +/// - [`Credential`](../aiken/transaction/credential.html#Credential) +/// - [`PolicyId`](../aiken/transaction/value.html#PolicyId) +/// +/// Note: there's no function to calculate blake2b-224 hash digests on-chain. +pub opaque type Blake2b_224 { + Blake2b_224 +} + +/// A blake2b-256 hash algorithm. +/// +/// Typically used for: +/// +/// - [`TransactionId`](../aiken/transaction.html#TransactionId) +pub opaque type Blake2b_256 { + Blake2b_256 +} + +/// Compute the blake2b-256 hash digest of some data. +pub fn blake2b_256(bytes: ByteArray) -> Hash { + builtin.blake2b_256(bytes) +} + +/// A SHA2-256 hash algorithm. +pub opaque type Sha2_256 { + Sha2_256 +} + +/// Compute the sha2-256 hash digest of some data. +pub fn sha2_256(bytes: ByteArray) -> Hash { + builtin.sha2_256(bytes) +} + +/// A SHA3-256 hash algorithm. +pub opaque type Sha3_256 { + Sha3_256 +} + +/// Compute the sha3-256 hash digest of some data. +pub fn sha3_256(bytes: ByteArray) -> Hash { + builtin.sha3_256(bytes) +} diff --git a/example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/int.ak b/example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/int.ak new file mode 100644 index 00000000..fd602b62 --- /dev/null +++ b/example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/int.ak @@ -0,0 +1,80 @@ +use aiken/bytearray +use aiken/math +use aiken/option + +/// Compare two integers. +/// +/// ```aiken +/// int.compare(14, 42) == Less +/// int.compare(14, 14) == Equal +/// int.compare(42, 14) == Greater +/// ``` +pub fn compare(left: Int, right: Int) -> Ordering { + if left < right { + Less + } else if left > right { + Greater + } else { + Equal + } +} + +/// Parse an integer from a utf-8 encoded 'ByteArray', when possible. +/// +/// ```aiken +/// int.from_utf8("14") == Some(14) +/// int.from_utf8("-42") == Some(-42) +/// int.from_utf8("007") == Some(7) +/// int.from_utf8("foo") == None +/// int.from_utf8("1.0") == None +/// int.from_utf8("1-2") == None +/// ``` +pub fn from_utf8(bytes: ByteArray) -> Option { + bytes + |> bytearray.foldr( + Some((0, 0)), + fn(byte, st) { + when st is { + None -> None + Some((n, e)) -> + if byte < 48 || byte > 57 { + if byte == 45 { + Some((-n, 0)) + } else { + None + } + } else if n < 0 { + None + } else { + let digit = byte - 48 + Some((n + digit * math.pow(10, e), e + 1)) + } + } + }, + ) + |> option.map(fn(tuple) { tuple.1st }) +} + +test from_utf8_1() { + from_utf8("0017") == Some(17) +} + +test from_utf8_2() { + from_utf8("42") == Some(42) +} + +test from_utf8_3() { + from_utf8("1337") == Some(1337) +} + +test from_utf8_4() { + from_utf8("-14") == Some(-14) +} + +test from_utf8_5() { + from_utf8("foo") == None +} + +test from_utf8_6() { + from_utf8("1-2") == None +} diff --git a/example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/interval.ak b/example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/interval.ak new file mode 100644 index 00000000..323d5a9a --- /dev/null +++ b/example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/interval.ak @@ -0,0 +1,680 @@ +//// In a eUTxO-based blockchain like Cardano, the management of time can be +//// finicky. +//// +//// Indeed, in order to maintain a complete determinism in the execution of +//// scripts, it is impossible to introduce a notion of _"current time"_ since +//// the execution would then depend on factor that are external to the +//// transaction itself: the ineluctable stream of time flowing in our universe. +//// +//// Hence, to work around that, we typically define time intervals, which gives +//// window -- a.k.a intervals -- within which the transaction can be executed. +//// From within a script, it isn't possible to know when exactly the script is +//// executed, but we can reason about the interval bounds to validate pieces of +//// logic. + +/// A type to represent intervals of values. Interval are inhabited by a type +/// `a` which is useful for non-infinite intervals that have a finite +/// lower-bound and/or upper-bound. +/// +/// This allows to represent all kind of mathematical intervals: +/// +/// ```aiken +/// // [1; 10] +/// let i0: Interval = Interval +/// { lower_bound: +/// IntervalBound { bound_type: Finite(1), is_inclusive: True } +/// , upper_bound: +/// IntervalBound { bound_type: Finite(10), is_inclusive: True } +/// } +/// ``` +/// +/// ```aiken +/// // (20; infinity) +/// let i1: Interval = Interval +/// { lower_bound: +/// IntervalBound { bound_type: Finite(20), is_inclusive: False } +/// , upper_bound: +/// IntervalBound { bound_type: PositiveInfinity, is_inclusive: False } +/// } +/// ``` +pub type Interval
{ + lower_bound: IntervalBound, + upper_bound: IntervalBound, +} + +/// An interval bound, either inclusive or exclusive. +pub type IntervalBound { + bound_type: IntervalBoundType, + is_inclusive: Bool, +} + +/// Return the highest bound of the two. +/// +/// ```aiken +/// let ib1 = IntervalBound { bound_type: Finite(0), is_inclusive: False } +/// let ib2 = IntervalBound { bound_type: Finite(1), is_inclusive: False } +/// +/// interval.max(ib1, ib2) == ib2 +/// ``` +pub fn max( + left: IntervalBound, + right: IntervalBound, +) -> IntervalBound { + when compare_bound(left, right) is { + Less -> right + Equal -> left + Greater -> left + } +} + +/// Return the smallest bound of the two. +/// +/// ```aiken +/// let ib1 = IntervalBound { bound_type: Finite(0), is_inclusive: False } +/// let ib2 = IntervalBound { bound_type: Finite(1), is_inclusive: False } +/// +/// interval.min(ib1, ib2) == ib1 +/// ``` +pub fn min( + left: IntervalBound, + right: IntervalBound, +) -> IntervalBound { + when compare_bound(left, right) is { + Less -> left + Equal -> left + Greater -> right + } +} + +fn compare_bound( + left: IntervalBound, + right: IntervalBound, +) -> Ordering { + when compare_bound_type(left.bound_type, right.bound_type) is { + Less -> Less + Greater -> Greater + Equal -> + if left.is_inclusive == right.is_inclusive { + Equal + } else if left.is_inclusive { + Greater + } else { + Less + } + } +} + +/// A type of interval bound. Where finite, a value of type `a` must be +/// provided. `a` will typically be an `Int`, representing a number of seconds or +/// milliseconds. +pub type IntervalBoundType { + NegativeInfinity + Finite(a) + PositiveInfinity +} + +fn compare_bound_type( + left: IntervalBoundType, + right: IntervalBoundType, +) -> Ordering { + when left is { + NegativeInfinity -> + when right is { + NegativeInfinity -> Equal + _ -> Less + } + PositiveInfinity -> + when right is { + PositiveInfinity -> Equal + _ -> Greater + } + Finite(left) -> + when right is { + NegativeInfinity -> Greater + PositiveInfinity -> Less + Finite(right) -> + if left < right { + Less + } else if left == right { + Equal + } else { + Greater + } + } + } +} + +// TODO: Replace 'Int' with a generic 'a' once we have comparable traits. + +/// Checks whether an element is contained within the interval. +/// +/// ```aiken +/// let iv = +/// Interval { +/// lower_bound: IntervalBound { +/// bound_type: Finite(14), +/// is_inclusive: True +/// }, +/// upper_bound: IntervalBound { +/// bound_type: Finite(42), +/// is_inclusive: False +/// }, +/// } +/// +/// interval.contains(iv, 25) == True +/// interval.contains(iv, 0) == False +/// interval.contains(iv, 14) == True +/// interval.contains(iv, 42) == False +/// ``` +pub fn contains(self: Interval, elem: Int) -> Bool { + let is_greater_than_lower_bound = + when self.lower_bound.bound_type is { + NegativeInfinity -> True + Finite(lower_bound) -> + if self.lower_bound.is_inclusive { + elem >= lower_bound + } else { + elem > lower_bound + } + PositiveInfinity -> False + } + + let is_smaller_than_upper_bound = + when self.upper_bound.bound_type is { + NegativeInfinity -> False + Finite(upper_bound) -> + if self.upper_bound.is_inclusive { + elem <= upper_bound + } else { + elem < upper_bound + } + PositiveInfinity -> True + } + + is_greater_than_lower_bound && is_smaller_than_upper_bound +} + +test contains_1() { + let iv = everything() + contains(iv, 14) +} + +test contains_2() { + let iv = entirely_before(15) + contains(iv, 14) +} + +test contains_3() { + let iv = before(14) + contains(iv, 14) +} + +test contains_4() { + let iv = entirely_before(14) + !contains(iv, 14) +} + +test contains_5() { + let iv = entirely_after(13) + contains(iv, 14) +} + +test contains_6() { + let iv = after(14) + contains(iv, 14) +} + +test contains_7() { + let iv = entirely_after(14) + !contains(iv, 14) +} + +test contains_8() { + let iv = between(42, 1337) + !contains(iv, 14) +} + +test contains_9() { + let iv = between(0, 42) + contains(iv, 14) +} + +test contains_10() { + let iv = between(0, 42) + contains(iv, 42) +} + +test contains_11() { + let iv = entirely_between(0, 42) + !contains(iv, 0) +} + +test contains_12() { + let iv = empty() + !contains(iv, 14) +} + +/// Create an interval that contains every possible values. i.e. (-INF, +INF) +/// +/// ```aiken +/// interval.contains(everything(), 0) == True +/// interval.contains(everything(), 1000) == True +/// ``` +pub fn everything() -> Interval { + Interval { + lower_bound: IntervalBound { + bound_type: NegativeInfinity, + is_inclusive: True, + }, + upper_bound: IntervalBound { + bound_type: PositiveInfinity, + is_inclusive: True, + }, + } +} + +/// Create an empty interval that contains no value. +/// +/// ```aiken +/// interval.contains(empty(), 0) == False +/// interval.contains(empty(), 1000) == False +/// ``` +pub fn empty() -> Interval { + Interval { + lower_bound: IntervalBound { + bound_type: PositiveInfinity, + is_inclusive: True, + }, + upper_bound: IntervalBound { + bound_type: NegativeInfinity, + is_inclusive: True, + }, + } +} + +/// Create an interval that includes all values between two bounds, including the bounds. i.e. [lower_bound, upper_bound] +/// +/// ```aiken +/// interval.between(10, 100) == Interval { +/// lower_bound: IntervalBound { bound_type: Finite(10), is_inclusive: True }, +/// upper_bound: IntervalBound { bound_type: Finite(100), is_inclusive: True }, +/// } +/// ``` +pub fn between(lower_bound: a, upper_bound: a) -> Interval { + Interval { + lower_bound: IntervalBound { + bound_type: Finite(lower_bound), + is_inclusive: True, + }, + upper_bound: IntervalBound { + bound_type: Finite(upper_bound), + is_inclusive: True, + }, + } +} + +/// Create an interval that includes all values between two bounds, excluding the bounds. i.e. (lower_bound, upper_bound) +/// +/// ```aiken +/// interval.entirely_between(10, 100) == Interval { +/// lower_bound: IntervalBound { bound_type: Finite(10), is_inclusive: False }, +/// upper_bound: IntervalBound { bound_type: Finite(100), is_inclusive: False }, +/// } +/// ``` +pub fn entirely_between(lower_bound: a, upper_bound: a) -> Interval { + Interval { + lower_bound: IntervalBound { + bound_type: Finite(lower_bound), + is_inclusive: False, + }, + upper_bound: IntervalBound { + bound_type: Finite(upper_bound), + is_inclusive: False, + }, + } +} + +/// Create an interval that includes all values greater than the given bound. i.e [lower_bound, +INF) +/// +/// ```aiken +/// interval.after(10) == Interval { +/// lower_bound: IntervalBound { bound_type: Finite(10), is_inclusive: True }, +/// upper_bound: IntervalBound { bound_type: PositiveInfinity, is_inclusive: True }, +/// } +/// ``` +pub fn after(lower_bound: a) -> Interval { + Interval { + lower_bound: IntervalBound { + bound_type: Finite(lower_bound), + is_inclusive: True, + }, + upper_bound: IntervalBound { + bound_type: PositiveInfinity, + is_inclusive: True, + }, + } +} + +// TODO: Replace 'Int' with a generic 'a' once we have comparable traits. + +/// Check whether the interval is entirely after the point "a" +/// +/// ```aiken +/// interval.is_entirely_after(interval.after(10), 5) == True +/// interval.is_entirely_after(interval.after(10), 10) == False +/// interval.is_entirely_after(interval.after(10), 15) == False +/// interval.is_entirely_after(interval.between(10, 20), 30) == False +/// interval.is_entirely_after(interval.between(10, 20), 5) == True +pub fn is_entirely_after(self: Interval, point: Int) -> Bool { + when self.lower_bound.bound_type is { + Finite(low) -> + if self.lower_bound.is_inclusive { + point < low + } else { + point <= low + } + _ -> False + } +} + +test is_entirely_after_1() { + is_entirely_after(after(10), 5) +} + +test is_entirely_after_2() { + !is_entirely_after(after(10), 10) +} + +test is_entirely_after_3() { + !is_entirely_after(after(10), 15) +} + +test is_entirely_after_4() { + !is_entirely_after(between(10, 20), 30) +} + +test is_entirely_after_5() { + is_entirely_after(between(10, 20), 5) +} + +test is_entirely_after_6() { + is_entirely_after(entirely_after(10), 10) +} + +test is_entirely_after_7() { + !is_entirely_after(before(10), 5) +} + +test is_entirely_after_8() { + !is_entirely_after(before(10), 15) +} + +test is_entirely_after_9() { + !is_entirely_after(entirely_before(10), 5) +} + +// TODO: Replace 'Int' with a generic 'a' once we have comparable traits. + +/// Check whether the interval is entirely before the point "a" +/// +/// ```aiken +/// interval.is_entirely_before(interval.before(10), 15) == True +/// interval.is_entirely_before(interval.before(10), 10) == False +/// interval.is_entirely_before(interval.before(10), 5) == False +/// interval.is_entirely_before(interval.between(10, 20), 30) == True +/// interval.is_entirely_before(interval.between(10, 20), 5) == False +pub fn is_entirely_before(self: Interval, point: Int) -> Bool { + when self.upper_bound.bound_type is { + Finite(hi) -> + if self.upper_bound.is_inclusive { + hi < point + } else { + hi <= point + } + _ -> False + } +} + +test is_entirely_before_1() { + is_entirely_before(before(10), 15) +} + +test is_entirely_before_2() { + !is_entirely_before(before(10), 10) +} + +test is_entirely_before_3() { + !is_entirely_before(before(10), 5) +} + +test is_entirely_before_4() { + is_entirely_before(between(10, 20), 30) +} + +test is_entirely_before_5() { + !is_entirely_before(between(10, 20), 5) +} + +test is_entirely_before_6() { + is_entirely_before(entirely_before(10), 10) +} + +test is_entirely_before_7() { + !is_entirely_before(after(10), 15) +} + +test is_entirely_before_8() { + !is_entirely_before(after(10), 5) +} + +test is_entirely_before_9() { + !is_entirely_before(entirely_after(10), 5) +} + +/// Create an interval that includes all values after (and not including) the given bound. i.e (lower_bound, +INF) +/// +/// ```aiken +/// interval.entirely_after(10) == Interval { +/// lower_bound: IntervalBound { bound_type: Finite(10), is_inclusive: False }, +/// upper_bound: IntervalBound { bound_type: PositiveInfinity, is_inclusive: True }, +/// } +/// ``` +pub fn entirely_after(lower_bound: a) -> Interval { + Interval { + lower_bound: IntervalBound { + bound_type: Finite(lower_bound), + is_inclusive: False, + }, + upper_bound: IntervalBound { + bound_type: PositiveInfinity, + is_inclusive: True, + }, + } +} + +/// Create an interval that includes all values before (and including) the given bound. i.e (-INF, upper_bound] +/// +/// ```aiken +/// interval.before(100) == Interval { +/// lower_bound: IntervalBound { bound_type: NegativeInfinity, is_inclusive: True }, +/// upper_bound: IntervalBound { bound_type: Finite(100), is_inclusive: True }, +/// } +/// ``` +pub fn before(upper_bound: a) -> Interval { + Interval { + lower_bound: IntervalBound { + bound_type: NegativeInfinity, + is_inclusive: True, + }, + upper_bound: IntervalBound { + bound_type: Finite(upper_bound), + is_inclusive: True, + }, + } +} + +/// Create an interval that includes all values before (and not including) the given bound. i.e (-INF, upper_bound) +/// +/// ```aiken +/// interval.entirely_before(10) == Interval { +/// lower_bound: IntervalBound { bound_type: NegativeInfinity, is_inclusive: True }, +/// upper_bound: IntervalBound { bound_type: Finite(10), is_inclusive: False }, +/// } +/// ``` +pub fn entirely_before(upper_bound: a) -> Interval { + Interval { + lower_bound: IntervalBound { + bound_type: NegativeInfinity, + is_inclusive: True, + }, + upper_bound: IntervalBound { + bound_type: Finite(upper_bound), + is_inclusive: False, + }, + } +} + +/// Tells whether an interval is empty; i.e. that is contains no value. +/// +/// ```aiken +/// let iv1 = interval.empty() +/// +/// let iv2 = Interval { +/// lower_bound: IntervalBound { bound_type: Finite(0), is_inclusive: False }, +/// upper_bound: IntervalBound { bound_type: Finite(0), is_inclusive: False }, +/// } +/// +/// let iv3 = Interval { +/// lower_bound: IntervalBound { bound_type: Finite(0), is_inclusive: False }, +/// upper_bound: IntervalBound { bound_type: Finite(100), is_inclusive: False }, +/// } +/// +/// interval.is_empty(iv1) == True +/// interval.is_empty(iv2) == True +/// interval.is_empty(iv3) == False +/// +/// // Note: Two empty intervals are not necessarily equal. +/// iv1 != iv2 +/// ``` +pub fn is_empty(self: Interval) -> Bool { + let ordering = + compare_bound_type(self.lower_bound.bound_type, self.upper_bound.bound_type) + + when ordering is { + Greater -> True + Equal -> !(self.lower_bound.is_inclusive && self.upper_bound.is_inclusive) + Less -> { + let is_open_interval = + !self.lower_bound.is_inclusive && !self.upper_bound.is_inclusive + if is_open_interval { + when (self.lower_bound.bound_type, self.upper_bound.bound_type) is { + (Finite(lower_bound), Finite(upper_bound)) -> + lower_bound + 1 == upper_bound + _ -> False + } + } else { + False + } + } + } +} + +/// Computes the largest interval contains in the two given intervals, if any. +/// +/// ```aiken +/// let iv1 = interval.between(0, 10) +/// let iv2 = interval.between(2, 14) +/// interval.intersection(iv1, iv2) == interval.between(2, 10) +/// +/// let iv1 = interval.entirely_before(10) +/// let iv2 = interval.entirely_after(0) +/// interval.intersection(iv1, iv2) == interval.entirely_between(0, 10) +/// +/// let iv1 = interval.between(0, 1) +/// let iv2 = interval.between(2, 3) +/// interval.intersection(iv1, iv2) |> interval.is_empty +/// ``` +pub fn intersection(iv1: Interval, iv2: Interval) -> Interval { + Interval { + lower_bound: max(iv1.lower_bound, iv2.lower_bound), + upper_bound: min(iv1.upper_bound, iv2.upper_bound), + } +} + +test intersection_1() { + let iv1 = between(0, 10) + let iv2 = between(2, 14) + intersection(iv1, iv2) == between(2, 10) +} + +test intersection_2() { + let iv1 = between(0, 1) + let iv2 = between(1, 2) + intersection(iv1, iv2) == between(1, 1) +} + +test intersection_3() { + let iv1 = between(0, 1) + let iv2 = entirely_between(1, 2) + intersection(iv1, iv2) + |> is_empty +} + +test intersection_4() { + let iv1 = entirely_between(0, 1) + let iv2 = entirely_between(1, 2) + intersection(iv1, iv2) + |> is_empty +} + +test intersection_5() { + let iv1 = between(0, 10) + let iv2 = before(4) + intersection(iv1, iv2) == between(0, 4) +} + +test intersection_6() { + let iv1 = entirely_before(10) + let iv2 = entirely_after(0) + intersection(iv1, iv2) == entirely_between(0, 10) +} + +/// Computes the smallest interval containing the two given intervals, if any +/// +/// ```aiken +/// let iv1 = between(0, 10) +/// let iv2 = between(2, 14) +/// hull(iv1, iv2) == between(0, 14) +/// +/// let iv1 = between(5, 10) +/// let iv2 = before(0) +/// hull(iv1, iv2) == before(10) +/// +/// let iv1 = entirely_after(0) +/// let iv2 = between(10, 42) +/// hull(iv1, iv2) = entirely_after(0) +/// ``` +pub fn hull(iv1: Interval, iv2: Interval) -> Interval { + Interval { + lower_bound: min(iv1.lower_bound, iv2.lower_bound), + upper_bound: max(iv1.upper_bound, iv2.upper_bound), + } +} + +test hull_1() { + let iv1 = between(0, 10) + let iv2 = between(2, 14) + hull(iv1, iv2) == between(0, 14) +} + +test hull_2() { + let iv1 = between(5, 10) + let iv2 = before(0) + hull(iv1, iv2) == before(10) +} + +test hull_3() { + let iv1 = entirely_after(0) + let iv2 = between(10, 42) + hull(iv1, iv2) == entirely_after(0) +} diff --git a/example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/list.ak b/example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/list.ak new file mode 100644 index 00000000..b8bb4b8c --- /dev/null +++ b/example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/list.ak @@ -0,0 +1,1337 @@ +use aiken/builtin +use aiken/bytearray +use aiken/int + +/// Determine if all elements of the list satisfy the given predicate. +/// +/// Note: an empty list always satisfies the predicate. +/// +/// ```aiken +/// list.all([], fn(n) { n > 0 }) == True +/// list.all([1, 2, 3], fn(n) { n > 0 }) == True +/// list.all([1, 2, 3], fn(n) { n == 2 }) == False +/// ``` +pub fn all(self: List, predicate: fn(a) -> Bool) -> Bool { + when self is { + [] -> True + [x, ..xs] -> predicate(x) && all(xs, predicate) + } +} + +test all_1() { + all([1, 2, 3], fn(n) { n > 0 }) == True +} + +test all_2() { + all([1, 2, 3], fn(n) { n > 42 }) == False +} + +test all_3() { + all([], fn(n) { n == 42 }) == True +} + +/// Determine if at least one element of the list satisfies the given predicate. +/// +/// Note: an empty list never satisfies the predicate. +/// +/// ```aiken +/// list.any([], fn(n) { n > 2 }) == False +/// list.any([1, 2, 3], fn(n) { n > 0 }) == True +/// list.any([1, 2, 3], fn(n) { n == 2 }) == True +/// list.any([1, 2, 3], fn(n) { n < 0 }) == False +/// ``` +pub fn any(self: List, predicate: fn(a) -> Bool) -> Bool { + when self is { + [] -> False + [x, ..xs] -> predicate(x) || any(xs, predicate) + } +} + +test any_1() { + any([1, 2, 3], fn(n) { n > 0 }) == True +} + +test any_2() { + any([1, 2, 3], fn(n) { n > 42 }) == False +} + +test any_3() { + any([], fn(n) { n == 42 }) == False +} + +/// Count how many items in the list satisfy the given predicate. +/// +/// ```aiken +/// list.count([], fn(a) { a > 2}) == 0 +/// list.count([1, 2, 3], fn(a) { n > 0 }) == 3 +/// list.count([1, 2, 3], fn(a) { n >= 2 }) == 2 +/// list.count([1, 2, 3], fn(a) { n > 5 }) == 0 +/// ``` +pub fn count(self: List, predicate: fn(a) -> Bool) -> Int { + foldr( + self, + 0, + fn(item, total) { + if predicate(item) { + total + 1 + } else { + total + } + }, + ) +} + +test count_empty() { + count([], fn(a) { a > 2 }) == 0 +} + +test count_all() { + count([1, 2, 3], fn(a) { a > 0 }) == 3 +} + +test count_some() { + count([1, 2, 3], fn(a) { a >= 2 }) == 2 +} + +test count_none() { + count([1, 2, 3], fn(a) { a > 5 }) == 0 +} + +/// Return Some(item) at the index or None if the index is out of range. The index is 0-based. +/// +/// ```aiken +/// list.at([1, 2, 3], 1) == Some(2) +/// list.at([1, 2, 3], 42) == None +/// ``` +pub fn at(self: List, index: Int) -> Option { + when self is { + [] -> None + [x, ..xs] -> + if index == 0 { + Some(x) + } else { + at(xs, index - 1) + } + } +} + +test at_1() { + at([1, 2, 3], -1) == None +} + +test at_2() { + at([], 0) == None +} + +test at_3() { + at([1, 2, 3], 3) == None +} + +test at_4() { + at([1], 0) == Some(1) +} + +test at_5() { + at([1, 2, 3], 2) == Some(3) +} + +/// Merge two lists together. +/// +/// ```aiken +/// list.concat([], []) == [] +/// list.concat([], [1, 2, 3]) == [1, 2, 3] +/// list.concat([1, 2, 3], [4, 5, 6]) == [1, 2, 3, 4, 5, 6] +/// ``` +pub fn concat(left: List, right: List) -> List { + when left is { + [] -> right + [x, ..xs] -> + [x, ..concat(xs, right)] + } +} + +test concat_1() { + concat([1, 2, 3], [4, 5, 6]) == [1, 2, 3, 4, 5, 6] +} + +test concat_2() { + concat([1, 2, 3], []) == [1, 2, 3] +} + +test concat_3() { + concat([], [1, 2, 3]) == [1, 2, 3] +} + +/// Remove the first occurrence of the given element from the list. +/// +/// ```aiken +/// list.delete([1, 2, 3, 1], 1) == [2, 3, 1] +/// list.delete([1, 2, 3], 14) == [1, 2, 3] +/// ``` +pub fn delete(self: List, elem: a) -> List { + when self is { + [] -> + [] + [x, ..xs] -> + if x == elem { + xs + } else { + [x, ..delete(xs, elem)] + } + } +} + +test delete_1() { + delete([], 42) == [] +} + +test delete_2() { + delete([1, 2, 3, 1], 1) == [2, 3, 1] +} + +test delete_3() { + delete([1, 2, 3], 14) == [1, 2, 3] +} + +test delete_4() { + delete([2], 2) == [] +} + +/// Remove the first occurrence of each element of the second list from the first one. +/// +/// ``` +/// list.difference(["h", "e", "l", "l", "o"], ["l", "e", "l"]) == ["h", "o"] +/// list.difference([1, 2, 3, 4, 5], [1, 1, 2]) == [3, 4, 5] +/// list.difference([1, 2, 3], []) == [1, 2, 3] +/// ``` +pub fn difference(self: List, with: List) -> List { + when with is { + [] -> self + [x, ..xs] -> difference(delete(self, x), xs) + } +} + +test difference_1() { + difference(["h", "e", "l", "l", "o"], ["l", "e", "l"]) == ["h", "o"] +} + +test difference_2() { + difference([1, 2, 3, 4, 5], [1, 1, 2]) == [3, 4, 5] +} + +test difference_3() { + difference([1, 2, 3], []) == [1, 2, 3] +} + +test difference_4() { + difference([], [1, 2, 3]) == [] +} + +/// Drop the first `n` elements of a list. +/// +/// ```aiken +/// list.drop([1, 2, 3], 2) == [3] +/// list.drop([], 42) == [] +/// list.drop([1, 2, 3], 42) == [] +/// ``` +pub fn drop(self: List, n: Int) -> List { + if n <= 0 { + self + } else { + when self is { + [] -> + [] + [_x, ..xs] -> drop(xs, n - 1) + } + } +} + +test drop_1() { + drop([], 42) == [] +} + +test drop_2() { + drop([1, 2, 3], 2) == [3] +} + +/// Returns the suffix of the given list after removing all elements that satisfy the predicate. +/// +/// ```aiken +/// list.drop_while([1, 2, 3], fn(x) { x < 2 }) == [2, 3] +/// list.drop_while([], fn(x) { x > 2 }) == [] +/// list.drop_while([1, 2, 3], fn(x) { x == 3 }) == [1, 2, 3] +/// ``` +pub fn drop_while(self: List, predicate: fn(a) -> Bool) -> List { + when self is { + [] -> + [] + [x, ..xs] -> + if predicate(x) { + drop_while(xs, predicate) + } else { + self + } + } +} + +test drop_while_1() { + drop_while([], fn(x) { x > 2 }) == [] +} + +test drop_while_2() { + let xs = + [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] + drop_while(xs, fn(x) { x > 5 }) == [5, 4, 3, 2, 1] +} + +test drop_while_3() { + let xs = + [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] + drop_while(xs, fn(x) { x == 42 }) == xs +} + +test drop_while_4() { + let xs = + [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] + drop_while(xs, fn(x) { x < 42 }) == [] +} + +/// Produce a list of elements that satisfy a predicate. +/// +/// ```aiken +/// list.filter([1, 2, 3], fn(x) { x >= 2 }) == [2, 3] +/// list.filter([], fn(x) { x > 2 }) == [] +/// list.filter([1, 2, 3], fn(x) { x == 3 }) == [3] +/// ``` +pub fn filter(self: List, predicate: fn(a) -> Bool) -> List { + when self is { + [] -> + [] + [x, ..xs] -> + if predicate(x) { + [x, ..filter(xs, predicate)] + } else { + filter(xs, predicate) + } + } +} + +test filter_1() { + filter([], fn(x) { x > 0 }) == [] +} + +test filter_2() { + let xs = + [1, 2, 3, 4, 5, 6] + filter(xs, fn(x) { builtin.mod_integer(x, 2) == 0 }) == [2, 4, 6] +} + +test filter_3() { + let filter_foldr = + fn(xs, f) { + foldr( + xs, + [], + fn(x, ys) { + if f(x) { + [x, ..ys] + } else { + ys + } + }, + ) + } + + let is_odd = + fn(n) { builtin.mod_integer(n, 2) != 0 } + + filter_foldr([1, 2, 3], is_odd) == filter([1, 2, 3], is_odd) +} + +/// Produce a list of transformed elements that satisfy a predicate. +/// +/// ```aiken +/// let transform = fn(x) { if x % 2 == 0 { None } else { Some(3*x) } } +/// list.filter_map([1, 2, 3], transform) == [3, 9] +/// ``` +pub fn filter_map(self: List, predicate: fn(a) -> Option) -> List { + when self is { + [] -> + [] + [x, ..xs] -> + when predicate(x) is { + None -> filter_map(xs, predicate) + Some(y) -> + [y, ..filter_map(xs, predicate)] + } + } +} + +test filter_map_1() { + filter_map([], fn(_) { Some(42) }) == [] +} + +test filter_map_2() { + filter_map( + [1, 2, 3, 4, 5, 6], + fn(x) { + if builtin.mod_integer(x, 2) != 0 { + Some(3 * x) + } else { + None + } + }, + ) == [3, 9, 15] +} + +/// Find the first element satisfying the given predicate, if any. +/// +/// ```aiken +/// list.find([1, 2, 3], fn(x) { x == 2 }) == Some(2) +/// list.find([4, 5, 6], fn(x) { x == 2 }) == None +/// ``` +pub fn find(self: List, predicate: fn(a) -> Bool) -> Option { + when self is { + [] -> None + [x, ..xs] -> + if predicate(x) { + Some(x) + } else { + find(xs, predicate) + } + } +} + +test find_1() { + find([1, 2, 3], fn(x) { x == 1 }) == Some(1) +} + +test find_2() { + find([1, 2, 3], fn(x) { x > 42 }) == None +} + +test find_3() { + find([], fn(_) { True }) == None +} + +/// Map elements of a list into a new list and flatten the result. +/// +/// ```aiken +/// list.flat_map([1, 2, 3], fn(a) { [a, 2*a] }) == [1, 2, 2, 4, 3, 6] +/// ``` +pub fn flat_map(self: List, with: fn(a) -> List) -> List { + foldr(self, [], fn(x, xs) { concat(with(x), xs) }) +} + +test flat_map_1() { + flat_map([], fn(a) { [a] }) == [] +} + +test flat_map_2() { + flat_map([1, 2, 3], fn(a) { [a, a] }) == [1, 1, 2, 2, 3, 3] +} + +/// Reduce a list from left to right. +/// +/// ```aiken +/// list.foldl([1, 2, 3], 0, fn(n, total) { n + total }) == 6 +/// list.foldl([1, 2, 3], [], fn(x, xs) { [x, ..xs] }) == [3, 2, 1] +/// ``` +pub fn foldl(self: List, zero: b, with: fn(a, b) -> b) -> b { + when self is { + [] -> zero + [x, ..xs] -> foldl(xs, with(x, zero), with) + } +} + +test foldl_1() { + foldl([], 0, fn(_, _) { 1 }) == 0 +} + +test foldl_2() { + foldl([1, 2, 3, 4, 5], 0, fn(n, total) { n + total }) == 15 +} + +test foldl_3() { + foldl([1, 2, 3, 4], [], fn(x, xs) { [x, ..xs] }) == [4, 3, 2, 1] +} + +/// Reduce a list from right to left. +/// +/// ```aiken +/// list.foldr([1, 2, 3], 0, fn(n, total) { n + total }) == 6 +/// list.foldr([1, 2, 3], [], fn(x, xs) { [x, ..xs] }) == [1, 2, 3] +/// ``` +pub fn foldr(self: List, zero: b, with: fn(a, b) -> b) -> b { + when self is { + [] -> zero + [x, ..xs] -> with(x, foldr(xs, zero, with)) + } +} + +test foldr_1() { + foldr([1, 2, 3, 4, 5], 0, fn(n, total) { n + total }) == 15 +} + +test foldr_2() { + foldr( + [1, 2, 3], + "", + fn(n, _str) { + if builtin.mod_integer(n, 2) == 0 { + "foo" + } else { + "bar" + } + }, + ) == "bar" +} + +test foldr_3() { + foldr([1, 2, 3, 4], [], fn(x, xs) { [x, ..xs] }) == [1, 2, 3, 4] +} + +/// Return all elements except the last one. +/// +/// ```aiken +/// list.init([]) == None +/// list.init([1, 2, 3]) == Some([1, 2]) +/// ``` +pub fn init(self: List) -> Option> { + when self is { + [] -> None + _ -> Some(do_init(self)) + } +} + +fn do_init(self: List) -> List { + when self is { + [] -> fail @"unreachable" + [_] -> + [] + [x, ..xs] -> + [x, ..do_init(xs)] + } +} + +test init_1() { + init([]) == None +} + +test init_2() { + init([1]) == Some([]) +} + +test init_3() { + init([1, 2, 3, 4]) == Some([1, 2, 3]) +} + +/// Figures out whether a list contain the given element. +/// +/// ```aiken +/// list.has([1, 2, 3], 2) == True +/// list.has([1, 2, 3], 14) == False +/// list.has([], 14) == False +/// ``` +pub fn has(self: List, elem: a) -> Bool { + when self is { + [] -> False + [x, ..xs] -> + if x == elem { + True + } else { + has(xs, elem) + } + } +} + +test has_1() { + has([1, 2, 3], 1) == True +} + +test has_2() { + has([1, 2, 3], 14) == False +} + +test has_3() { + has([], 14) == False +} + +/// Gets the index of an element of a list, if any. Otherwise, returns None. +/// +/// ```aiken +/// list.index_of([1, 5, 2], 2) == Some(2) +/// list.index_of([1, 7, 3], 4) == None +/// list.index_of([1, 0, 9, 6], 6) == 3 +/// list.index_of([], 6) == None +/// ``` +pub fn index_of(self: List, elem: a) -> Option { + do_index_of(self, elem, 0) +} + +fn do_index_of(self: List, elem: a, i: Int) -> Option { + when self is { + [] -> None + [x, ..xs] -> + if x == elem { + Some(i) + } else { + do_index_of(xs, elem, i + 1) + } + } +} + +test index_of_1() { + index_of([1, 5, 2], 2) == Some(2) +} + +test index_of_2() { + index_of([1, 7, 3], 4) == None +} + +test index_of_3() { + index_of([1, 0, 9, 6], 6) == Some(3) +} + +test index_of_4() { + index_of([], 6) == None +} + +/// Get the first element of a list +/// +/// ```aiken +/// list.head([1, 2, 3]) == Some(1) +/// list.head([]) == None +/// ``` +pub fn head(self: List) -> Option { + when self is { + [] -> None + _ -> Some(builtin.head_list(self)) + } +} + +test head_1() { + head([1, 2, 3]) == Some(1) +} + +test head_2() { + head([]) == None +} + +/// Like [`foldr`](#foldr), but also provides the position (0-based) of the elements when iterating. +/// +/// ```aiken +/// let group = fn(i, x, xs) { [(i, x), ..xs] } +/// list.indexed_foldr(["a", "b", "c"], [], group) == [ +/// (0, "a"), +/// (1, "b"), +/// (2, "c") +/// ] +/// ``` +pub fn indexed_foldr( + self: List, + zero: result, + with: fn(Int, a, result) -> result, +) -> result { + do_indexed_foldr(0, self, zero, with) +} + +fn do_indexed_foldr( + n: Int, + self: List, + zero: result, + with: fn(Int, a, result) -> result, +) -> result { + when self is { + [] -> zero + [x, ..xs] -> with(n, x, do_indexed_foldr(n + 1, xs, zero, with)) + } +} + +test indexed_foldr_1() { + indexed_foldr([], 0, fn(i, x, xs) { i + x + xs }) == 0 +} + +test indexed_foldr_2() { + let letters = + ["a", "b", "c"] + indexed_foldr(letters, [], fn(i, x, xs) { [(i, x), ..xs] }) == [ + (0, "a"), + (1, "b"), + (2, "c"), + ] +} + +/// List [`map`](#map) but provides the position (0-based) of the elements while iterating. +/// +/// ```aiken +/// list.indexed_map([1, 2, 3], fn(i, x) { i + x }) == [1, 3, 5] +/// ``` +pub fn indexed_map(self: List, with: fn(Int, a) -> result) -> List { + do_indexed_map(0, self, with) +} + +fn do_indexed_map( + n: Int, + self: List, + with: fn(Int, a) -> result, +) -> List { + when self is { + [] -> + [] + [x, ..xs] -> + [with(n, x), ..do_indexed_map(n + 1, xs, with)] + } +} + +test indexed_map_1() { + indexed_map([], fn(i, _n) { i }) == [] +} + +test indexed_map_2() { + indexed_map( + [4, 8, 13, 2], + fn(i, n) { + if n == 8 { + n + } else { + i + } + }, + ) == [0, 8, 2, 3] +} + +/// Checks whether a list is empty. +/// +/// ```aiken +/// list.is_empty([]) == True +/// list.is_empty([1, 2, 3]) == False +/// ``` +pub fn is_empty(self: List) -> Bool { + when self is { + [] -> True + _ -> False + } +} + +test is_empty_1() { + is_empty([]) == True +} + +test is_empty_2() { + is_empty([1, 2, 3]) == False +} + +/// Get the last in the given list, if any. +/// +/// ```aiken +/// list.last([]) == None +/// list.last([1, 2, 3]) == Some(3) +/// ``` +pub fn last(self: List) -> Option { + when self is { + [] -> None + [x] -> Some(x) + [_, ..xs] -> last(xs) + } +} + +test last_1() { + last([]) == None +} + +test last_2() { + last([1]) == Some(1) +} + +test last_3() { + last([1, 2, 3, 4]) == Some(4) +} + +/// Get the number of elements in the given list. +/// +/// ```aiken +/// list.length([]) == 0 +/// list.length([1, 2, 3]) == 3 +/// ``` +pub fn length(self: List) -> Int { + when self is { + [] -> 0 + [_, ..xs] -> 1 + length(xs) + } +} + +test length_1() { + length([]) == 0 +} + +test length_2() { + length([1, 2, 3]) == 3 +} + +/// Apply a function to each element of a list. +/// +/// ```aiken +/// list.map([1, 2, 3, 4], fn(n) { n + 1 }) == [2, 3, 4, 5] +/// ``` +pub fn map(self: List, with: fn(a) -> result) -> List { + when self is { + [] -> + [] + [x, ..xs] -> + [with(x), ..map(xs, with)] + } +} + +test map_1() { + map([], fn(n) { n + 1 }) == [] +} + +test map_2() { + map([1, 2, 3, 4], fn(n) { n + 1 }) == [2, 3, 4, 5] +} + +/// Apply a function of two arguments, combining elements from two lists. +/// +/// Note: if one list is longer, the extra elements are dropped. +/// +/// ```aiken +/// list.map2([1, 2, 3], [1, 2], fn(a, b) { a + b }) == [2, 4] +/// ``` +pub fn map2( + self: List, + bs: List, + with: fn(a, b) -> result, +) -> List { + when self is { + [] -> + [] + [x, ..xs] -> + when bs is { + [] -> + [] + [y, ..ys] -> + [with(x, y), ..map2(xs, ys, with)] + } + } +} + +test map2_1() { + map2([], [1, 2, 3], fn(a, b) { a + b }) == [] +} + +test map2_2() { + map2([1, 2, 3], [1, 2], fn(a, b) { a + b }) == [2, 4] +} + +test map2_3() { + map2([42], [1, 2, 3], fn(_a, b) { Some(b) }) == [Some(1)] +} + +/// Apply a function of three arguments, combining elements from three lists. +/// +/// Note: if one list is longer, the extra elements are dropped. +/// +/// ```aiken +/// list.map3([1, 2, 3], [1, 2], [1, 2, 3], fn(a, b, c) { a + b + c }) == [3, 6] +/// ``` +pub fn map3( + self: List, + bs: List, + cs: List, + with: fn(a, b, c) -> result, +) -> List { + when self is { + [] -> + [] + [x, ..xs] -> + when bs is { + [] -> + [] + [y, ..ys] -> + when cs is { + [] -> + [] + [z, ..zs] -> + [with(x, y, z), ..map3(xs, ys, zs, with)] + } + } + } +} + +test map3_1() { + map3([], [], [1, 2, 3], fn(a, b, c) { a + b + c }) == [] +} + +test map3_2() { + map3([1, 2, 3], [1, 2], [1, 2, 3], fn(a, b, c) { a + b + c }) == [3, 6] +} + +/// Add an element in front of the list. Sometimes useful when combined with +/// other functions. +/// +/// ```aiken +/// list.push([2, 3], 1) == [1, ..[2, 3]] == [1, 2, 3] +/// ``` +pub fn push(self: List, elem: a) -> List { + [elem, ..self] +} + +test push_1() { + push([], 0) == [0] +} + +test push_2() { + push([2, 3], 1) == [1, 2, 3] +} + +/// Construct a list of a integer from a given range. +/// +/// ```aiken +/// list.range(0, 3) == [0, 1, 2, 3] +/// list.range(-1, 1) == [-1, 0, 1] +/// ``` +pub fn range(from: Int, to: Int) -> List { + if from > to { + [] + } else { + [from, ..range(from + 1, to)] + } +} + +test range_1() { + range(0, 3) == [0, 1, 2, 3] +} + +test range_2() { + range(-1, 1) == [-1, 0, 1] +} + +/// Construct a list filled with n copies of a value. +/// +/// ```aiken +/// list.repeat("na", 3) == ["na", "na", "na"] +/// ``` +pub fn repeat(elem: a, n_times: Int) -> List { + if n_times <= 0 { + [] + } else { + [elem, ..repeat(elem, n_times - 1)] + } +} + +test repeat_1() { + repeat(42, 0) == [] +} + +test repeat_2() { + repeat(14, 3) == [14, 14, 14] +} + +/// Return the list with its elements in the reserve order. +/// +/// ```aiken +/// list.reverse([1, 2, 3]) == [3, 2, 1] +/// ``` +pub fn reverse(self: List) -> List { + foldl(self, [], fn(x, xs) { [x, ..xs] }) +} + +test reverse_1() { + reverse([]) == [] +} + +test reverse_2() { + reverse([1, 2, 3]) == [3, 2, 1] +} + +/// Returns a tuple with all elements that satisfy the predicate at first +/// element, and the rest as second element. +/// +/// ```aiken +/// list.partition([1, 2, 3, 4], fn(x) { x % 2 == 0 }) == ([2, 4], [1, 3]) +/// ``` +pub fn partition(self: List, predicate: fn(a) -> Bool) -> (List, List) { + when self is { + [] -> ([], []) + [x, ..xs] -> { + let (left, right) = partition(xs, predicate) + if predicate(x) { + ([x, ..left], right) + } else { + (left, [x, ..right]) + } + } + } +} + +test partition_1() { + partition([], fn(x) { x > 2 }) == ([], []) +} + +test partition_2() { + let xs = + [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] + partition(xs, fn(x) { x > 5 }) == ([10, 9, 8, 7, 6], [5, 4, 3, 2, 1]) +} + +test partition_3() { + let xs = + [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] + partition(xs, fn(x) { x == 42 }) == ([], xs) +} + +test partition_4() { + let xs = + [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] + partition(xs, fn(x) { x < 42 }) == (xs, []) +} + +test partition_5() { + partition([1, 2, 3, 4], fn(x) { x % 2 == 0 }) == ([2, 4], [1, 3]) +} + +/// Extract a sublist from the given list using 0-based indexes. Negative +/// indexes wrap over, so `-1` refers to the last element of the list. +/// +/// ```aiken +/// list.slice([1, 2, 3, 4, 5, 6], from: 2, to: 4) == [3, 4, 5] +/// list.slice([1, 2, 3, 4, 5, 6], from: -2, to: -1) == [5, 6] +/// list.slice([1, 2, 3, 4, 5, 6], from: 1, to: -1) == [2, 3, 4, 5, 6] +/// ``` +pub fn slice(self: List, from: Int, to: Int) { + let (i, l) = + if from >= 0 { + (from, None) + } else { + let l = length(self) + (l + from, Some(l)) + } + + let j = + if to >= 0 { + to - i + 1 + } else { + when l is { + Some(l) -> l + to - i + 1 + None -> length(self) + to - i + 1 + } + } + + self + |> drop(i) + |> take(j) +} + +test slice_1() { + slice([1, 2, 3], 0, 2) == [1, 2, 3] +} + +test slice_2() { + slice([1, 2, 3, 4, 5, 6], from: 2, to: 4) == [3, 4, 5] +} + +test slice_3() { + slice([1, 2, 3, 4, 5, 6], from: -2, to: -1) == [5, 6] +} + +test slice_4() { + slice([1, 2, 3, 4, 5, 6], from: 1, to: -1) == [2, 3, 4, 5, 6] +} + +test slice_5() { + slice([1, 2, 3, 4, 5, 6], from: -4, to: -3) == [3, 4] +} + +test slice_6() { + slice([1, 2, 3, 4, 5, 6], from: -2, to: 1) == [] +} + +/// Sort a list in ascending order using the given comparison function. +/// +/// ```aiken +/// use aiken/int +/// +/// sort([3, 1, 4, 0, 2], int.compare) == [0, 1, 2, 3, 4] +/// sort([1, 2, 3], int.compare) == [1, 2, 3] +/// ``` +pub fn sort(self: List, compare: fn(a, a) -> Ordering) -> List { + when self is { + [] -> + [] + [x, ..xs] -> insert(sort(xs, compare), x, compare) + } +} + +fn insert(self: List, e: a, compare: fn(a, a) -> Ordering) -> List { + when self is { + [] -> + [e] + [x, ..xs] -> + if compare(e, x) == Less { + [e, ..self] + } else { + [x, ..insert(xs, e, compare)] + } + } +} + +test sort_1() { + let xs = + [6, 7, 5, 4, 1, 3, 9, 8, 0, 2] + sort(xs, int.compare) == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +} + +test sort_2() { + let xs = + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + sort(xs, int.compare) == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +} + +test sort_3() { + let xs = + [9, 8, 7, 6, 5, 4, 3, 2, 1, 0] + sort(xs, int.compare) == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +} + +test sort_4() { + sort([], int.compare) == [] +} + +/// Cut a list in two, such that the first list contains the given number of / +/// elements and the second list contains the rest. +/// +/// Fundamentally equivalent to (but more efficient): +/// +/// ```aiken +/// // span(xs, n) == (take(xs, n), drop(xs, n)) +/// span([1, 2, 3, 4, 5], 3) == ([1, 2, 3], [4, 5]) +/// ``` +pub fn span(self: List, n: Int) -> (List, List) { + when self is { + [] -> ([], []) + [x, ..xs] -> + if n <= 0 { + ([], self) + } else { + let (left, right) = span(xs, n - 1) + ([x, ..left], right) + } + } +} + +test span_1() { + span([], 2) == ([], []) +} + +test span_2() { + span([1, 2, 3], 2) == ([1, 2], [3]) +} + +test span_3() { + span([1, 2, 3], -1) == ([], [1, 2, 3]) +} + +test span_4() { + span([1, 2, 3], 42) == ([1, 2, 3], []) +} + +/// Get elements of a list after the first one, if any. +/// +/// ```aiken +/// list.tail([]) == None +/// list.tail([1, 2, 3]) == Some([2, 3]) +/// ``` +pub fn tail(self: List) -> Option> { + when self is { + [] -> None + [_, ..xs] -> Some(xs) + } +} + +test tail_1() { + tail([1, 2, 3]) == Some([2, 3]) +} + +test tail_2() { + tail([]) == None +} + +/// Get the first `n` elements of a list. +/// +/// ```aiken +/// list.take([1, 2, 3], 2) == [1, 2] +/// list.take([1, 2, 3], 14) == [1, 2, 3] +/// ``` +pub fn take(self: List, n: Int) -> List { + if n <= 0 { + [] + } else { + when self is { + [] -> + [] + [x, ..xs] -> + [x, ..take(xs, n - 1)] + } + } +} + +test take_1() { + take([], 42) == [] +} + +test take_2() { + take([1, 2, 3], 2) == [1, 2] +} + +/// Returns the longest prefix of the given list where all elements satisfy the predicate. +/// +/// ```aiken +/// list.take_while([1, 2, 3], fn(x) { x > 2 }) == [] +/// list.take_while([1, 2, 3], fn(x) { x < 2 }) == [1] +/// ``` +pub fn take_while(self: List, predicate: fn(a) -> Bool) -> List { + when self is { + [] -> + [] + [x, ..xs] -> + if predicate(x) { + [x, ..take_while(xs, predicate)] + } else { + [] + } + } +} + +test take_while_1() { + take_while([], fn(x) { x > 2 }) == [] +} + +test take_while_2() { + let xs = + [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] + take_while(xs, fn(x) { x > 5 }) == [10, 9, 8, 7, 6] +} + +test take_while_3() { + let xs = + [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] + take_while(xs, fn(x) { x == 42 }) == [] +} + +test take_while_4() { + let xs = + [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] + take_while(xs, fn(x) { x < 42 }) == xs +} + +/// Removes duplicate elements from a list. +/// +/// ```aiken +/// list.unique([1, 2, 3, 1]) == [1, 2, 3] +/// ``` +pub fn unique(self: List) -> List { + when self is { + [] -> + [] + [x, ..xs] -> + [x, ..unique(filter(xs, fn(y) { y != x }))] + } +} + +test unique_1() { + unique([]) == [] +} + +test unique_2() { + let xs = + [1, 2, 3, 1, 1, 3, 4, 1, 2, 3, 2, 4, 5, 6, 7, 8, 9, 10, 9] + unique(xs) == [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] +} + +/// Decompose a list of tuples into a tuple of lists. +/// +/// ``` +/// list.unzip([(1, "a"), (2, "b")]) == ([1, 2], ["a", "b"]) +/// ``` +pub fn unzip(self: List<(a, b)>) -> (List, List) { + when self is { + [] -> ([], []) + [(a, b), ..xs] -> { + let (a_tail, b_tail) = unzip(xs) + ([a, ..a_tail], [b, ..b_tail]) + } + } +} + +test unzip_1() { + unzip([]) == ([], []) +} + +test unzip_2() { + unzip([(1, "a"), (2, "b")]) == ([1, 2], ["a", "b"]) +} + +/// Combine two lists together. +/// +/// Note: if one list is longer, the extra elements are dropped. +/// +/// ```aiken +/// list.zip([1, 2], ["a", "b", "c"]) == [(1, "a"), (2, "b")] +/// ``` +pub fn zip(self: List, bs: List) -> List<(a, b)> { + when self is { + [] -> + [] + [x, ..xs] -> + when bs is { + [] -> + [] + [y, ..ys] -> + [(x, y), ..zip(xs, ys)] + } + } +} + +test zip_1() { + zip([], [1, 2, 3]) == [] +} + +test zip_2() { + zip([1, 2, 3], []) == [] +} + +test zip_3() { + zip([1, 2], ["a", "b", "c"]) == [(1, "a"), (2, "b")] +} + +/// Reduce a list from left to right using the accumulator as left operand. +/// Said differently, this is [`foldl`](#foldl) with callback arguments swapped. +/// +/// ```aiken +/// list.reduce([#[1], #[2], #[3]], #[0], bytearray.concat) == #[0, 1, 2, 3] +/// list.reduce([True, False, True], False, fn(b, a) { or { b, a } }) == True +/// ``` +pub fn reduce(self: List, zero: b, with: fn(b, a) -> b) -> b { + foldl(self, zero, flip(with)) +} + +test reduce_1() { + reduce([], 0, fn(n, total) { n + total }) == 0 +} + +test reduce_2() { + reduce([1, 2, 3], 0, fn(n, total) { n + total }) == 6 +} + +test reduce_3() { + reduce([True, False, True], False, fn(left, right) { left || right }) == True +} + +test reduce_4() { + reduce( + [#[1], #[2], #[3]], + #[9], + fn(left, right) { bytearray.concat(left, right) }, + ) == #[9, 1, 2, 3] +} diff --git a/example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/math.ak b/example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/math.ak new file mode 100644 index 00000000..764152bf --- /dev/null +++ b/example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/math.ak @@ -0,0 +1,372 @@ +//// This module contains some basic Math utilities. Standard arithmetic +//// operations on integers are available through native operators: +//// +//// Operator | Description +//// --- | :--- +//// `+` | Arithmetic sum +//// `-` | Arithmetic difference +//// `/` | Whole division +//// `*` | Arithmetic multiplication +//// `%` | Remainder by whole division +//// +//// Here are a few examples: +//// +//// ```aiken +//// 1 + 1 // 2 +//// 10 - 2 // 8 +//// 40 / 14 // 2 +//// 3 * 4 // 12 +//// 10 % 3 // 1 + +use aiken/builtin + +/// Calculate the absolute value of an integer. +/// +/// ```aiken +/// math.abs(-42) == 42 +/// math.abs(14) == 14 +/// ``` +pub fn abs(self: Int) -> Int { + if self < 0 { + 0 - self + } else { + self + } +} + +test abs_1() { + abs(14) == 14 +} + +test abs_2() { + abs(-42) == 42 +} + +/// Restrict the value of an integer between two min and max bounds +/// +/// ```aiken +/// math.clamp(14, min: 0, max: 10) == 10 +/// ``` +pub fn clamp(self: Int, min: Int, max: Int) -> Int { + if self < min { + min + } else { + if self > max { + max + } else { + self + } + } +} + +test clamp_1() { + clamp(14, min: 0, max: 10) == 10 +} + +test clamp_2() { + clamp(7, min: 0, max: 10) == 7 +} + +test clamp_3() { + clamp(7, min: 10, max: 100) == 10 +} + +/// Return the maximum of two integers. +pub fn max(a: Int, b: Int) -> Int { + if a > b { + a + } else { + b + } +} + +test max_1() { + max(0, 0) == 0 +} + +test max_2() { + max(14, 42) == 42 +} + +test max_3() { + max(42, 14) == 42 +} + +/// Return the minimum of two integers. +pub fn min(a: Int, b: Int) -> Int { + if a > b { + b + } else { + a + } +} + +test min_1() { + min(0, 0) == 0 +} + +test min_2() { + min(14, 42) == 14 +} + +test min_3() { + min(42, 14) == 14 +} + +/// Calculates a number to the power of `e` using the exponentiation by +/// squaring method. +/// +/// ```aiken +/// math.pow(3, 5) == 243 +/// math.pow(7, 2) == 49 +/// math.pow(3, -4) == 0 +/// math.pow(0, 0) == 1 +/// math.pow(513, 3) == 135005697 +/// ``` +pub fn pow(self: Int, e: Int) -> Int { + if e < 0 { + 0 + } else if e == 0 { + 1 + } else if e % 2 == 0 { + pow(self * self, e / 2) + } else { + self * pow(self * self, ( e - 1 ) / 2) + } +} + +test pow_3_5() { + pow(3, 5) == 243 +} + +test pow_7_2() { + pow(7, 2) == 49 +} + +test pow_3__4() { + // negative powers round to zero + pow(3, -4) == 0 +} + +test pow_0_0() { + // sorry math + pow(0, 0) == 1 +} + +test pow_513_3() { + pow(513, 3) == 135005697 +} + +test pow_2_4() { + pow(2, 4) == 16 +} + +test pow_2_42() { + pow(2, 42) == 4398046511104 +} + +/// Calculates the power of 2 for a given exponent `e`. Much cheaper than +/// using `pow(2, _)` for small exponents (0 < e < 256). +/// +/// ```aiken +/// math.pow2(-2) == 0 +/// math.pow2(0) == 1 +/// math.pow2(1) == 2 +/// math.pow2(4) == 16 +/// math.pow2(42) == 4398046511104 +/// ``` +pub fn pow2(e: Int) -> Int { + // do_pow2(e, 1) + if e < 8 { + if e < 0 { + 0 + } else { + builtin.index_bytearray(#[1, 2, 4, 8, 16, 32, 64, 128], e) + } + } else if e < 32 { + 256 * pow2(e - 8) + } else { + 4294967296 * pow2(e - 32) + } +} + +test pow2_neg() { + pow2(-2) == 0 +} + +test pow2_0() { + pow2(0) == 1 +} + +test pow2_1() { + pow2(1) == 2 +} + +test pow2_4() { + pow2(4) == 16 +} + +test pow2_42() { + pow2(42) == 4398046511104 +} + +test pow2_256() { + pow2(256) == 115792089237316195423570985008687907853269984665640564039457584007913129639936 +} + +/// The logarithm in base `b` of an element using integer divisions. +/// +/// ```aiken +/// math.log(10, base: 2) == 3 +/// math.log(42, base: 2) == 5 +/// math.log(42, base: 3) == 3 +/// math.log(5, base: 0) == 0 +/// math.log(4, base: 4) == 1 +/// math.log(4, base: 42) == 0 +/// ``` +pub fn log(self: Int, base: Int) -> Int { + if base <= 0 { + 0 + } else if self == base { + 1 + } else if self < base { + 0 + } else { + 1 + log(self / base, base) + } +} + +test log_10_2() { + log(10, base: 2) == 3 +} + +test log_42_2() { + log(42, base: 2) == 5 +} + +test log_42_3() { + log(42, base: 3) == 3 +} + +test log_5_0() { + log(5, base: 0) == 0 +} + +test log_4_4() { + log(4, base: 4) == 1 +} + +test log_4_43() { + log(4, base: 43) == 0 +} + +/// The greatest common divisor of two integers. +/// +/// ```aiken +/// math.gcd(42, 14) == 14 +/// math.gcd(14, 42) == 14 +/// math.gcd(0, 0) == 0 +/// ``` +pub fn gcd(x: Int, y: Int) -> Int { + abs(do_gcd(x, y)) +} + +fn do_gcd(x: Int, y: Int) -> Int { + when y is { + 0 -> x + _ -> do_gcd(y, x % y) + } +} + +test gcd_test1() { + gcd(10, 300) == 10 +} + +test gcd_test2() { + gcd(-10, 300) == 10 +} + +test gcd_test3() { + gcd(42, 14) == 14 +} + +/// Calculates the square root of an integer using the [Babylonian +/// method](https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method). This returns either the exact result or the smallest integer +/// nearest to the square root. +/// +/// Returns `None` for negative values. +/// +/// ```aiken +/// math.sqrt(0) == Some(0) +/// math.sqrt(25) == Some(5) +/// math.sqrt(44203) == Some(210) +/// math.sqrt(-42) == None +/// ``` +pub fn sqrt(self: Int) -> Option { + if self < 0 { + None + } else if self <= 1 { + Some(self) + } else { + Some(sqrt_babylonian(self, self, ( self + 1 ) / 2)) + } +} + +// The basic idea is that if x is an overestimate to the square root of a +// non-negative real number S then S/x will be an underestimate, or vice versa, +// and so the average of these two numbers may reasonably be expected to provide a +// better approximation (though the formal proof of that assertion depends on the +// inequality of arithmetic and geometric means that shows this average is always +// an overestimate of the square root. +fn sqrt_babylonian(self: Int, x: Int, y: Int) -> Int { + if y >= x { + x + } else { + sqrt_babylonian(self, y, ( y + self / y ) / 2) + } +} + +test sqrt1() { + sqrt(0) == Some(0) +} + +test sqrt2() { + sqrt(1) == Some(1) +} + +test sqrt3() { + sqrt(25) == Some(5) +} + +test sqrt4() { + sqrt(44203) == Some(210) +} + +test sqrt5() { + sqrt(975461057789971041) == Some(987654321) +} + +test sqrt6() { + sqrt(-42) == None +} + +/// Checks if an integer has a given integer square root x. +/// The check has constant time complexity (O(1)). +/// +/// ```aiken +/// math.is_sqrt(0, 0) +/// math.is_sqrt(25, 5) +/// ! math.is_sqrt(25, -5) +/// math.is_sqrt(44203, 210) +/// ``` +pub fn is_sqrt(self: Int, x: Int) -> Bool { + x * x <= self && ( x + 1 ) * ( x + 1 ) > self +} + +test is_sqrt1() { + is_sqrt(44203, 210) +} + +test is_sqrt2() { + is_sqrt(975461057789971041, 987654321) +} diff --git a/example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/math/rational.ak b/example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/math/rational.ak new file mode 100644 index 00000000..6bca6434 --- /dev/null +++ b/example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/math/rational.ak @@ -0,0 +1,795 @@ +//// This module implements operations between rational numbers. Internally, rational aren't +//// automatically reduced as this is **only done on-demand**. +//// +//// Thus, for example: +//// +//// ```aiken +//// rational.new(2, 3) != rational.new(4, 6) +//// ``` +//// +//// Comparing rational values should, therefore, only happen after reduction (see [reduce](#reduce)) or via the [compare](#compare) method. + +use aiken/builtin +use aiken/list +use aiken/math +use aiken/option + +/// Opaque type used to ensure the sign of the Rational is managed strictly in the numerator. +pub opaque type Rational { + numerator: Int, + denominator: Int, +} + +/// An unsafe constructor for `Rational` values. Assumes that the following invariants are +/// enforced: +/// +/// - the denominator is positive (the sign is managed in the numerator); +/// - the denominator is not null. +/// +/// This function is mainly used as a quick way to construct rationals from literal values. +fn ratio(numerator: Int, denominator: Int) -> Rational { + Rational { numerator, denominator } +} + +/// Make a `Rational` number from the ratio of two integers. +/// +/// Returns `None` when the denominator is null. +/// +/// ```aiken +/// rational.new(14, 42) == Some(r) +/// rational.new(14, 0) == None +/// ``` +pub fn new(numerator: Int, denominator: Int) -> Option { + if denominator == 0 { + None + } else if denominator < 0 { + Some(Rational { numerator: -numerator, denominator: -denominator }) + } else { + Some(Rational { numerator, denominator }) + } +} + +test new_1() { + and { + (new(2, 0) == None)?, + (new(2, 3) == Some(ratio(2, 3)))?, + (new(-2, 3) == Some(ratio(-2, 3)))?, + (new(2, -3) == Some(ratio(-2, 3)))?, + (new(2, 4) == Some(ratio(2, 4)))?, + (new(-2, -3) == Some(ratio(2, 3)))?, + (new(-2, -4) == Some(ratio(2, 4)))?, + } +} + +/// Get the numerator of a rational value. +/// +/// ```aiken +/// expect Some(x) = rational.new(2, 3) +/// rational.numerator(x) == 2 +/// ``` +pub fn numerator(self: Rational) -> Int { + self.numerator +} + +test numerator_1() { + expect Some(x) = new(2, 3) + expect Some(y) = new(-2, 3) + expect Some(z) = new(2, -3) + expect Some(w) = new(-2, -3) + + and { + (numerator(x) == 2)?, + (numerator(y) == -2)?, + (numerator(z) == -2)?, + (numerator(w) == 2)?, + } +} + +/// Get the denominator of a rational value. +/// +/// ```aiken +/// expect Some(x) = rational.new(2, 3) +/// rational.denominator(x) == 3 +/// ``` +pub fn denominator(self: Rational) -> Int { + self.denominator +} + +test denominator_1() { + expect Some(x) = new(2, 3) + expect Some(y) = new(-2, 3) + expect Some(z) = new(2, -3) + expect Some(w) = new(-2, -3) + and { + (denominator(x) == 3)?, + (denominator(y) == 3)?, + (denominator(z) == 3)?, + (denominator(w) == 3)?, + } +} + +/// A null `Rational`. +pub fn zero() -> Rational { + Rational { numerator: 0, denominator: 1 } +} + +test zero_1() { + zero() == ratio(0, 1) +} + +/// Multiplication: the product of two rational values. +/// +/// ```aiken +/// expect Some(x) = rational.new(2, 3) +/// expect Some(y) = rational.new(3, 4) +/// +/// Some(rational.mul(x, y)) == rational.new(6, 12) +/// ``` +pub fn mul(left: Rational, right: Rational) -> Rational { + let Rational { numerator: a_n, denominator: a_d } = left + let Rational { numerator: b_n, denominator: b_d } = right + Rational { numerator: a_n * b_n, denominator: a_d * b_d } +} + +test mul_1() { + mul(ratio(2, 3), ratio(3, 4)) == ratio(6, 12) +} + +test mul_2() { + mul(ratio(-2, 3), ratio(-3, 4)) == ratio(6, 12) +} + +test mul_3() { + let result = + ratio(2, 5) + |> mul(ratio(1, 8)) + |> mul(ratio(3, 10)) + |> mul(ratio(21, 100)) + |> mul(ratio(3, 5)) + |> mul(ratio(2, 8)) + |> mul(ratio(4, 10)) + |> mul(ratio(22, 100)) + |> reduce + + result == ratio(2079, 50000000) +} + +/// Division: quotient of two rational values. Returns `None` when the second +/// value is null. +/// +/// ```aiken +/// expect Some(x) = rational.new(2, 3) +/// expect Some(y) = rational.new(3, 4) +/// +/// rational.div(x, y) == rational.new(8, 9) +/// ``` +pub fn div(left: Rational, right: Rational) -> Option { + reciprocal(right) |> option.map(mul(left, _)) +} + +test div_1() { + div(ratio(2, 3), ratio(3, 4)) == new(8, 9) +} + +test div_2() { + div(ratio(2, 3), ratio(-3, 4)) == new(-8, 9) +} + +/// Addition: sum of two rational values +/// +/// ```aiken +/// expect Some(x) = rational.new(2, 3) +/// expect Some(y) = rational.new(3, 4) +/// +/// Some(rational.add(x, y)) == rational.new(17, 12) +/// ``` +pub fn add(left: Rational, right: Rational) -> Rational { + let Rational { numerator: a_n, denominator: a_d } = left + let Rational { numerator: b_n, denominator: b_d } = right + Rational { numerator: a_n * b_d + b_n * a_d, denominator: a_d * b_d } +} + +test add_1() { + add(ratio(2, 3), ratio(3, 4)) == ratio(17, 12) +} + +test add_2() { + add(ratio(-2, 3), ratio(3, 4)) == ratio(1, 12) +} + +/// Subtraction: difference of two rational values +/// +/// ```aiken +/// expect Some(x) = rational.new(2, 3) +/// expect Some(y) = rational.new(3, 4) +/// +/// Some(rational.sub(x, y)) == rational.new(-1, 12) +/// ``` +pub fn sub(left: Rational, right: Rational) -> Rational { + let Rational { numerator: a_n, denominator: a_d } = left + let Rational { numerator: b_n, denominator: b_d } = right + Rational { numerator: a_n * b_d - b_n * a_d, denominator: a_d * b_d } +} + +test sub_1() { + sub(ratio(2, 3), ratio(3, 4)) == ratio(-1, 12) +} + +test sub_2() { + sub(ratio(2, 3), ratio(-3, 4)) == ratio(17, 12) +} + +test sub_3() { + sub(ratio(-2, 3), ratio(3, 4)) == ratio(-17, 12) +} + +/// Create a new `Rational` from an `Int`. +/// +/// ```aiken +/// Some(rational.from_int(14)) == rational.new(14, 1) +/// Some(rational.from_int(-5)) == rational.new(-5, 1) +/// Some(rational.from_int(0)) == rational.new(0, 1) +/// ``` +pub fn from_int(numerator: Int) -> Rational { + Rational { numerator, denominator: 1 } +} + +test from_int_1() { + and { + (from_int(14) == ratio(14, 1))?, + (from_int(-5) == ratio(-5, 1))?, + (from_int(0) == ratio(0, 1))?, + } +} + +/// Returns the nearest `Int` between zero and a given `Rational`. +/// +/// ```aiken +/// expect Some(x) = rational.new(2, 3) +/// rational.truncate(x) == 0 +/// +/// expect Some(y) = rational.new(44, 14) +/// rational.truncate(y) == 3 +/// +/// expect Some(z) = rational.new(-14, 3) +/// rational.truncate(z) == -4 +/// ``` +pub fn truncate(self: Rational) -> Int { + let Rational { numerator: a_n, denominator: a_d } = self + builtin.quotient_integer(a_n, a_d) +} + +test truncate_1() { + and { + (truncate(ratio(5, 2)) == 2)?, + (truncate(ratio(5, 3)) == 1)?, + (truncate(ratio(5, 4)) == 1)?, + (truncate(ratio(5, 5)) == 1)?, + (truncate(ratio(5, 6)) == 0)?, + (truncate(ratio(8, 3)) == 2)?, + (truncate(ratio(-14, 3)) == -4)?, + } +} + +/// Returns the greatest `Int` no greater than a given `Rational` +/// +/// ```aiken +/// expect Some(x) = rational.new(2, 3) +/// rational.floor(x) == 0 +/// +/// expect Some(y) = rational.new(44, 14) +/// rational.floor(y) == 3 +/// +/// expect Some(z) = rational.new(-14, 3) +/// rational.floor(z) == -5 +/// ``` +pub fn floor(self: Rational) -> Int { + let Rational { numerator: a_n, denominator: a_d } = self + a_n / a_d +} + +test floor_1() { + and { + (floor(ratio(5, 2)) == 2)?, + (floor(ratio(5, 3)) == 1)?, + (floor(ratio(5, 4)) == 1)?, + (floor(ratio(5, 5)) == 1)?, + (floor(ratio(5, 6)) == 0)?, + (floor(ratio(8, 3)) == 2)?, + (floor(ratio(-14, 3)) == -5)?, + } +} + +/// Returns the smallest `Int` not less than a given `Rational` +/// +/// ```aiken +/// expect Some(x) = rational.new(2, 3) +/// rational.ceil(x) == 1 +/// +/// expect Some(y) = rational.new(44, 14) +/// rational.ceil(y) == 4 +/// +/// expect Some(z) = rational.new(-14, 3) +/// rational.ceil(z) == -4 +/// ``` +pub fn ceil(self: Rational) -> Int { + let Rational { numerator, denominator } = self + if builtin.remainder_integer(numerator, denominator) > 0 { + builtin.quotient_integer(numerator, denominator) + 1 + } else { + builtin.quotient_integer(numerator, denominator) + } +} + +test ceil_1() { + and { + (ceil(ratio(13, 5)) == 3)?, + (ceil(ratio(15, 5)) == 3)?, + (ceil(ratio(16, 5)) == 4)?, + (ceil(ratio(-3, 5)) == 0)?, + (ceil(ratio(-5, 5)) == -1)?, + (ceil(ratio(-14, 3)) == -4)?, + (ceil(ratio(-14, 6)) == -2)?, + (ceil(ratio(44, 14)) == 4)?, + } +} + +/// Returns the proper fraction of a given `Rational` `r`. That is, a 2-tuple of +/// an `Int` and `Rational` (n, f) such that: +/// +/// - `r = n + f`; +/// - `n` and `f` have the same sign as `r`; +/// - `f` has an absolute value less than 1. +pub fn proper_fraction(self: Rational) -> (Int, Rational) { + let Rational { numerator, denominator } = self + ( + builtin.quotient_integer(numerator, denominator), + Rational { + numerator: builtin.remainder_integer(numerator, denominator), + denominator, + }, + ) +} + +test proper_fraction_1() { + let r = ratio(10, 7) + let (n, f) = proper_fraction(r) + and { + (n == 1)?, + (f == ratio(3, 7))?, + (r == add(from_int(n), f))?, + } +} + +test proper_fraction_2() { + let r = ratio(-10, 7) + let (n, f) = proper_fraction(r) + and { + (n == -1)?, + (f == ratio(-3, 7))?, + (r == add(from_int(n), f))?, + } +} + +test proper_fraction_3() { + let r = ratio(4, 2) + let (n, f) = proper_fraction(r) + and { + (n == 2)?, + (f == ratio(0, 2))?, + (r == add(from_int(n), f))?, + } +} + +/// Round the argument to the nearest whole number. If the argument is +/// equidistant between two values, the greater value is returned (it +/// rounds half towards positive infinity). +/// +/// ```aiken +/// expect Some(x) = rational.new(2, 3) +/// rational.round(x) == 1 +/// +/// expect Some(y) = rational.new(3, 2) +/// rational.round(y) == 2 +/// +/// expect Some(z) = rational.new(-3, 2) +/// rational.round(z) == -1 +/// ``` +/// +/// ⚠️ This behaves differently than _Haskell_. If you're coming from +/// `PlutusTx`, beware that in Haskell, rounding on equidistant values depends on the +/// whole number being odd or even. If you need this behaviour, use [`round_even`](#round_even). +pub fn round(self: Rational) -> Int { + let (n, f) = proper_fraction(self) + + let is_negative = f.numerator < 0 + + when compare(abs(f), ratio(1, 2)) is { + Less -> n + Equal if is_negative -> n + Equal -> n + 1 + Greater if is_negative -> n - 1 + Greater -> n + 1 + } +} + +test round_1() { + and { + (round(ratio(10, 7)) == 1)?, + (round(ratio(11, 7)) == 2)?, + (round(ratio(3, 2)) == 2)?, + (round(ratio(5, 2)) == 3)?, + (round(ratio(-3, 2)) == -1)?, + (round(ratio(-2, 3)) == -1)?, + (round(ratio(-10, 7)) == -1)?, + (round(ratio(4, 2)) == 2)?, + } +} + +/// Round the argument to the nearest whole number. If the argument is +/// equidistant between two values, it returns the value that is even (it +/// rounds half to even, also known as 'banker's rounding'). +/// +/// ```aiken +/// expect Some(w) = rational.new(2, 3) +/// rational.round_even(w) == 1 +/// +/// expect Some(x) = rational.new(3, 2) +/// rational.round_even(x) == 2 +/// +/// expect Some(y) = rational.new(5, 2) +/// rational.round_even(y) == 2 +/// +/// expect Some(y) = rational.new(-3, 2) +/// rational.round_even(y) == -2 +/// ``` +pub fn round_even(self: Rational) -> Int { + let (n, f) = proper_fraction(self) + + let m = + when compare(f, ratio(0, 1)) is { + Less -> -1 + _ -> 1 + } + + let is_even = n % 2 == 0 + + when compare(abs(f), ratio(1, 2)) is { + Less -> n + Equal if is_even -> n + Equal -> n + m + Greater -> n + m + } +} + +test round_even_1() { + and { + (round_even(ratio(10, 7)) == 1)?, + (round_even(ratio(11, 7)) == 2)?, + (round_even(ratio(3, 2)) == 2)?, + (round_even(ratio(5, 2)) == 2)?, + (round_even(ratio(-3, 2)) == -2)?, + (round_even(ratio(-2, 3)) == -1)?, + (round_even(ratio(-10, 7)) == -1)?, + (round_even(ratio(4, 2)) == 2)?, + } +} + +/// Change the sign of a `Rational`. +/// +/// ```aiken +/// expect Some(x) = rational.new(3, 2) +/// expect Some(y) = rational.new(-3, 2) +/// +/// rational.negate(x) == y +/// rational.negate(y) == x +/// ``` +pub fn negate(a: Rational) -> Rational { + let Rational { numerator: a_n, denominator: a_d } = a + Rational { numerator: -a_n, denominator: a_d } +} + +test negate_1() { + and { + (negate(ratio(5, 2)) == ratio(-5, 2))?, + (negate(ratio(-5, 2)) == ratio(5, 2))?, + (negate(negate(ratio(5, 2))) == ratio(5, 2))?, + } +} + +/// Absolute value of a `Rational`. +/// +/// ```aiken +/// expect Some(x) = rational.new(3, 2) +/// expect Some(y) = rational.new(-3, 2) +/// +/// rational.abs(x) == x +/// rational.abs(y) == x +/// ``` +pub fn abs(self: Rational) -> Rational { + let Rational { numerator: a_n, denominator: a_d } = self + Rational { numerator: math.abs(a_n), denominator: a_d } +} + +test abs_examples() { + and { + (abs(ratio(5, 2)) == ratio(5, 2))?, + (abs(ratio(-5, 2)) == ratio(5, 2))?, + (abs(ratio(5, 2)) == abs(ratio(-5, 2)))?, + } +} + +/// Reciprocal of a `Rational` number. That is, a new `Rational` where the +/// numerator and denominator have been swapped. +/// +/// ```aiken +/// expect Some(x) = rational.new(2, 5) +/// rational.reciprocal(x) == rational.new(5, 2) +/// +/// let y = rational.zero() +/// rational.reciprocal(y) == None +/// ``` +pub fn reciprocal(self: Rational) -> Option { + let Rational { numerator: a_n, denominator: a_d } = self + if a_n < 0 { + Some(Rational { numerator: -a_d, denominator: -a_n }) + } else if a_n > 0 { + Some(Rational { numerator: a_d, denominator: a_n }) + } else { + None + } +} + +test reciprocal_1() { + and { + (reciprocal(ratio(5, 2)) == new(2, 5))?, + (reciprocal(ratio(-5, 2)) == new(-2, 5))?, + (reciprocal(ratio(0, 2)) == None)?, + (reciprocal(ratio(2, 3)) == new(3, 2))?, + (reciprocal(ratio(-2, 3)) == new(-3, 2))?, + } +} + +/// Reduce a rational to its irreducible form. This operation makes the +/// numerator and denominator coprime. +/// +/// ```aiken +/// expect Some(x) = rational.new(80, 200) +/// Some(rational.reduce(x)) == rational.new(2, 5) +/// ``` +pub fn reduce(self: Rational) -> Rational { + let Rational { numerator: a_n, denominator: a_d } = self + let d = math.gcd(a_n, a_d) + Rational { numerator: a_n / d, denominator: a_d / d } +} + +test reduce_1() { + and { + (reduce(ratio(80, 200)) == ratio(2, 5))?, + (reduce(ratio(-5, 1)) == ratio(-5, 1))?, + (reduce(ratio(0, 3)) == ratio(0, 1))?, + } +} + +/// Compare two rationals for an ordering. This is safe to use even for +/// non-reduced rationals. +/// +/// ```aiken +/// expect Some(x) = rational.new(2, 3) +/// expect Some(y) = rational.new(3, 4) +/// expect Some(z) = rational.new(4, 6) +/// +/// compare(x, y) == Less +/// compare(y, x) == Greater +/// compare(x, x) == Equal +/// compare(x, z) == Equal +/// ``` +pub fn compare(left: Rational, right: Rational) -> Ordering { + let Rational { numerator: a_n, denominator: a_d } = left + let Rational { numerator: b_n, denominator: b_d } = right + + let l = a_n * b_d + let r = b_n * a_d + + if l < r { + Less + } else if l > r { + Greater + } else { + Equal + } +} + +test compare_1() { + expect Some(x) = new(2, 3) + expect Some(y) = new(3, 4) + expect Some(z) = new(4, 6) + and { + compare(x, y) == Less, + compare(y, x) == Greater, + compare(x, x) == Equal, + compare(x, z) == Equal, + } +} + +/// Comparison of two rational values using a chosen heuristic. For example: +/// +/// ```aiken +/// expect Some(x) = rational.new(2, 3) +/// expect Some(y) = rational.new(3, 4) +/// +/// rational.compare_with(x, >, y) == False +/// rational.compare_with(y, >, x) == True +/// rational.compare_with(x, >, x) == False +/// rational.compare_with(x, >=, x) == True +/// rational.compare_with(x, ==, x) == True +/// rational.compare_with(x, ==, y) == False +/// ``` +pub fn compare_with( + left: Rational, + with: fn(Int, Int) -> Bool, + right: Rational, +) -> Bool { + let Rational { numerator: a_n, denominator: a_d } = left + let Rational { numerator: b_n, denominator: b_d } = right + with(a_n * b_d, b_n * a_d) +} + +// TODO: Rewrite tests using binary-operator as first-class functions once aiken-lang/aiken#619 is merged. + +test compare_with_eq() { + let eq = + compare_with(_, fn(l, r) { l == r }, _) + + expect Some(x) = new(2, 3) + expect Some(y) = new(3, 4) + + !eq(x, y)? && !eq(y, x)? && eq(x, x)? +} + +test compare_with_neq() { + let neq = + compare_with(_, fn(l, r) { l != r }, _) + + expect Some(x) = new(2, 3) + expect Some(y) = new(3, 4) + + neq(x, y)? && neq(y, x)? && !neq(x, x)? +} + +test compare_with_gte() { + let gte = + compare_with(_, fn(l, r) { l >= r }, _) + + expect Some(x) = new(2, 3) + expect Some(y) = new(3, 4) + + !gte(x, y)? && gte(y, x)? && gte(x, x)? +} + +test compare_with_gt() { + let gt = + compare_with(_, fn(l, r) { l > r }, _) + + expect Some(x) = new(2, 3) + expect Some(y) = new(3, 4) + + !gt(x, y)? && gt(y, x)? && !gt(x, x)? +} + +test compare_with_lte() { + let lte = + compare_with(_, fn(l, r) { l <= r }, _) + + expect Some(x) = new(2, 3) + expect Some(y) = new(3, 4) + + lte(x, y)? && !lte(y, x)? && lte(x, x)? +} + +test compare_with_lt() { + let lt = + compare_with(_, fn(l, r) { l < r }, _) + + expect Some(x) = new(2, 3) + expect Some(y) = new(3, 4) + + lt(x, y)? && !lt(y, x)? && !lt(x, x)? +} + +/// Calculate the arithmetic mean between two `Rational` values. +/// +/// ```aiken +/// let x = rational.from_int(0) +/// let y = rational.from_int(1) +/// let z = rational.from_int(2) +/// +/// expect Some(result) = rational.arithmetic_mean([x, y, z]) +/// +/// rational.compare(result, y) == Equal +/// ``` +pub fn arithmetic_mean(self: List) -> Option { + div(list.foldr(self, zero(), add), from_int(list.length(self))) +} + +test arithmetic_mean_1() { + let x = ratio(1, 2) + let y = ratio(1, 2) + expect Some(z) = arithmetic_mean([x, y]) + reduce(z) == ratio(1, 2) +} + +test arithmetic_mean_2() { + let x = ratio(1, 1) + let y = ratio(2, 1) + expect Some(z) = arithmetic_mean([x, y]) + reduce(z) == ratio(3, 2) +} + +test arithmetic_mean_3() { + let xs = + [ + ratio(1, 1), + ratio(2, 1), + ratio(3, 1), + ratio(4, 1), + ratio(5, 1), + ratio(6, 1), + ] + expect Some(z) = arithmetic_mean(xs) + reduce(z) == ratio(7, 2) +} + +/// Calculate the geometric mean between two `Rational` values. This returns +/// either the exact result or the smallest integer nearest to the square root +/// for the numerator and denominator. +/// +/// ```aiken +/// expect Some(x) = rational.new(1, 3) +/// expect Some(y) = rational.new(1, 6) +/// +/// rational.geometric_mean(x, y) == rational.new(1, 4) +/// ``` +pub fn geometric_mean(left: Rational, right: Rational) -> Option { + let Rational { numerator: a_n, denominator: a_d } = left + let Rational { numerator: b_n, denominator: b_d } = right + when math.sqrt(a_n * b_n) is { + Some(numerator) -> + when math.sqrt(a_d * b_d) is { + Some(denominator) -> Some(Rational { numerator, denominator }) + None -> None + } + None -> None + } +} + +test geometric_mean1() { + expect Some(x) = new(1, 2) + expect Some(y) = new(1, 2) + geometric_mean(x, y) == new(1, 2) +} + +test geometric_mean2() { + expect Some(x) = new(-1, 2) + expect Some(y) = new(1, 2) + geometric_mean(x, y) == None +} + +test geometric_mean3() { + expect Some(x) = new(1, 2) + expect Some(y) = new(-1, 2) + geometric_mean(x, y) == None +} + +test geometric_mean4() { + expect Some(x) = new(1, 3) + expect Some(y) = new(1, 6) + geometric_mean(x, y) == new(1, 4) +} + +test geometric_mean5() { + expect Some(x) = new(67, 2500) + expect Some(y) = new(35331, 1000) + expect Some(yi) = reciprocal(y) + geometric_mean(x, yi) == new(258, 9398) +} diff --git a/example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/option.ak b/example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/option.ak new file mode 100644 index 00000000..8ad35fd0 --- /dev/null +++ b/example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/option.ak @@ -0,0 +1,301 @@ +/// Picks the first element which is not None. If there's no such element, return None. +/// +/// ```aiken +/// option.choice([]) == None +/// option.choice([Some(14), Some(42)]) == Some(14) +/// option.choice([None, Some(42)]) == Some(42) +/// option.choice([None, None]) == None +/// ``` +pub fn choice(self: List>) -> Option { + when self is { + [] -> None + [head, ..others] -> + when head is { + None -> choice(others) + _ -> head + } + } +} + +test choice_1() { + Some(1) == choice([Some(1), Some(2)]) +} + +test choice_2() { + None == choice([]) +} + +test choice_3() { + Some(1) == choice([None, Some(1)]) +} + +/// Provide a default value, turning an optional value into a normal value. +/// +/// ```aiken +/// option.or_else(None, "aiken") == "aiken" +/// option.or_else(Some(42), 14) == 42 +/// ``` +pub fn or_else(self: Option, default: a) -> a { + when self is { + None -> default + Some(a) -> a + } +} + +test or_else_1() { + or_else(None, "aiken") == "aiken" +} + +test or_else_2() { + or_else(Some(42), 14) == 42 +} + +/// Like [`or_else`](#or_else) but allows returning an `Option`. +/// This is effectively mapping the error branch. +/// +/// ```aiken +/// option.or_try(None, fn(_) { Some("aiken") }) == Some("aiken") +/// option.or_try(Some(42), fn(_) { Some(14) }) == Some(42) +/// ``` +pub fn or_try(self: Option, compute_default: fn() -> Option) -> Option { + when self is { + None -> compute_default() + _ -> self + } +} + +test or_try_1() { + or_try(None, fn() { Some("aiken") }) == Some("aiken") +} + +test or_try_2() { + or_try(Some(42), fn() { fail }) == Some(42) +} + +/// Apply a function to the inner value of an [`Option`](#option) +/// +/// ```aiken +/// option.map(None, fn(n) { n * 2 }) == None +/// option.map(Some(14), fn(n) { n * 2 }) == Some(28) +/// ``` +pub fn map(self: Option, with: fn(a) -> result) -> Option { + when self is { + None -> None + Some(a) -> Some(with(a)) + } +} + +test map_1() { + map(None, fn(_) { Void }) == None +} + +test map_2() { + map(Some(14), fn(n) { n + 1 }) == Some(15) +} + +/// Combine two [`Option`](#option) together. +/// +/// ```aiken +/// type Foo { +/// Foo(Int, Int) +/// } +/// +/// option.map2(Some(14), Some(42), Foo) == Some(Foo(14, 42)) +/// option.map2(None, Some(42), Foo) == None +/// option.map2(Some(14), None, Foo) == None +/// ``` +pub fn map2( + opt_a: Option, + opt_b: Option, + with: fn(a, b) -> result, +) -> Option { + when opt_a is { + None -> None + Some(a) -> + when opt_b is { + None -> None + Some(b) -> Some(with(a, b)) + } + } +} + +test map2_1() { + map2(None, Some(42), fn(_, _) { 14 }) == None +} + +test map2_2() { + map2(Some(42), None, fn(_, _) { 14 }) == None +} + +test map2_3() { + map2(Some(14), Some(42), fn(a, b) { (a, b) }) == Some((14, 42)) +} + +/// Combine three [`Option`](#option) together. +/// +/// ```aiken +/// type Foo { +/// Foo(Int, Int, Int) +/// } +/// +/// option.map3(Some(14), Some(42), Some(1337), Foo) == Some(Foo(14, 42, 1337)) +/// option.map3(None, Some(42), Some(1337), Foo) == None +/// option.map3(Some(14), None, None, Foo) == None +/// ``` +pub fn map3( + opt_a: Option, + opt_b: Option, + opt_c: Option, + with: fn(a, b, c) -> result, +) -> Option { + when opt_a is { + None -> None + Some(a) -> + when opt_b is { + None -> None + Some(b) -> + when opt_c is { + None -> None + Some(c) -> Some(with(a, b, c)) + } + } + } +} + +test map3_1() { + map3(None, Some(42), None, fn(_, _, _) { 14 }) == None +} + +test map3_2() { + map3(Some(42), None, None, fn(_, _, _) { 14 }) == None +} + +test map3_3() { + map3(Some(14), Some(42), Some(1337), fn(a, b, c) { c - a + b }) == Some(1365) +} + +/// Chain together many computations that may fail. +/// +/// ```aiken +/// self +/// |> dict.get(policy_id) +/// |> option.and_then(dict.get(_, asset_name)) +/// |> option.or_else(0) +/// ``` +pub fn and_then( + self: Option, + then: fn(a) -> Option, +) -> Option { + when self is { + None -> None + Some(a) -> then(a) + } +} + +fn try_decrement(n: Int) -> Option { + if n > 0 { + Some(n - 1) + } else { + None + } +} + +test and_then_1() { + let result = + None + |> and_then(try_decrement) + result == None +} + +test and_then_2() { + let result = + Some(14) + |> and_then(try_decrement) + result == Some(13) +} + +test and_then_3() { + let result = + Some(0) + |> and_then(try_decrement) + result == None +} + +/// Converts from `Option>` to `Option`. +/// +/// ```aiken +/// option.flatten(Some(Some(42))) == Some(42) +/// option.flatten(Some(None)) == None +/// option.flatten(None) == None +/// ``` +/// +/// Flattening only removes one level of nesting at a time: +/// +/// ```aiken +/// flatten(Some(Some(Some(42)))) == Some(Some(42)) +/// Some(Some(Some(42))) |> flatten |> flatten == Some(42) +/// ``` +pub fn flatten(opt: Option>) -> Option { + when opt is { + Some(inner) -> inner + None -> None + } +} + +test flatten_1() { + let x: Option> = Some(Some(6)) + Some(6) == flatten(x) +} + +test flatten_2() { + let x: Option> = Some(None) + None == flatten(x) +} + +test flatten_3() { + let x: Option> = None + None == flatten(x) +} + +test flatten_4() { + let x: Option>> = Some(Some(Some(6))) + + let result = + x + |> flatten + |> flatten + + Some(6) == result +} + +/// Asserts whether an option is `Some`, irrespective of the value it contains. +pub fn is_some(self: Option) -> Bool { + when self is { + Some(_) -> True + _ -> False + } +} + +test is_some_1() { + is_some(Some(0)) == True +} + +test is_some_2() { + is_some(None) == False +} + +/// Asserts whether an option is `None`. +pub fn is_none(self: Option) -> Bool { + when self is { + Some(_) -> False + _ -> True + } +} + +test is_none_1() { + is_none(Some(0)) == False +} + +test is_none_2() { + is_none(None) == True +} diff --git a/example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/pairs.ak b/example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/pairs.ak new file mode 100644 index 00000000..61849dbd --- /dev/null +++ b/example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/pairs.ak @@ -0,0 +1,589 @@ +//// A module for working with associative lists (a.k.a `Pairs`). +//// +//// While any function that works on `List` also work on `Pairs`, this module provides some extra helpers +//// that are specifically tailored to working with associative lists. Fundamentally, a `Pairs` is +//// a type-alias to `List>`. +//// +//// ### Important +//// +//// Unlike dictionnaries (a.k.a. `Dict`), associative lists make no assumption +//// about the ordering of elements within the list. As a result, lookup +//// functions do traverse the entire list when invoked. They are also not _sets_, +//// and thus allow for duplicate keys. This is reflected in the functions used +//// to interact with them. + +/// Remove a single key-value pair from the `Pairs`. If the key is not found, no changes are made. +/// Duplicate keys are not removed. Only the **first** key found is removed. +/// +/// ```aiken +/// alist.remove_first([], "a") == [] +/// alist.remove_first([Pair("a", 1)], "a") == [] +/// alist.remove_first([Pair("a", 1), Pair("b", 2)], "a") == [Pair("b", 2)] +/// alist.remove_first([Pair("a", 1), Pair("b", 2), Pair("a", 3)], "a") == [Pair("b", 2), Pair("a", 3)] +/// ``` +pub fn remove_first(self: Pairs, key k: key) -> Pairs { + when self is { + [] -> + [] + [Pair(k2, v2), ..rest] -> + if k == k2 { + rest + } else { + [Pair(k2, v2), ..remove_first(rest, k)] + } + } +} + +test remove_first_1() { + remove_first([], "a") == [] +} + +test remove_first_2() { + remove_first([Pair("a", 14)], "a") == [] +} + +test remove_first_3() { + let fixture = + [Pair("a", 14)] + remove_first(fixture, "b") == fixture +} + +test remove_first_4() { + let fixture = + [Pair("a", 1), Pair("b", 2), Pair("a", 3)] + remove_first(fixture, "a") == [Pair("b", 2), Pair("a", 3)] +} + +/// Remove a single key-value pair from the Pairs. If the key is not found, no changes are made. +/// Duplicate keys are not removed. Only the **last** key found is removed. +/// +/// ```aiken +/// alist.remove_last([], "a") == [] +/// alist.remove_last([Pair("a", 1)], "a") == [] +/// alist.remove_last([Pair("a", 1), Pair("b", 2)], "a") == [Pair("b", 2)] +/// alist.remove_last([Pair("a", 1), Pair("b", 2), Pair("a", 3)], "a") == [Pair("a", 1), Pair("b", 2)] +/// ``` +pub fn remove_last(self: Pairs, key k: key) -> Pairs { + when self is { + [] -> + [] + [Pair(k2, v2), ..rest] -> + if k == k2 { + let tail = remove_last(rest, k) + if tail == rest { + rest + } else { + [Pair(k2, v2), ..tail] + } + } else { + [Pair(k2, v2), ..remove_last(rest, k)] + } + } +} + +test remove_last_1() { + remove_last([], "a") == [] +} + +test remove_last_2() { + remove_last([Pair("a", 14)], "a") == [] +} + +test remove_last_3() { + let fixture = + [Pair("a", 14)] + remove_last(fixture, "b") == fixture +} + +test remove_last_4() { + let fixture = + [Pair("a", 1), Pair("b", 2), Pair("a", 3)] + remove_last(fixture, "a") == [Pair("a", 1), Pair("b", 2)] +} + +/// Remove all key-value pairs matching the key from the Pairs. If the key is not found, no changes are made. +/// +/// ```aiken +/// alist.remove_all([], "a") == [] +/// alist.remove_all([Pair("a", 1)], "a") == [] +/// alist.remove_all([Pair("a", 1), Pair("b", 2)], "a") == [Pair("b", 2)] +/// alist.remove_all([Pair("a", 1), Pair("b", 2), Pair("a", 3)], "a") == [Pair("b", 2)] +/// ``` +pub fn remove_all(self: Pairs, key k: key) -> Pairs { + when self is { + [] -> + [] + [Pair(k2, v2), ..rest] -> + if k == k2 { + remove_all(rest, k) + } else { + [Pair(k2, v2), ..remove_all(rest, k)] + } + } +} + +test remove_all_1() { + remove_all([], "a") == [] +} + +test remove_all_2() { + remove_all([Pair("a", 14)], "a") == [] +} + +test remove_all_3() { + let fixture = + [Pair("a", 14)] + remove_all(fixture, "b") == fixture +} + +test remove_all_4() { + let fixture = + [Pair("a", 1), Pair("b", 2), Pair("a", 3)] + remove_all(fixture, "a") == [Pair("b", 2)] +} + +/// Finds the first key in the alist associated with a given value, if any. +/// +/// ```aiken +/// alist.find_first([], 1) == None +/// alist.find_first([Pair("a", 1)], 1) == Some("a") +/// alist.find_first([Pair("a", 1), Pair("b", 2)], 1) == Some("a") +/// alist.find_first([Pair("a", 1), Pair("b", 2), Pair("c", 1)], 1) == Some("a") +/// ``` +pub fn find_first(self: Pairs, v: value) -> Option { + when self is { + [] -> None + [Pair(k2, v2), ..rest] -> + if v == v2 { + Some(k2) + } else { + find_first(rest, v) + } + } +} + +test find_first_1() { + find_first([], "a") == None +} + +test find_first_2() { + find_first([Pair("a", 14)], 14) == Some("a") +} + +test find_first_3() { + find_first([Pair("a", 14)], 42) == None +} + +test find_first_4() { + find_first([Pair("a", 14), Pair("b", 42), Pair("c", 14)], 14) == Some("a") +} + +/// Finds the last key in the alist associated with a given value, if any. +/// +/// ```aiken +/// alist.find_last([], 1) == None +/// alist.find_last([Pair("a", 1)], 1) == Some("a") +/// alist.find_last([Pair("a", 1), Pair("b", 2)], 1) == Some("a") +/// alist.find_last([Pair("a", 1), Pair("b", 2), Pair("c", 1)], 1) == Some("c") +/// ``` +pub fn find_last(self: Pairs, v: value) -> Option { + when self is { + [] -> None + [Pair(k2, v2), ..rest] -> + if v == v2 { + when find_last(rest, v) is { + None -> Some(k2) + some -> some + } + } else { + find_last(rest, v) + } + } +} + +test find_last_1() { + find_last([], "a") == None +} + +test find_last_2() { + find_last([Pair("a", 14)], 14) == Some("a") +} + +test find_last_3() { + find_last([Pair("a", 14)], 42) == None +} + +test find_last_4() { + find_last([Pair("a", 14), Pair("b", 42), Pair("c", 14)], 14) == Some("c") +} + +/// Finds all keys in the alist associated with a given value. +/// +/// ```aiken +/// alist.find_all([], 1) == [] +/// alist.find_all([Pair("a", 1)], 1) == ["a"] +/// alist.find_all([Pair("a", 1), Pair("b", 2)], 1) == ["a"] +/// alist.find_all([Pair("a", 1), Pair("b", 2), Pair("c", 1)], 1) == ["a", "c"] +/// ``` +pub fn find_all(self: Pairs, v: value) -> List { + when self is { + [] -> + [] + [Pair(k2, v2), ..rest] -> + if v == v2 { + [k2, ..find_all(rest, v)] + } else { + find_all(rest, v) + } + } +} + +test find_all_1() { + find_all([], "a") == [] +} + +test find_all_2() { + find_all([Pair("a", 14)], 14) == ["a"] +} + +test find_all_3() { + find_all([Pair("a", 14)], 42) == [] +} + +test find_all_4() { + find_all([Pair("a", 14), Pair("b", 42), Pair("c", 14)], 14) == ["a", "c"] +} + +/// Fold over the key-value pairs in a Pairs. The fold direction follows the +/// order of elements in the Pairs and is done from right-to-left. +/// +/// ```aiken +/// let fixture = [ +/// Pair(1, 100), +/// Pair(2, 200), +/// Pair(3, 300), +/// ] +/// +/// alist.foldr(fixture, 0, fn(k, v, result) { k * v + result }) == 1400 +/// ``` +pub fn foldr( + self: Pairs, + zero: result, + with: fn(key, value, result) -> result, +) -> result { + when self is { + [] -> zero + [Pair(k, v), ..rest] -> with(k, v, foldr(rest, zero, with)) + } +} + +test foldr_1() { + foldr([], 14, fn(_, _, _) { 42 }) == 14 +} + +test foldr_2() { + foldr( + [Pair("a", 42), Pair("b", 14)], + zero: 0, + with: fn(_, v, total) { v + total }, + ) == 56 +} + +test foldr_3() { + let fixture = + [Pair(1, 100), Pair(2, 200), Pair(3, 300)] + + foldr(fixture, 0, fn(k, v, result) { k * v + result }) == 1400 +} + +/// Fold over the key-value pairs in a alist. The fold direction follows keys +/// in ascending order and is done from left-to-right. +/// +/// ```aiken +/// let fixture = [ +/// Pair(1, 100), +/// Pair(2, 200), +/// Pair(3, 300), +/// ] +/// +/// alist.foldl(fixture, 0, fn(k, v, result) { k * v + result }) == 1400 +/// ``` +pub fn foldl( + self: Pairs, + zero: result, + with: fn(key, value, result) -> result, +) -> result { + when self is { + [] -> zero + [Pair(k, v), ..rest] -> foldl(rest, with(k, v, zero), with) + } +} + +test foldl_1() { + foldl([], 14, fn(_, _, _) { 42 }) == 14 +} + +test foldl_2() { + foldl( + [Pair("a", 42), Pair("b", 14)], + zero: 0, + with: fn(_, v, total) { v + total }, + ) == 56 +} + +/// Get the value in the alist by its key. +/// If multiple values with the same key exist, only the first one is returned. +/// +/// ```aiken +/// alist.get_first([], "a") == None +/// alist.get_first([Pair("a", 1)], "a") == Some(1) +/// alist.get_first([Pair("a", 1), Pair("b", 2)], "a") == Some(1) +/// alist.get_first([Pair("a", 1), Pair("b", 2), Pair("a", 3)], "a") == Some(1) +/// ``` +pub fn get_first(self: Pairs, key k: key) -> Option { + when self is { + [] -> None + [Pair(k2, v), ..rest] -> + if k == k2 { + Some(v) + } else { + get_first(rest, k) + } + } +} + +test get_first_1() { + get_first([], "a") == None +} + +test get_first_2() { + get_first([Pair("a", 1)], "a") == Some(1) +} + +test get_first_3() { + get_first([Pair("a", 1), Pair("b", 2)], "a") == Some(1) +} + +test get_first_4() { + get_first([Pair("a", 1), Pair("b", 2), Pair("a", 3)], "a") == Some(1) +} + +test get_first_5() { + get_first([Pair("a", 1), Pair("b", 2), Pair("c", 3)], "d") == None +} + +/// Get the value in the alist by its key. +/// If multiple values with the same key exist, only the last one is returned. +/// +/// ```aiken +/// alist.get_last([], "a") == None +/// alist.get_last([Pair("a", 1)], "a") == Some(1) +/// alist.get_last([Pair("a", 1), Pair("b", 2)], "a") == Some(1) +/// alist.get_last([Pair("a", 1), Pair("b", 2), Pair("a", 3)], "a") == Some(3) +/// ``` +pub fn get_last(self: Pairs, key k: key) -> Option { + when self is { + [] -> None + [Pair(k2, v), ..rest] -> + if k == k2 { + when get_last(rest, k) is { + None -> Some(v) + some -> some + } + } else { + get_last(rest, k) + } + } +} + +test get_last_1() { + get_last([], "a") == None +} + +test get_last_2() { + get_last([Pair("a", 1)], "a") == Some(1) +} + +test get_last_3() { + get_last([Pair("a", 1), Pair("b", 2)], "a") == Some(1) +} + +test get_last_4() { + get_last([Pair("a", 1), Pair("b", 2), Pair("a", 3)], "a") == Some(3) +} + +test get_last_5() { + get_last([Pair("a", 1), Pair("b", 2), Pair("c", 3)], "d") == None +} + +/// Get all values in the alist associated with a given key. +/// +/// ```aiken +/// alist.get_all([], "a") == [] +/// alist.get_all([Pair("a", 1)], "a") == [1] +/// alist.get_all([Pair("a", 1), Pair("b", 2)], "a") == [1] +/// alist.get_all([Pair("a", 1), Pair("b", 2), Pair("a", 3)], "a") == [1, 3] +/// ``` +pub fn get_all(self: Pairs, key k: key) -> List { + when self is { + [] -> + [] + [Pair(k2, v), ..rest] -> + if k == k2 { + [v, ..get_all(rest, k)] + } else { + get_all(rest, k) + } + } +} + +test get_all_1() { + get_all([], "a") == [] +} + +test get_all_2() { + get_all([Pair("a", 1)], "a") == [1] +} + +test get_all_3() { + get_all([Pair("a", 1), Pair("b", 2)], "a") == [1] +} + +test get_all_4() { + get_all([Pair("a", 1), Pair("b", 2), Pair("a", 3)], "a") == [1, 3] +} + +test get_all_5() { + get_all([Pair("a", 1), Pair("b", 2), Pair("c", 3)], "d") == [] +} + +/// Check if a key exists in the alist. +/// +/// ```aiken +/// alist.has_key([], "a") == False +/// alist.has_key([Pair("a", 1)], "a") == True +/// alist.has_key([Pair("a", 1), Pair("b", 2)], "a") == True +/// alist.has_key([Pair("a", 1), Pair("b", 2), Pair("a", 3)], "a") == True +/// ``` +pub fn has_key(self: Pairs, k: key) -> Bool { + when self is { + [] -> False + // || is lazy so this is fine + [Pair(k2, _), ..rest] -> k == k2 || has_key(rest, k) + } +} + +test has_key_1() { + !has_key([], "a") +} + +test has_key_2() { + has_key([Pair("a", 14)], "a") +} + +test has_key_3() { + !has_key([Pair("a", 14)], "b") +} + +test has_key_4() { + has_key([Pair("a", 14), Pair("b", 42)], "b") +} + +test has_key_5() { + has_key([Pair("a", 14), Pair("b", 42), Pair("a", 42)], "a") +} + +/// Extract all the keys present in a given `Pairs`. +/// +/// ```aiken +/// alist.keys([]) == [] +/// alist.keys([Pair("a", 1)]) == ["a"] +/// alist.keys([Pair("a", 1), Pair("b", 2)]) == ["a", "b"] +/// alist.keys([Pair("a", 1), Pair("b", 2), Pair("a", 3)]) == ["a", "b", "a"] +/// ``` +pub fn keys(self: Pairs) -> List { + when self is { + [] -> + [] + [Pair(k, _), ..rest] -> + [k, ..keys(rest)] + } +} + +test keys_1() { + keys([]) == [] +} + +test keys_2() { + keys([Pair("a", 0)]) == ["a"] +} + +test keys_3() { + keys([Pair("a", 0), Pair("b", 0)]) == ["a", "b"] +} + +/// Apply a function to all key-value pairs in a alist, replacing the values. +/// +/// ```aiken +/// let fixture = [Pair("a", 100), Pair("b", 200)] +/// +/// alist.map(fixture, fn(_k, v) { v * 2 }) == [Pair("a", 200), Pair("b", 400)] +/// ``` +pub fn map( + self: Pairs, + with: fn(key, value) -> result, +) -> Pairs { + when self is { + [] -> + [] + [Pair(k, v), ..rest] -> + [Pair(k, with(k, v)), ..map(rest, with)] + } +} + +test map_1() { + let fixture = + [Pair("a", 1), Pair("b", 2)] + + map(fixture, with: fn(k, _) { k }) == [Pair("a", "a"), Pair("b", "b")] +} + +test map_2() { + let fixture = + [Pair("a", 1), Pair("b", 2)] + + map(fixture, with: fn(_, v) { v + 1 }) == [Pair("a", 2), Pair("b", 3)] +} + +/// Extract all the values present in a given `Pairs`. +/// +/// ```aiken +/// alist.values([]) == [] +/// alist.values([Pair("a", 1)]) == [1] +/// alist.values([Pair("a", 1), Pair("b", 2)]) == [1, 2] +/// alist.values([Pair("a", 1), Pair("b", 2), Pair("a", 3)]) == [1, 2, 3] +/// ``` +pub fn values(self: Pairs) -> List { + when self is { + [] -> + [] + [Pair(_, v), ..rest] -> + [v, ..values(rest)] + } +} + +test values_1() { + values([]) == [] +} + +test values_2() { + values([Pair("a", 1)]) == [1] +} + +test values_3() { + values([Pair("a", 1), Pair("b", 2)]) == [1, 2] +} + +test values_4() { + values([Pair("a", 1), Pair("b", 2), Pair("a", 3)]) == [1, 2, 3] +} diff --git a/example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/string.ak b/example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/string.ak new file mode 100644 index 00000000..b9eee08c --- /dev/null +++ b/example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/string.ak @@ -0,0 +1,134 @@ +use aiken/builtin.{ + append_bytearray, append_string, decode_utf8, encode_utf8, length_of_bytearray, +} +use aiken/cbor + +/// Combine two `String` together. +/// +/// ```aiken +/// string.concat(left: @"Hello", right: @", World!") == @"Hello, World!" +/// ``` +pub fn concat(left: String, right: String) -> String { + append_string(left, right) +} + +test concat_1() { + concat(@"", @"") == @"" +} + +test concat_2() { + concat(@"", @"foo") == concat(@"foo", @"") +} + +test concat_3() { + concat(left: @"Hello", right: @", World!") == @"Hello, World!" +} + +/// Convert a `ByteArray` into a `String` +/// +///
⚠️
WARNING
| This functions fails if the underlying `ByteArray` isn't UTF-8-encoded.
In particular, you cannot convert arbitrary hash digests using this function.
For converting arbitrary `ByteArray`s, use [bytearray.to_hex](/stdlib/aiken/bytearray.html#to_hex). +/// --- | --- +/// +/// ```aiken +/// string.from_bytearray("foo") == @"foo" +/// +/// string.from_bytearray(#"666f6f") == @"foo" +/// +/// string.from_bytearray(some_hash) -> fail +/// ``` +pub fn from_bytearray(bytes: ByteArray) -> String { + decode_utf8(bytes) +} + +test from_bytearray_1() { + from_bytearray(#[]) == @"" +} + +test from_bytearray_2() { + from_bytearray(#[65, 66, 67]) == @"ABC" +} + +test from_bytearray_3() { + from_bytearray("ABC") == @"ABC" +} + +/// Convert an `Int` to its `String` representation. +/// +/// ```aiken +/// string.from_int(42) == @"42" +/// ``` +pub fn from_int(n: Int) -> String { + cbor.diagnostic(n) +} + +test from_int_1() { + from_int(0) == @"0" +} + +test from_int_2() { + from_int(5) == @"5" +} + +test from_int_3() { + from_int(42) == @"42" +} + +test from_int_4() { + from_int(200) == @"200" +} + +/// Join a list of strings, separated by a given _delimiter_. +/// +/// ```aiken +/// string.join([], @"+") == @"" +/// string.join([@"a", @"b", @"c"], @",") == @"a,b,c" +/// ``` +pub fn join(list: List, delimiter: String) -> String { + do_join(list, encode_utf8(delimiter), #"") + |> decode_utf8 +} + +fn do_join(xs, delimiter, bytes) { + when xs is { + [] -> bytes + [x, ..rest] -> + do_join( + rest, + delimiter, + if length_of_bytearray(bytes) == 0 { + encode_utf8(x) + } else { + append_bytearray(bytes, append_bytearray(delimiter, encode_utf8(x))) + }, + ) + } +} + +test join_1() { + join([], @",") == @"" +} + +test join_2() { + join([@"a", @"b", @"c"], @",") == @"a,b,c" +} + +/// Convert a `String` into a `ByteArray` +/// +/// ```aiken +/// string.to_bytearray(@"foo") == "foo" +/// ``` +pub fn to_bytearray(self: String) -> ByteArray { + encode_utf8(self) +} + +test to_bytearray_1() { + to_bytearray(@"") == "" +} + +test to_bytearray_2() { + to_bytearray(@"ABC") == #[65, 66, 67] +} + +test to_bytearray_3() { + to_bytearray(@"ABC") == "ABC" +} diff --git a/example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/time.ak b/example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/time.ak new file mode 100644 index 00000000..f1818151 --- /dev/null +++ b/example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/time.ak @@ -0,0 +1,3 @@ +/// A number of milliseconds since 00:00:00 UTC on 1 January 1970. +pub type PosixTime = + Int diff --git a/example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/transaction.ak b/example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/transaction.ak new file mode 100644 index 00000000..eb1572e9 --- /dev/null +++ b/example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/transaction.ak @@ -0,0 +1,224 @@ +use aiken/builtin +use aiken/dict.{Dict} +use aiken/hash.{Blake2b_224, Blake2b_256, Hash, blake2b_256} +use aiken/interval.{Interval} +use aiken/list +use aiken/option +use aiken/time.{PosixTime} +use aiken/transaction/certificate.{Certificate} +use aiken/transaction/credential.{ + Address, Script, ScriptCredential, StakeCredential, VerificationKey, + VerificationKeyCredential, +} +use aiken/transaction/value.{MintedValue, PolicyId, Value} + +/// A context given to a script by the Cardano ledger when being executed. +/// +/// The context contains information about the entire transaction that contains +/// the script. The transaction may also contain other scripts; to distinguish +/// between multiple scripts, the `ScriptContext` also contains a `purpose` +/// which indicates which script (or, for what purpose) of the transaction is +/// being executed. +pub type ScriptContext { + transaction: Transaction, + purpose: ScriptPurpose, +} + +/// Characterizes the kind of script being executed. +pub type ScriptPurpose { + /// For scripts executed as minting/burning policies, to insert + /// or remove assets from circulation. It's parameterized by the identifier + /// of the associated policy. + Mint(PolicyId) + /// For scripts that are used as payment credentials for addresses in + /// transaction outputs. They govern the rule by which the output they + /// reference can be spent. + Spend(OutputReference) + /// For scripts that validate reward withdrawals from a reward account. + /// + /// The argument identifies the target reward account. + WithdrawFrom(StakeCredential) + /// Needed when delegating to a pool using stake credentials defined as a + /// Plutus script. This purpose is also triggered when de-registering such + /// stake credentials. + /// + /// It embeds the certificate that's being validated. + Publish(Certificate) +} + +/// A Cardano `Transaction`, as seen by Plutus scripts. +/// +/// Note that this is a representation of a transaction, and not the 1:1 +/// translation of the transaction as seen by the ledger. In particular, +/// Plutus scripts can't see inputs locked by bootstrap addresses, outputs +/// to bootstrap addresses or just transaction metadata. +pub type Transaction { + inputs: List, + reference_inputs: List, + outputs: List, + fee: Value, + mint: MintedValue, + certificates: List, + withdrawals: Pairs, + validity_range: ValidityRange, + extra_signatories: List>, + redeemers: Pairs, + datums: Dict, Data>, + id: TransactionId, +} + +/// A placeholder / empty `Transaction` to serve as a base in a transaction +/// builder. This is particularly useful for constructing test transactions. +/// +/// Every field is empty or null, and we have in particular: +/// +/// ```aiken +/// use aiken/transaction +/// +/// transaction.placeholder().id == TransactionId { +/// hash: #"0000000000000000000000000000000000000000000000000000000000000000", +/// } +/// +/// transaction.placeholder().validity_range == interval.everything() +/// ``` +pub fn placeholder() -> Transaction { + Transaction { + inputs: [], + reference_inputs: [], + outputs: [], + fee: value.zero(), + mint: value.zero() |> value.to_minted_value(), + certificates: [], + withdrawals: [], + validity_range: interval.everything(), + extra_signatories: [], + redeemers: [], + datums: dict.new(), + id: TransactionId { + hash: #"0000000000000000000000000000000000000000000000000000000000000000", + }, + } +} + +/// An interval of POSIX time, measured in number milliseconds since 1970-01-01T00:00:00Z. +pub type ValidityRange = + Interval + +/// A unique transaction identifier, as the hash of a transaction body. Note that the transaction id +/// isn't a direct hash of the `Transaction` as visible on-chain. Rather, they correspond to hash +/// digests of transaction body as they are serialized on the network. +pub type TransactionId { + hash: Hash, +} + +/// An `Input` made of an output reference and, the resolved value associated with that output. +pub type Input { + output_reference: OutputReference, + output: Output, +} + +/// An `OutputReference` is a unique reference to an output on-chain. The `output_index` +/// corresponds to the position in the output list of the transaction (identified by its id) +/// that produced that output +pub type OutputReference { + transaction_id: TransactionId, + output_index: Int, +} + +/// A transaction `Output`, with an address, a value and optional datums and script references. +pub type Output { + address: Address, + value: Value, + datum: Datum, + reference_script: Option>, +} + +/// An output `Datum`. +pub type Datum { + NoDatum + /// A datum referenced by its hash digest. + DatumHash(Hash) + /// A datum completely inlined in the output. + InlineDatum(Data) +} + +/// A type-alias for Redeemers, passed to scripts for validation. The `Data` is +/// opaque because it is user-defined and it is the script's responsibility to +/// parse it into its expected form. +pub type Redeemer = + Data + +/// Find an input by its [`OutputReference`](#OutputReference). This is typically used in +/// combination with the `Spend` [`ScriptPurpose`](#ScriptPurpose) to find a script's own +/// input. +/// +/// ```aiken +/// validator { +/// fn(datum, redeemer, ctx: ScriptContext) { +/// expect Spend(my_output_reference) = +/// ctx.purpose +/// +/// expect Some(input) = +/// ctx.transaction.inputs +/// |> transaction.find_input(my_output_reference) +/// } +/// } +/// ``` +pub fn find_input( + inputs: List, + output_reference: OutputReference, +) -> Option { + inputs + |> list.find(fn(input) { input.output_reference == output_reference }) +} + +/// Find a [`Datum`](#Datum) by its hash, if present. The function looks first for +/// datums in the witness set, and then for inline datums if it doesn't find any in +/// witnesses. +pub fn find_datum( + outputs: List, + datums: Dict, Data>, + datum_hash: Hash, +) -> Option { + datums + |> dict.get(datum_hash) + |> option.or_try( + fn() { + outputs + |> list.filter_map( + fn(output) { + when output.datum is { + InlineDatum(data) -> + if + blake2b_256(builtin.serialise_data(data)) == datum_hash{ + + Some(data) + } else { + None + } + _ -> None + } + }, + ) + |> list.head + }, + ) +} + +/// Find all outputs that are paying into the given script hash, if any. This is useful for +/// contracts running over multiple transactions. +pub fn find_script_outputs( + outputs: List, + script_hash: Hash, +) -> List { + outputs + |> list.filter( + fn(output) { + when output.address.payment_credential is { + ScriptCredential(addr_script_hash) -> + script_hash == addr_script_hash + VerificationKeyCredential(_) -> False + } + }, + ) +} diff --git a/example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/transaction/certificate.ak b/example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/transaction/certificate.ak new file mode 100644 index 00000000..a5d9c8e7 --- /dev/null +++ b/example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/transaction/certificate.ak @@ -0,0 +1,15 @@ +use aiken/hash.{Blake2b_224, Hash} +use aiken/transaction/credential.{PoolId, StakeCredential, VerificationKey} + +/// An on-chain certificate attesting of some operation. Publishing +/// certificates / triggers different kind of rules; most of the time, +/// they require signatures from / specific keys. +pub type Certificate { + CredentialRegistration { delegator: StakeCredential } + CredentialDeregistration { delegator: StakeCredential } + CredentialDelegation { delegator: StakeCredential, delegatee: PoolId } + PoolRegistration { pool_id: PoolId, vrf: Hash } + PoolDeregistration { pool_id: PoolId, epoch: Int } + Governance + TreasuryMovement +} diff --git a/example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/transaction/credential.ak b/example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/transaction/credential.ak new file mode 100644 index 00000000..fa6327dd --- /dev/null +++ b/example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/transaction/credential.ak @@ -0,0 +1,112 @@ +use aiken/builtin +use aiken/hash.{Blake2b_224, Hash} + +/// A general structure for representing an on-chain `Credential`. +/// +/// Credentials are always one of two kinds: a direct public/private key +/// pair, or a script (native or Plutus). +pub type Credential { + VerificationKeyCredential(Hash) + ScriptCredential(Hash) +} + +/// A Cardano `Address` typically holding one or two credential references. +/// +/// Note that legacy bootstrap addresses (a.k.a. 'Byron addresses') are +/// completely excluded from Plutus contexts. Thus, from an on-chain +/// perspective only exists addresses of type 00, 01, ..., 07 as detailed +/// in [CIP-0019 :: Shelley Addresses](https://github.com/cardano-foundation/CIPs/tree/master/CIP-0019/#shelley-addresses). +pub type Address { + payment_credential: PaymentCredential, + stake_credential: Option, +} + +/// Smart-constructor for an [Address](#Address) from a [verification key](#VerificationKey) hash. The resulting address has no delegation rights whatsoever. +pub fn from_verification_key(vk: Hash) -> Address { + Address { + payment_credential: VerificationKeyCredential(vk), + stake_credential: None, + } +} + +/// Smart-constructor for an [Address](#Address) from a [script](#Script) hash. The address has no delegation rights whatsoever. +pub fn from_script(script: Hash) -> Address { + Address { + payment_credential: ScriptCredential(script), + stake_credential: None, + } +} + +/// Set (or reset) the delegation part of an [Address](#Address) using a [verification key](#VerificationKey) hash. This is useful when combined with [`from_verification_key`](#from_verification_key) and/or [`from_script`](#from_script). +pub fn with_delegation_key( + self: Address, + vk: Hash, +) -> Address { + Address { + payment_credential: self.payment_credential, + stake_credential: Some(Inline(VerificationKeyCredential(vk))), + } +} + +/// Set (or reset) the delegation part of an [Address](#Address) using a [script](#Script) hash. This is useful when combined with [`from_verification_key`](#from_verification_key) and/or [`from_script`](#from_script). +pub fn with_delegation_script( + self: Address, + script: Hash, +) -> Address { + Address { + payment_credential: self.payment_credential, + stake_credential: Some(Inline(ScriptCredential(script))), + } +} + +/// Represent a type of object that can be represented either inline (by hash) +/// or via a reference (i.e. a pointer to an on-chain location). +/// +/// This is mainly use for capturing pointers to a stake credential +/// registration certificate in the case of so-called pointer addresses. +pub type Referenced
{ + Inline(a) + Pointer { slot_number: Int, transaction_index: Int, certificate_index: Int } +} + +pub type VerificationKey = + ByteArray + +pub type Script = + ByteArray + +pub type Signature = + ByteArray + +/// Verify an Ed25519 signature using the given verification key. +/// Returns `True` when the signature is valid. +pub fn verify_signature( + key: VerificationKey, + msg: ByteArray, + sig: Signature, +) -> Bool { + builtin.verify_ed25519_signature(key, msg, sig) +} + +/// A `StakeCredential` represents the delegation and rewards withdrawal conditions +/// associated with some stake address / account. +/// +/// A `StakeCredential` is either provided inline, or, by reference using an +/// on-chain pointer. +/// +/// Read more about pointers in [CIP-0019 :: Pointers](https://github.com/cardano-foundation/CIPs/tree/master/CIP-0019/#pointers). +pub type StakeCredential = + Referenced + +/// A 'PaymentCredential' represents the spending conditions associated with +/// some output. Hence, +/// +/// - a `VerificationKeyCredential` captures an output locked by a public/private key pair; +/// - and a `ScriptCredential` captures an output locked by a native or Plutus script. +/// +pub type PaymentCredential = + Credential + +/// A unique stake pool identifier, as a hash of its owner verification key. +pub type PoolId = + Hash diff --git a/example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/transaction/value.ak b/example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/transaction/value.ak new file mode 100644 index 00000000..eac22032 --- /dev/null +++ b/example-web/aiken/build/packages/aiken-lang-stdlib/lib/aiken/transaction/value.ak @@ -0,0 +1,686 @@ +use aiken/dict.{Dict, from_ascending_pairs_with} +use aiken/hash.{Blake2b_224, Hash} +use aiken/list +use aiken/option +use aiken/transaction/credential.{Script} + +/// A type-alias for a `PolicyId`. A `PolicyId` is always 28-byte long +pub type PolicyId = + Hash + +/// Ada, the native currency, isn't associated with any `PolicyId` (it's not +/// possible to mint Ada!). +/// +/// By convention, it is an empty `ByteArray`. +pub const ada_policy_id = #"" + +/// A type-alias for 'AssetName`, which are free-form byte-arrays between +/// 0 and 32 bytes. +pub type AssetName = + ByteArray + +/// Ada, the native currency, isn't associated with any `AssetName` (it's not +/// possible to mint Ada!). +/// +/// By convention, it is an empty `ByteArray`. +pub const ada_asset_name = #"" + +/// A multi-asset output `Value`. Contains tokens indexed by [PolicyId](#PolicyId) and [AssetName](#AssetName). +/// +/// This type maintain some invariants by construction; in particular, a `Value` will never contain a +/// zero quantity of a particular token. +pub opaque type Value { + inner: Dict>, +} + +/// Construct an empty `Value` with nothing in it. +pub fn zero() -> Value { + Value { inner: dict.new() } +} + +/// Check is a `Value` is zero. That is, it has no assets and holds no Ada/Lovelace. +pub fn is_zero(self: Value) -> Bool { + self == zero() +} + +/// Construct a `Value` from an asset identifier (i.e. `PolicyId` + `AssetName`) +/// and a given quantity. +pub fn from_asset( + policy_id: PolicyId, + asset_name: AssetName, + quantity: Int, +) -> Value { + if quantity == 0 { + dict.new() + |> Value + } else { + let asset = + dict.new() + |> dict.insert(asset_name, quantity) + dict.new() + |> dict.insert(policy_id, asset) + |> Value + } +} + +/// Construct a `Value` from a lovelace quantity. +/// +/// Friendly reminder: 1 Ada = 1.000.000 Lovelace +pub fn from_lovelace(quantity: Int) -> Value { + from_asset(ada_policy_id, ada_asset_name, quantity) +} + +/// Get a `Value` excluding Ada. +pub fn without_lovelace(self: Value) -> Value { + dict.delete(self.inner, ada_policy_id) + |> Value +} + +test without_lovelace_1() { + let v = from_lovelace(1000000) + without_lovelace(v) == zero() +} + +test without_lovelace_2() { + let v = from_lovelace(1000000) + let v2 = from_lovelace(50000000) + without_lovelace(v) == without_lovelace(v2) +} + +test without_lovelace_3() { + let v = + from_asset(#"010203", #"040506", 100) + |> add(ada_policy_id, ada_asset_name, 100000000) + let v2 = from_asset(#"010203", #"040506", 100) + without_lovelace(v) == without_lovelace(v2) && without_lovelace(v) == v2 +} + +/// Negates quantities of all tokens (including Ada) in that `Value`. +/// +/// ``` +/// v1 +/// |> value.negate +/// |> value.merge(v1) +/// |> value.is_zero +/// // True +/// ``` +pub fn negate(self: Value) -> Value { + dict.map(self.inner, fn(_, a) { dict.map(a, fn(_, q) { 0 - q }) }) + |> Value +} + +/// Combine two `Value` together. +pub fn merge(left v0: Value, right v1: Value) -> Value { + Value( + dict.union_with( + v0.inner, + v1.inner, + fn(_, a0, a1) { + let result = + dict.union_with( + a0, + a1, + fn(_, q0, q1) { + let q = q0 + q1 + if q == 0 { + None + } else { + Some(q) + } + }, + ) + if dict.is_empty(result) { + None + } else { + Some(result) + } + }, + ), + ) +} + +test merge_1() { + let v1 = from_lovelace(1) + let v2 = from_lovelace(-1) + merge(v1, v2) == zero() +} + +test merge_2() { + let v1 = from_asset(#"00", #"", 1) + let v2 = from_asset(#"01", #"", 2) + let v3 = from_asset(#"02", #"", 3) + let v = + from_lovelace(42) + |> merge(v3) + |> merge(v1) + |> merge(v2) + + flatten(v) == [ + (#"", #"", 42), + (#"00", #"", 1), + (#"01", #"", 2), + (#"02", #"", 3), + ] +} + +test merge_3() { + let v1 = from_asset(#"00", #"", 1) + let v2 = from_asset(#"00", #"", -1) + let v3 = from_asset(#"01", #"", 1) + + let v = + zero() + |> merge(v1) + |> merge(v2) + |> merge(v3) + + flatten(v) == [(#"01", #"", 1)] +} + +test merge_4() { + let v1 = from_asset(#"00", #"", 1) + let v2 = from_asset(#"00", #"", -1) + + merge(v1, v2) == zero() +} + +test merge_5() { + let v = + zero() + |> add(#"acab", #"beef", 0) + + merge(zero(), v) == zero() +} + +/// Add a (positive or negative) quantity of a single token to a value. +/// This is more efficient than [`merge`](#merge) for a single asset. +pub fn add( + self: Value, + policy_id: PolicyId, + asset_name: AssetName, + quantity: Int, +) -> Value { + if quantity == 0 { + self + } else { + let helper = + fn(_, left, _right) { + let inner_result = + dict.insert_with( + left, + asset_name, + quantity, + fn(_k, ql, qr) { + let q = ql + qr + if q == 0 { + None + } else { + Some(q) + } + }, + ) + if dict.is_empty(inner_result) { + None + } else { + Some(inner_result) + } + } + + Value( + dict.insert_with( + self.inner, + policy_id, + dict.from_ascending_pairs([Pair(asset_name, quantity)]), + helper, + ), + ) + } +} + +test add_1() { + let v = + zero() + |> add(#"acab", #"beef", 321) + |> add(#"acab", #"beef", -321) + v == zero() +} + +test add_2() { + let v = + from_lovelace(123) + |> add(#"acab", #"beef", 321) + |> add(#"acab", #"beef", -1 * 321) + v == from_lovelace(123) +} + +test add_3() { + let v = + from_lovelace(1) + |> add(ada_policy_id, ada_asset_name, 2) + |> add(ada_policy_id, ada_asset_name, 3) + v == from_lovelace(6) +} + +test add_4() { + let v = + zero() + |> add(#"acab", #"beef", 0) + v == zero() +} + +test add_5() { + let v = + zero() + |> add(#"acab", #"beef", 0) + |> add(#"acab", #"beef", 0) + v == zero() +} + +/// Extract the quantity of a given asset. +pub fn quantity_of( + self: Value, + policy_id: PolicyId, + asset_name: AssetName, +) -> Int { + self.inner + |> dict.get(policy_id) + |> option.and_then(dict.get(_, asset_name)) + |> option.or_else(0) +} + +/// A specialized version of `quantity_of` for the Ada currency. +pub fn lovelace_of(self: Value) -> Int { + quantity_of(self, ada_policy_id, ada_asset_name) +} + +/// Get all tokens associated with a given policy. +pub fn tokens(self: Value, policy_id: PolicyId) -> Dict { + self.inner + |> dict.get(policy_id) + |> option.or_else(dict.new()) +} + +/// A list of all token policies in that Value with non-zero tokens. +pub fn policies(self: Value) -> List { + dict.keys(self.inner) +} + +/// Flatten a value as list of 3-tuple (PolicyId, AssetName, Quantity). +/// +/// Handy to manipulate values as uniform lists. +pub fn flatten(self: Value) -> List<(PolicyId, AssetName, Int)> { + dict.foldr( + self.inner, + [], + fn(policy_id, asset_list, value) { + dict.foldr( + asset_list, + value, + fn(asset_name, quantity, xs) { + [(policy_id, asset_name, quantity), ..xs] + }, + ) + }, + ) +} + +/// Flatten a value as a list of results, possibly discarding some along the way. +/// +/// When the transform function returns `None`, the result is discarded altogether. +pub fn flatten_with( + self: Value, + with: fn(PolicyId, AssetName, Int) -> Option, +) -> List { + dict.foldr( + self.inner, + [], + fn(policy_id, asset_list, value) { + dict.foldr( + asset_list, + value, + fn(asset_name, quantity, xs) { + when with(policy_id, asset_name, quantity) is { + None -> xs + Some(x) -> + [x, ..xs] + } + }, + ) + }, + ) +} + +test flatten_with_1() { + flatten_with(zero(), fn(p, a, q) { Some((p, a, q)) }) == [] +} + +test flatten_with_2() { + let v = + zero() + |> add("a", "1", 14) + |> add("b", "", 42) + |> add("a", "2", 42) + + flatten_with( + v, + fn(p, a, q) { + if q == 42 { + Some((p, a)) + } else { + None + } + }, + ) == [("a", "2"), ("b", "")] +} + +/// Reduce a value into a single result +/// +/// ``` +/// value.zero() +/// |> value.add("a", "1", 10) +/// |> value.add("b", "2", 20) +/// |> value.reduce(v, 0, fn(_, _, quantity, acc) { acc + quantity }) +/// // 30 +/// ``` +pub fn reduce( + self: Value, + start: acc, + with: fn(PolicyId, AssetName, Int, acc) -> acc, +) -> acc { + dict.foldr( + self.inner, + start, + fn(policy_id, asset_list, result) { + dict.foldr(asset_list, result, with(policy_id, _, _, _)) + }, + ) +} + +test reduce_1() { + let v = + zero() + |> add("a", "1", 10) + |> add("b", "2", 20) + let result = reduce(v, 0, fn(_, _, quantity, acc) { acc + quantity }) + result == 30 +} + +test reduce_2() { + let v = + zero() + |> add("a", "1", 5) + |> add("a", "2", 15) + |> add("b", "", 10) + let result = + reduce( + v, + [], + fn(policy_id, asset_name, _, acc) { [(policy_id, asset_name), ..acc] }, + ) + result == [("a", "1"), ("a", "2"), ("b", "")] +} + +test reduce_3() { + let v = zero() + let result = reduce(v, 1, fn(_, _, quantity, acc) { acc + quantity }) + result == 1 +} + +/// Promote an arbitrary list of assets into a `Value`. This function fails +/// (i.e. halt the program execution) if: +/// +/// - there's any duplicate amongst `PolicyId`; +/// - there's any duplicate amongst `AssetName`; +/// - the `AssetName` aren't sorted in ascending lexicographic order; or +/// - any asset quantity is null. +/// +/// This function is meant to turn arbitrary user-defined `Data` into safe `Value`, +/// while checking for internal invariants. +pub fn from_asset_list(xs: Pairs>) -> Value { + xs + |> list.foldr( + dict.new(), + fn(inner, acc) { + expect Pair(p, [_, ..] as x) = inner + x + |> from_ascending_pairs_with(fn(v) { v != 0 }) + |> dict.insert_with( + acc, + p, + _, + fn(_, _, _) { + fail @"Duplicate policy in the asset list." + }, + ) + }, + ) + |> Value +} + +test from_asset_list_1() { + let v = from_asset_list([]) + v == zero() +} + +test from_asset_list_2() fail { + let v = from_asset_list([Pair(#"33", [])]) + v == zero() +} + +test from_asset_list_3() fail { + let v = from_asset_list([Pair(#"33", [Pair(#"", 0)])]) + v != zero() +} + +test from_asset_list_4() { + let v = from_asset_list([Pair(#"33", [Pair(#"", 1)])]) + flatten(v) == [(#"33", #"", 1)] +} + +test from_asset_list_5() { + let v = from_asset_list([Pair(#"33", [Pair(#"", 1), Pair(#"33", 1)])]) + flatten(v) == [(#"33", #"", 1), (#"33", #"33", 1)] +} + +test from_asset_list_6() fail { + let v = + from_asset_list( + [ + Pair(#"33", [Pair(#"", 1), Pair(#"33", 1)]), + Pair(#"33", [Pair(#"", 1), Pair(#"33", 1)]), + ], + ) + v != zero() +} + +test from_asset_list_7() fail { + let v = + from_asset_list( + [ + Pair(#"33", [Pair(#"", 1), Pair(#"33", 1)]), + Pair(#"34", [Pair(#"", 1), Pair(#"", 1)]), + ], + ) + v != zero() +} + +test from_asset_list_8() { + let v = + from_asset_list( + [ + Pair(#"33", [Pair(#"", 1), Pair(#"33", 1)]), + Pair(#"34", [Pair(#"31", 1)]), + Pair(#"35", [Pair(#"", 1)]), + ], + ) + flatten(v) == [ + (#"33", #"", 1), + (#"33", #"33", 1), + (#"34", #"31", 1), + (#"35", #"", 1), + ] +} + +test from_asset_list_9() { + let v = + from_asset_list( + [ + Pair(#"35", [Pair(#"", 1)]), + Pair(#"33", [Pair(#"", 1), Pair(#"33", 1)]), + Pair(#"34", [Pair(#"31", 1)]), + ], + ) + flatten(v) == [ + (#"33", #"", 1), + (#"33", #"33", 1), + (#"34", #"31", 1), + (#"35", #"", 1), + ] +} + +/// Convert the value into a dictionary of dictionaries. +pub fn to_dict(self: Value) -> Dict> { + self.inner +} + +/// A multi-asset value that can be found when minting transaction. It always holds +/// a null quantity of _Ada_. Note that because of historical reasons, this is slightly +/// different from `Value` found in transaction outputs. +/// +/// Note that you're never expected to construct a `MintedValue` yourself. If you need to +/// manipulate multi-asset values, use [Value](#Value) +/// +/// See also [`from_minted_value`](#from_minted_value). +pub opaque type MintedValue { + inner: Dict>, +} + +/// Convert minted value into a dictionary of dictionaries. +pub fn minted_to_dict(self: MintedValue) -> Dict> { + self.inner +} + +/// Convert a [`MintedValue`](#MintedValue) into a [`Value`](#Value). +pub fn from_minted_value(self: MintedValue) -> Value { + self.inner |> dict.delete(ada_policy_id) |> Value +} + +test from_minted_value_1() { + flatten(from_minted_value(from_internal_list([]))) == [] +} + +test from_minted_value_2() { + flatten(from_minted_value(from_internal_list([("p0", "a0", 1)]))) == [ + ("p0", "a0", 1), + ] +} + +test from_minted_value_3() { + let assets = + [("p0", "a0", 1), ("p1", "a0", 1), ("p0", "a0", 1), ("p1", "a1", 1)] + + let result = + [("p0", "a0", 2), ("p1", "a0", 1), ("p1", "a1", 1)] + + flatten(from_minted_value(from_internal_list(assets))) == result +} + +test from_minted_value_4() { + let assets = + [ + ("", "", 0), + ("p0", "a0", 1), + ("p1", "a0", 1), + ("p0", "a0", 1), + ("p1", "a1", 1), + ] + + let result = + [("p0", "a0", 2), ("p1", "a0", 1), ("p1", "a1", 1)] + + flatten(from_minted_value(from_internal_list(assets))) == result +} + +test from_minted_value_5() { + let assets = + [ + ("p0", "a0", 1), + ("p0", "a1", 1), + ("p1", "a0", 1), + ("p1", "a1", 1), + ("p1", "a2", 1), + ("p2", "a0", 1), + ("p2", "a1", 1), + ("p3", "a0", 1), + ("p3", "a1", 1), + ("p3", "a2", 1), + ("p3", "a3", 1), + ("p3", "a4", 1), + ("p3", "a5", 1), + ("p3", "a6", 1), + ("p3", "a7", 1), + ] + + flatten(from_minted_value(from_internal_list(assets))) == assets +} + +/// Convert a [`Value`](#Value) into a [`MintedValue`](#MintedValue). +pub fn to_minted_value(self: Value) -> MintedValue { + self.inner + |> dict.insert(ada_policy_id, dict.insert(dict.new(), ada_asset_name, 0)) + |> MintedValue +} + +test to_minted_value_1() { + let minted_value = to_minted_value(zero()) + ( minted_value.inner |> dict.to_pairs |> list.length ) == 1 +} + +test to_minted_value_2() { + let minted_value = to_minted_value(from_lovelace(42)) + ( + minted_value.inner + |> dict.get(ada_policy_id) + |> option.and_then(dict.get(_, ada_asset_name)) + ) == Some(0) +} + +/// Convert a list of tokens into a `MintedValue`. +/// +/// NOTE: Not exposed because we do not want people to construct `MintedValue`. Only +/// get them from the script context. +fn from_internal_list(xs: List<(PolicyId, AssetName, Int)>) -> MintedValue { + list.foldr( + xs, + MintedValue(dict.new()), + fn(elem, st) { + let (policy_id, asset_name, quantity) = elem + unchecked_add(st, policy_id, asset_name, quantity) + }, + ) +} + +fn unchecked_add( + self: MintedValue, + policy_id: PolicyId, + asset_name: AssetName, + quantity: Int, +) -> MintedValue { + MintedValue( + dict.insert_with( + self.inner, + policy_id, + dict.from_ascending_pairs([Pair(asset_name, quantity)]), + fn(_, left, _right) { + Some( + dict.insert_with( + left, + asset_name, + quantity, + fn(_k, ql, qr) { Some(ql + qr) }, + ), + ) + }, + ), + ) +} diff --git a/example-web/aiken/build/packages/packages.toml b/example-web/aiken/build/packages/packages.toml new file mode 100644 index 00000000..a6ebad92 --- /dev/null +++ b/example-web/aiken/build/packages/packages.toml @@ -0,0 +1,9 @@ +[[packages]] +name = "aiken-lang/stdlib" +version = "1.9.0" +source = "github" + +[[packages]] +name = "sidan-lab/vodka" +version = "0.0.1-beta" +source = "github" diff --git a/example-web/aiken/build/packages/sidan-lab-vodka/.github/workflows/build_docs.yml b/example-web/aiken/build/packages/sidan-lab-vodka/.github/workflows/build_docs.yml new file mode 100644 index 00000000..20adfd62 --- /dev/null +++ b/example-web/aiken/build/packages/sidan-lab-vodka/.github/workflows/build_docs.yml @@ -0,0 +1,50 @@ +# Simple workflow for deploying static content to GitHub Pages +name: Deploy static inline documentation to Pages + +on: + # Runs on pushes targeting the default branch + push: + branches: ["main"] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + # Single deploy job since we're just deploying + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Pages + uses: actions/configure-pages@v5 + + - uses: aiken-lang/setup-aiken@v1 + with: + version: "v1.0.29-alpha" + - run: aiken fmt --check + - run: aiken check -D + - run: aiken docs + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + # Upload entire repository + path: "./docs" + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/example-web/aiken/build/packages/sidan-lab-vodka/.github/workflows/release.yml b/example-web/aiken/build/packages/sidan-lab-vodka/.github/workflows/release.yml new file mode 100644 index 00000000..8df19c55 --- /dev/null +++ b/example-web/aiken/build/packages/sidan-lab-vodka/.github/workflows/release.yml @@ -0,0 +1,80 @@ +name: Auto Release + +on: + pull_request: + types: + - closed + branches: + - main + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - uses: aiken-lang/setup-aiken@v1 + with: + version: "v1.0.29-alpha" + - run: aiken fmt --check + - run: aiken check -D + - run: aiken docs + + check-version: + runs-on: ubuntu-latest + if: github.event.pull_request.merged == true + outputs: + version-updated: ${{ steps.compare-versions.outputs.version-updated }} + version: ${{ steps.compare-versions.outputs.version }} + steps: + - name: Checkout main branch at commit before merge + uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.base.sha }} + + - name: Get package version from main branch before merge + id: pre-merge-version + run: | + PRE_MERGE_VERSION=$(grep -m 1 '^version = ' aiken.toml | sed 's/version = "\(.*\)"/\1/') + echo "pre_merge_version=$PRE_MERGE_VERSION" >> "$GITHUB_OUTPUT" + + - name: Checkout main branch at commit after merge + uses: actions/checkout@v4 + with: + ref: "main" + + - name: Get package version from main branch after merge + id: post-merge-version + run: | + POST_MERGE_VERSION=$(grep -m 1 '^version = ' aiken.toml | sed 's/version = "\(.*\)"/\1/') + echo "post_merge_version=$POST_MERGE_VERSION" >> "$GITHUB_OUTPUT" + + - name: Compare versions + id: compare-versions + run: | + if [[ "${{ steps.pre-merge-version.outputs.pre_merge_version }}" != "${{ steps.post-merge-version.outputs.post_merge_version }}" ]]; then + echo "version-updated=true" >> "$GITHUB_OUTPUT" + echo "version=${{ steps.post-merge-version.outputs.post_merge_version }}" >> "$GITHUB_OUTPUT" + else + echo "version-updated=false" >> "$GITHUB_OUTPUT" + fi + + release: + needs: [build, check-version] + if: needs.check-version.outputs.version-updated == 'true' + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Create a Release in a GitHub Action + uses: comnoco/create-release-action@v2.0.5 + with: + tag_name: ${{ needs.check-version.outputs.version }} + release_name: ${{ needs.check-version.outputs.version }} + draft: false + prerelease: false + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/example-web/aiken/build/packages/sidan-lab-vodka/.gitignore b/example-web/aiken/build/packages/sidan-lab-vodka/.gitignore new file mode 100644 index 00000000..7b31be95 --- /dev/null +++ b/example-web/aiken/build/packages/sidan-lab-vodka/.gitignore @@ -0,0 +1,16 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +docs \ No newline at end of file diff --git a/example-web/aiken/build/packages/sidan-lab-vodka/LICENSE b/example-web/aiken/build/packages/sidan-lab-vodka/LICENSE new file mode 100644 index 00000000..261eeb9e --- /dev/null +++ b/example-web/aiken/build/packages/sidan-lab-vodka/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/example-web/aiken/build/packages/sidan-lab-vodka/README.md b/example-web/aiken/build/packages/sidan-lab-vodka/README.md new file mode 100644 index 00000000..a9bd87d5 --- /dev/null +++ b/example-web/aiken/build/packages/sidan-lab-vodka/README.md @@ -0,0 +1,87 @@ +# Vodka + +Vodka is a library build for Aiken development. It offers + +1. Cocktail - Validating utils in writing on-chain code in aiken +2. Mocktail - Unit test utils for easy building mock value for unit test + +## Vodka is pure and simple + +For your transaction. + +```rs +let Transaction { inputs, outputs, extra_signatories, .. } = context.transaction +``` + +Locating inputs & outputs: + +```rs +when (inputs_at(inputs, target_address), outputs_at(outputs, target_address)) is { + ([only_input], [only_output]) -> ... + _ -> False +} +``` + +Checking signature with: + +```rs +key_signed(extra_signatories, key_hash_required) +``` + +## Taste it before vodka cocktail, mocktail can be mixed, blended and Mesh + +Building unit testing in vodka, easily indicating how you should build in [whisky](https://whisky.sidan.io/) and [Mesh](https://meshjs.dev/). + +You can taste if your transaction can pass your aiken contract validation: + +```rs +# Mock transaction +let mock_tx: Transaction = mocktail_tx() + ... + |> required_signer_hash(is_key_provided, mock_pub_key_hex(1)) + |> complete() +``` + +Then move it to blend a whisky: + +```rs +let mut tx = MeshTxBuilder::new_core() +tx.spending_plutus_script_v2() + ... + .required_signer_hash(key_hash) + .complete(None) + +``` + +Or Mesh: + +```ts +const txBuilder = new MeshTxBuilder(); +await txBuilder + ... + .requiredSignerHash(keyHash) + .complete(); +``` + +You can build + +## Start mixing + +Simply run + +```sh +aiken add sidan-lab/vodka --version 0.0.1-beta +``` + +or putting the below in you `aiken.toml` + +```toml +[[dependencies]] +name = "sidan-lab/vodka" +version = "0.0.1-beta" +source = "github" +``` + +## Documentation + +Please refer to the [hosted documentation](https://sidan-lab.github.io/vodka/). diff --git a/example-web/aiken/build/packages/sidan-lab-vodka/aiken.lock b/example-web/aiken/build/packages/sidan-lab-vodka/aiken.lock new file mode 100644 index 00000000..62f2265a --- /dev/null +++ b/example-web/aiken/build/packages/sidan-lab-vodka/aiken.lock @@ -0,0 +1,15 @@ +# This file was generated by Aiken +# You typically do not need to edit this file + +[[requirements]] +name = "aiken-lang/stdlib" +version = "1.9.0" +source = "github" + +[[packages]] +name = "aiken-lang/stdlib" +version = "1.9.0" +requirements = [] +source = "github" + +[etags] diff --git a/example-web/aiken/build/packages/sidan-lab-vodka/aiken.toml b/example-web/aiken/build/packages/sidan-lab-vodka/aiken.toml new file mode 100644 index 00000000..560a7c2b --- /dev/null +++ b/example-web/aiken/build/packages/sidan-lab-vodka/aiken.toml @@ -0,0 +1,14 @@ +name = "sidan-lab/vodka" +version = "0.0.1-beta" +license = "Apache-2.0" +description = "Aiken utils for project 'sidan-lab/vodka" + +[repository] +user = "sidan-lab" +project = "vodka" +platform = "github" + +[[dependencies]] +name = "aiken-lang/stdlib" +version = "1.9.0" +source = "github" diff --git a/example-web/aiken/build/packages/sidan-lab-vodka/build/aiken-compile.lock b/example-web/aiken/build/packages/sidan-lab-vodka/build/aiken-compile.lock new file mode 100644 index 00000000..e69de29b diff --git a/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/.editorconfig b/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/.editorconfig new file mode 100644 index 00000000..0759674c --- /dev/null +++ b/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*.ak] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/.gitattributes b/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/.gitattributes new file mode 100644 index 00000000..99fefcf4 --- /dev/null +++ b/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/.gitattributes @@ -0,0 +1,2 @@ +# Temp hack to get some syntax highlighting on github +*.ak linguist-language=Gleam diff --git a/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/.github/workflows/continuous-integration.yml b/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/.github/workflows/continuous-integration.yml new file mode 100644 index 00000000..29d6f498 --- /dev/null +++ b/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/.github/workflows/continuous-integration.yml @@ -0,0 +1,64 @@ +name: Continuous Integration + +on: + workflow_dispatch: + push: + branches: ["main"] + tags: ["*.*.*"] + pull_request: + branches: ["main"] + +env: + CARGO_TERM_COLOR: always + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: "pages" + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: 📥 Checkout repository + uses: actions/checkout@v3 + + - name: 🧰 Setup Pages + uses: actions/configure-pages@v2 + + - name: 🧰 Install Aiken + uses: aiken-lang/setup-aiken@v1 + with: + version: v1.0.28-alpha + + - name: 📝 Run fmt + run: aiken fmt --check + + - name: 🔬 Run tests + run: aiken check + + - name: 📘 Generate documentation + shell: bash + working-directory: . + run: aiken docs -o docs + + - name: 📦 Upload artifact + uses: actions/upload-pages-artifact@v2 + with: + path: "docs/" + + deploy: + if: ${{ startsWith(github.ref, 'refs/tags') }} + needs: build + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: 🚀 Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v1 diff --git a/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/.gitignore b/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/.gitignore new file mode 100644 index 00000000..3a3d38e6 --- /dev/null +++ b/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/.gitignore @@ -0,0 +1,3 @@ +build/ +docs/ +.DS_Store \ No newline at end of file diff --git a/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/CHANGELOG.md b/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/CHANGELOG.md new file mode 100644 index 00000000..2bec225d --- /dev/null +++ b/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/CHANGELOG.md @@ -0,0 +1,185 @@ +# Changelog + +## v1.9.0 - 2024-05-24 + +### Added + +- A new module [`aiken/pairs`](https://aiken-lang.github.io/stdlib/aiken/pairs.html) to work with associative lists (a.k.a. `Pairs`). + +### Changed + +- **BREAKING-CHANGE**
+ Specialized all `Dict`'s key to `ByteArray`, and thus remove the need for passing an extra comparison function in many functions. `Dict` are however still specialized with a phantom type for keys. + +- **BREAKING-CHANGE**
+ Few functions from `Dict` have been renamed for consistency: + - `from_list` -> `from_pairs` + - `from_ascending_list` -> `from_ascending_pairs` + - `to_list` -> `to_pairs` + +### Removed + +N/A + +## v1.8.0 - 2024-03-28 + +### Added + +- [`value.reduce`](https://aiken-lang.github.io/stdlib/aiken/transaction/value.html#reduce) to efficiently fold over a value and its elements. + +- [`value.from_asset_list`](https://aiken-lang.github.io/stdlib/aiken/transaction/value.html#from_asset_list) to turn an asset list into a Value while enforcing invariants expected of `Value`. + +- [`math.is_sqrt`](https://aiken-lang.github.io/stdlib/aiken/math.html#is_sqrt) as a more efficient alternative to `sqrt`. + +### Changed + +- Disclaimers in documentation to [`bytearray.to_string`](https://aiken-lang.github.io/stdlib/aiken/bytearray.html#to_string) and [`string.from_bytearray`](https://aiken-lang.github.io/stdlib/aiken/string.html#from_bytearray) regarding UTF-8 encoding. + +### Removed + +N/A + +## v1.7.0 - 2023-11-07 + +### Added + +- [`list.index_of`](https://aiken-lang.github.io/stdlib/aiken/list.html#index_of): For getting a values index in a list. +- [`transaction.placeholder`](https://aiken-lang.github.io/stdlib/aiken/transaction.html#placeholder): For constructing test transactions. +- [`transaction.value.is_zero`](https://aiken-lang.github.io/stdlib/aiken/transaction/value.html#is_zero): For checking whether a value is null. + +### Changed + +- [`value.to_minted_value`](https://aiken-lang.github.io/stdlib/aiken/transaction/value.html#to_minted_value) now correctly preserves the invariant of `MintedValue`: it always contain a null quantity of Ada. + +### Removed + +N/A + +## v1.6.0 - 2023-09-08 + +### Added + +- [`math.pow2`](https://aiken-lang.github.io/stdlib/aiken/math.html#pow2): For faster exponentions for powers of two. +- [`bytearray.test_bit`](https://aiken-lang.github.io/stdlib/aiken/bytearray.html#test_bit): For testing if a bit is set in a bytearray (MSB). + +## v1.5.0 - 2023-08-16 + +### Removed + +- retired `list.and` and `list.or` because of the new keywords for logical op chaining. + +## v1.4.0 - 2023-07-21 + +### Changed + +- Fixed missing null-check on `value.add`. Adding a null quantity of token is now correctly a no-op. + +## v1.3.0 - 2023-06-30 + +### Added + +- [`math.sqrt`](https://aiken-lang.github.io/stdlib/aiken/math.html#sqrt): For calculating integer square roots using a quadratically convergent method. +- [`math/rational.numerator`](https://aiken-lang.github.io/stdlib/aiken/math/rational.html#numerator) & [`math/rational.denominator`](https://aiken-lang.github.io/stdlib/aiken/math/rational.html#numerator): For accessing parts of a rational value. +- [`math/rational.arithmetic_mean`](https://aiken-lang.github.io/stdlib/aiken/math/rational.html#arithmetic_mean): For computing [arithmetic mean](https://en.wikipedia.org/wiki/Arithmetic_mean) of rational values. +- [`math/rational.geometric_mean`](https://aiken-lang.github.io/stdlib/aiken/math/rational.html#geometric_mean): For computing [geometric mean](https://en.wikipedia.org/wiki/Geometric_mean) of two rational values. + +### Changed + +- Clear empty asset lists in [`Value`](https://aiken-lang.github.io/stdlib/aiken/transaction/value.html#Value) on various operations. Before that fix, it could happen that removing all assets from a given policy would lead to an empty dictionnary of assets still be present in the `Value`. + +## v1.2.0 - 2023-06-17 + +### Added + +- [`transaction/value.MintedValue`](https://aiken-lang.github.io/stdlib/aiken/transaction/value.html#MintedValue) +- [`transaction/value.from_minted_value`](https://aiken-lang.github.io/stdlib/aiken/transaction/value.html#from_minted_value): Convert from `MintedValue` to `Value` +- [`transaction/value.to_minted_value`](https://aiken-lang.github.io/stdlib/aiken/transaction/value.html#to_minted_value): Convert from `Value` to `MintedValue` +- [`transaction/bytearray.to_hex`](https://aiken-lang.github.io/stdlib/aiken/bytearray.html#to_hex): Convert a `ByteArray` to a hex encoded `String` +- [`math/rational`](https://aiken-lang.github.io/stdlib/aiken/math/rational.html): Working with rational numbers. + - [x] `abs` + - [x] `add` + - [x] `ceil` + - [x] `compare` + - [x] `compare_with` + - [x] `div` + - [x] `floor` + - [x] `from_int` + - [x] `mul` + - [x] `negate` + - [x] `new` + - [x] `proper_fraction` + - [x] `reciprocal` + - [x] `reduce` + - [x] `round` + - [x] `round_even` + - [x] `sub` + - [x] `truncate` + - [x] `zero` + +### Removed + +- module `MintedValue` was merged with `Value` + +## v1.1.0 - 2023-06-06 + +### Added + +- [`list.count`](https://aiken-lang.github.io/stdlib/aiken/list.html#count): Count how many items in the list satisfy the given predicate. + +- [`int.from_utf8`](https://aiken-lang.github.io/stdlib/aiken/int.html#from_utf8): Parse an integer from a utf-8 encoded `ByteArray`, when possible. + +- [`dict.foldl`](https://aiken-lang.github.io/stdlib/aiken/dict.html#foldl) & [`dict.foldr`](https://aiken-lang.github.io/stdlib/aiken/dict.html#foldr): for left and right folds over dictionnary elements in ascending key order. + +- [`dict.insert_with`](https://aiken-lang.github.io/stdlib/aiken/dict.html#insert_with): Insert a value in the dictionary at a given key. When the key already exist, the provided merge function is called. + +- [`transaction/value.add`](https://aiken-lang.github.io/stdlib/aiken/transaction/value.html#add): Add a (positive or negative) quantity of a single token to a value. This is more efficient than `merge` for a single asset. + +- [`transaction/value.to_dict`](https://aiken-lang.github.io/stdlib/aiken/transaction/value.html#to_dict): Convert a `Value` into a dictionnary of dictionnaries. + +- A new module [`transaction/minted_value`](https://aiken-lang.github.io/stdlib/aiken/transaction/minted_value.html): This is used exclusively for representing values present in the `mint` field of transactions. This allows to simplify some of the implementation for `Value` which no longer needs to handle the special case where null-quantity tokens would be present. It isn't possible to construct `MintedValue` by hand, they come from the script context entirely and are 'read-only'. + +- More documentation for `dict` and `interval` modules. + +### Changed + +> **Warning** +> +> Most of those changes are breaking-changes. Though, given we're still in an +> alpha state, only the `minor` component is bumped from the version number. +> Please forgive us. + +- Rework `list.{foldl, foldr, reduce, indexed_foldr}`, `dict.{fold}`, `bytearray.{foldl, foldr, reduce}` to take the iterator as last argument. For example: + + ``` + fn foldl(self: List
, with: fn(a, b) -> b, zero: b) -> b + + ↓ becomes + + fn foldl(self: List, zero: b, with: fn(a, b) -> b) -> b + ``` + +- Fixed implementation of `bytearray.slice`; `slice` would otherwise behave as if the second argument were an offset. + +- Rename `transaction/value.add` into `transaction/value.merge`. + +- Swap arguments of the merge function in `dict.union_with`; the first value received now corresponds to the value already present in the dictionnary. + +- Fixed various examples from the documentation + +### Removed + +- Removed `dict.fold`; replaced with `dict.foldl` and `dict.foldr` to remove ambiguity. + +## v1.0.0 - 2023-04-13 + +### Added + +N/A + +### Changed + +N/A + +### Removed + +N/A diff --git a/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/LICENSE b/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/LICENSE new file mode 100644 index 00000000..4a1de273 --- /dev/null +++ b/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2022 Lucas Rosa + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/README.md b/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/README.md new file mode 100644 index 00000000..b3a4c5ff --- /dev/null +++ b/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/README.md @@ -0,0 +1,62 @@ +
+
+

Aiken Aiken Standard Library

+ +[![Licence](https://img.shields.io/github/license/aiken-lang/stdlib)](https://github.com/aiken-lang/stdlib/blob/main/LICENSE) +[![Continuous Integration](https://github.com/aiken-lang/stdlib/actions/workflows/continuous-integration.yml/badge.svg?branch=main)](https://github.com/aiken-lang/stdlib/actions/workflows/continuous-integration.yml) + +
+
+ +## Compatibility + +aiken's version | stdlib's version(s) +--- | --- +`v1.0.28-alpha` | `1.9.0` +`v1.0.26-alpha` | `1.8.0` + +## Overview + +The official standard library for the [Aiken](https://aiken-lang.org) Cardano +smart-contract language. + +It extends the language builtins with useful data-types, functions, constants +and aliases that make using Aiken a bliss. + +```aiken +use aiken/hash.{Blake2b_224, Hash} +use aiken/list +use aiken/transaction.{ScriptContext} +use aiken/transaction/credential.{VerificationKey} + +pub type Datum { + owner: Hash, +} + +pub type Redeemer { + msg: ByteArray, +} + +/// A simple validator which replicates a basic public/private signature lock. +/// +/// - The key (hash) is set as datum when the funds are sent to the script address. +/// - The spender is expected to provide a signature, and the string 'Hello, World!' as message +/// - The signature is implicitly verified by the ledger, and included as 'extra_signatories' +/// +validator { + fn spend(datum: Datum, redeemer: Redeemer, context: ScriptContext) -> Bool { + let must_say_hello = + redeemer.msg == "Hello, World!" + + let must_be_signed = + context.transaction.extra_signatories + |> list.any(fn(vkh: ByteArray) { vkh == datum.owner }) + + must_say_hello && must_be_signed + } +} +``` + +## Stats + +![Alt](https://repobeats.axiom.co/api/embed/f0a17e7f6133630e165b9e56ec5447bef32fe831.svg "Repobeats analytics image") diff --git a/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/aiken.lock b/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/aiken.lock new file mode 100644 index 00000000..6e350cda --- /dev/null +++ b/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/aiken.lock @@ -0,0 +1,7 @@ +# This file was generated by Aiken +# You typically do not need to edit this file + +requirements = [] +packages = [] + +[etags] diff --git a/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/aiken.toml b/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/aiken.toml new file mode 100644 index 00000000..5d5dc776 --- /dev/null +++ b/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/aiken.toml @@ -0,0 +1,11 @@ +name = "aiken-lang/stdlib" +version = "1.9.0" +licences = ["Apache-2.0"] +description = "The Aiken Standard Library" + +dependencies = [] + +[repository] +user = "aiken-lang" +project = "stdlib" +platform = "github" diff --git a/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/bytearray.ak b/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/bytearray.ak new file mode 100644 index 00000000..840079eb --- /dev/null +++ b/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/bytearray.ak @@ -0,0 +1,529 @@ +use aiken/builtin +use aiken/math +use aiken/option + +/// Compare two bytearrays lexicographically. +/// +/// ```aiken +/// bytearray.compare(#"00", #"FF") == Less +/// bytearray.compare(#"42", #"42") == Equal +/// bytearray.compare(#"FF", #"00") == Greater +/// ``` +pub fn compare(left: ByteArray, right: ByteArray) -> Ordering { + if builtin.less_than_bytearray(left, right) { + Less + } else if builtin.equals_bytearray(left, right) { + Equal + } else { + Greater + } +} + +/// Combine two `ByteArray` together. +/// +/// ```aiken +/// bytearray.concat(left: #[1, 2, 3], right: #[4, 5, 6]) == #[1, 2, 3, 4, 5, 6] +/// ``` +pub fn concat(left: ByteArray, right: ByteArray) -> ByteArray { + builtin.append_bytearray(left, right) +} + +test concat_1() { + concat(#"", #"") == #"" +} + +test concat_2() { + concat(#"", #"01") == #"01" +} + +test concat_3() { + concat(#"0102", #"") == #"0102" +} + +test concat_4() { + concat(#"0102", #"0304") == #"01020304" +} + +/// Returns the suffix of a `ByteArray` after `n` elements. +/// +/// ```aiken +/// bytearray.drop(#[1, 2, 3], n: 2) == #[3] +/// ``` +pub fn drop(self: ByteArray, n: Int) -> ByteArray { + builtin.slice_bytearray(n, builtin.length_of_bytearray(self) - n, self) +} + +test drop_1() { + let x = #"01020304050607" + drop(x, 2) == #"0304050607" +} + +test drop_2() { + let x = #"01020304050607" + drop(x, 0) == x +} + +test drop_3() { + let x = #"01" + drop(x, 1) == #"" +} + +test drop_4() { + let x = #"" + drop(x, 2) == #"" +} + +/// Left-fold over bytes of a [`ByteArray`](https://aiken-lang.github.io/prelude/aiken.html#ByteArray). Note that every byte given to the callback function is comprised between 0 and 255. +/// +/// ```aiken +/// bytearray.foldl(#"acab", 0, fn(byte, acc) { acc * 256 + byte }) == 44203 +/// bytearray.foldl(#[1, 2, 3], #"", flip(bytearray.push)) == #[3, 2, 1] +/// ``` +pub fn foldl( + self: ByteArray, + zero: result, + with: fn(Int, result) -> result, +) -> result { + do_foldl(self, zero, builtin.length_of_bytearray(self), 0, with) +} + +fn do_foldl( + self: ByteArray, + zero: result, + len: Int, + cursor: Int, + with: fn(Int, result) -> result, +) -> result { + if cursor == len { + zero + } else { + do_foldl( + self, + with(builtin.index_bytearray(self, cursor), zero), + len, + cursor + 1, + with, + ) + } +} + +test foldl_1() { + foldl(#[], 42, fn(byte, acc) { byte + acc }) == 42 +} + +test foldl_2() { + foldl(#"acab", 0, fn(byte, acc) { acc * 256 + byte }) == 44203 +} + +test foldl_3() { + foldl( + #"356cf088720a169dae0ce0bb1df8588944389fa43322f0d6ef4ed8c069bfd405", + 0, + fn(byte, acc) { acc * 256 + byte }, + ) == 24165060555594911913195642527692216679757672038384202527929620681761931383813 +} + +test foldl_4() { + foldl(#[1, 2, 3, 4, 5], #"", flip(push)) == #[5, 4, 3, 2, 1] +} + +/// Right-fold over bytes of a [`ByteArray`](https://aiken-lang.github.io/prelude/aiken.html#ByteArray). Note that every byte given to the callback function is comprised between 0 and 255. +/// +/// ```aiken +/// bytearray.foldr(#"acab", 0, fn(byte, acc) { acc * 256 + byte }) == 43948 +/// bytearray.foldl(#[1, 2, 3], #"", flip(bytearray.push)) == #[1, 2, 3] +/// ``` +pub fn foldr( + self: ByteArray, + zero: result, + with: fn(Int, result) -> result, +) -> result { + do_foldr(self, zero, builtin.length_of_bytearray(self) - 1, with) +} + +fn do_foldr( + self: ByteArray, + zero: result, + cursor: Int, + with: fn(Int, result) -> result, +) -> result { + if cursor < 0 { + zero + } else { + do_foldr( + self, + with(builtin.index_bytearray(self, cursor), zero), + cursor - 1, + with, + ) + } +} + +test foldr_1() { + foldr(#[], 42, fn(byte, acc) { byte + acc }) == 42 +} + +test foldr_2() { + foldr(#"acab", 0, fn(byte, acc) { acc * 256 + byte }) == 43948 +} + +test foldr_3() { + foldr(#[1, 2, 3, 4, 5], #"", flip(push)) == #[1, 2, 3, 4, 5] +} + +/// Search the start and end positions of a sub-array in a `ByteArray`. +/// +/// ```aiken +/// bytearray.index_of("Hello, World!", "World") == Some((7, 11)) +/// bytearray.index_of("Hello, World!", "foo") == None +/// bytearray.index_of("Hello, World!", "!") == Some((12, 12)) +/// bytearray.index_of("Hello, World!", "o") == Some((4, 4)) +/// bytearray.index_of("Hello, World!", "Hello, World!") == Some((0, 12)) +/// ``` +pub fn index_of(self: ByteArray, bytes: ByteArray) -> Option<(Int, Int)> { + let offset = length(bytes) + + do_index_of(self, bytes, 0, offset, length(self)) + |> option.map(fn(ix) { (ix, ix + offset - 1) }) +} + +fn do_index_of( + self: ByteArray, + bytes: ByteArray, + cursor: Int, + offset: Int, + size: Int, +) -> Option { + if cursor + offset > size { + None + } else { + if builtin.slice_bytearray(cursor, offset, self) == bytes { + Some(cursor) + } else { + do_index_of(self, bytes, cursor + 1, offset, size) + } + } +} + +test index_of_1() { + index_of("Hello, World!", "World") == Some((7, 11)) +} + +test index_of_2() { + index_of("Hello, World!", "foo") == None +} + +test index_of_3() { + index_of("Hello, World!", "!") == Some((12, 12)) +} + +test index_of_4() { + index_of("Hello, World!", "o") == Some((4, 4)) +} + +test index_of_5() { + index_of("Hello, World!", "Hello, World!") == Some((0, 12)) +} + +/// Returns the number of bytes in a `ByteArray`. +/// +/// ```aiken +/// bytearray.length(#[1, 2, 3]) == 3 +/// ``` +pub fn length(self: ByteArray) -> Int { + builtin.length_of_bytearray(self) +} + +test length_1() { + length(#"") == 0 +} + +test length_2() { + length(#"010203") == 3 +} + +/// Returns `True` when the given `ByteArray` is empty. +/// +/// ```aiken +/// bytearray.is_empty(#"") == True +/// bytearray.is_empty(#"00ff") == False +/// ``` +pub fn is_empty(self: ByteArray) -> Bool { + builtin.length_of_bytearray(self) == 0 +} + +test is_empty_1() { + is_empty(#"") == True +} + +test is_empty_2() { + is_empty(#"01") == False +} + +/// Convert a `String` into a `ByteArray`. +/// +/// ```aiken +/// bytearray.from_string(@"ABC") == #"414243" +/// ``` +pub fn from_string(str: String) -> ByteArray { + builtin.encode_utf8(str) +} + +test from_string_1() { + from_string(@"") == "" +} + +test from_string_2() { + from_string(@"ABC") == #"414243" +} + +/// Add a byte element in front of a `ByteArray`. When the given byte is +/// greater than 255, it wraps-around. **PlutusV2 behavior** So 256 is mapped to 0, 257 to 1, and so +/// forth. +/// In PlutusV3 this will error instead of wrapping around. +/// +/// ```aiken +/// bytearray.push(#"", 0) == #"00" +/// bytearray.push(#"0203", 1) == #"010203" +/// bytearray.push(#"0203", 257) == #"010203" +/// ``` +pub fn push(self: ByteArray, byte: Int) -> ByteArray { + builtin.cons_bytearray(byte, self) +} + +test push_1() { + push(#[], 0) == #[0] +} + +test push_2() { + push(#[2, 3], 1) == #[1, 2, 3] +} + +test push_3() { + let x = 257 + push(#[2, 3], x) == #[1, 2, 3] +} + +/// Reduce bytes in a ByteArray from left to right using the accumulator as left operand. +/// Said differently, this is [`foldl`](#foldl) with callback arguments swapped. +/// +/// ```aiken +/// bytearray.reduce(#[1,2,3], #[], bytearray.push) == #[3, 2, 1] +/// ``` +pub fn reduce( + self: ByteArray, + zero: result, + with: fn(result, Int) -> result, +) -> result { + foldl(self, zero, flip(with)) +} + +test reduce_1() { + reduce(#[], #[], push) == #[] +} + +test reduce_2() { + reduce(#[1, 2, 3], #[], push) == #[3, 2, 1] +} + +/// Extract a `ByteArray` as a slice of another `ByteArray`. +/// +/// Indexes are 0-based and inclusive. +/// +/// ```aiken +/// bytearray.slice(#[0, 1, 2, 3, 4, 5, 6], start: 1, end: 3) == #[1, 2, 3] +/// ``` +pub fn slice(self: ByteArray, start: Int, end: Int) -> ByteArray { + builtin.slice_bytearray(start, end - start + 1, self) +} + +test slice_1() { + slice(#"", 1, 2) == #"" +} + +test slice_2() { + slice(#"010203", 1, 2) == #"0203" +} + +test slice_3() { + slice(#"010203", 0, 42) == #"010203" +} + +test slice_4() { + slice(#[0, 1, 2, 3, 4], 0, 3) == #[0, 1, 2, 3] +} + +test slice_5() { + slice(#[0, 1, 2, 3, 4], 1, 2) == #[1, 2] +} + +/// Returns the n-length prefix of a `ByteArray`. +/// +/// ```aiken +/// bytearray.take(#[1, 2, 3], n: 2) == #[1, 2] +/// ``` +pub fn take(self: ByteArray, n: Int) -> ByteArray { + builtin.slice_bytearray(0, n, self) +} + +test take_1() { + let x = #"01020304050607" + take(x, 2) == #"0102" +} + +test take_2() { + let x = #"01020304050607" + take(x, 0) == #"" +} + +test take_3() { + let x = #"01" + take(x, 1) == x +} + +test take_4() { + let x = #"010203" + take(x, 0) == #"" +} + +/// Convert a `ByteArray` into a `String`. +/// +///
⚠️
WARNING
| This functions fails if the underlying `ByteArray` isn't UTF-8-encoded.
In particular, you cannot convert arbitrary hash digests using this function.
For converting arbitrary `ByteArray`s, use [bytearray.to_hex](#to_hex). +/// --- | --- +/// +/// +/// ```aiken +/// bytearray.to_string(#"414243") == "ABC" +/// +/// bytearray.to_string(some_hash) -> fail +/// ``` +pub fn to_string(self: ByteArray) -> String { + builtin.decode_utf8(self) +} + +test to_string_1() { + to_string("") == @"" +} + +test to_string_2() { + to_string("ABC") == @"ABC" +} + +/// Encode a `ByteArray` as a hexidecimal `String`. +/// +/// ```aiken +/// use aiken/bytearray +/// +/// bytearray.to_hex("Hello world!") == @"48656c6c6f20776f726c6421" +/// ``` +pub fn to_hex(self: ByteArray) -> String { + self + |> encode_base16(builtin.length_of_bytearray(self) - 1, "") + |> builtin.decode_utf8 +} + +// Construct an hex string in reverse order, from the back. The 'builder' is an +// accumulator. It works fast because `index_bytearray` follows a constant-time cost +// model +fn encode_base16(bytes: ByteArray, ix: Int, builder: ByteArray) -> ByteArray { + if ix < 0 { + builder + } else { + let byte = builtin.index_bytearray(bytes, ix) + let msb = byte / 16 + let lsb = byte % 16 + encode_base16( + bytes, + ix - 1, + builtin.cons_bytearray( + msb + if msb < 10 { + 48 + } else { + 87 + }, + builtin.cons_bytearray( + lsb + if lsb < 10 { + 48 + } else { + 87 + }, + builder, + ), + ), + ) + } +} + +test to_hex_1() { + to_hex("Hello world!") == @"48656c6c6f20776f726c6421" +} + +test to_hex_2() { + to_hex("The quick brown fox jumps over the lazy dog") == @"54686520717569636b2062726f776e20666f78206a756d7073206f76657220746865206c617a7920646f67" +} + +/// Checks whether a bit (Most-Significant-Bit first) is set in the given 'ByteArray'. +/// +/// For example, consider the following bytearray: `#"8b765f"`. It can also be written as the +/// following bits sequence: +/// +/// `8` | `b` | `7` | `6` | `5` | `f` +/// --- | --- | --- | --- | --- | --- +/// `1000` | `1011` | `0111` | `0110` | `0101` | `1111` +/// +/// And thus, we have: +/// +/// ```aiken +/// test_bit(#"8b765f", 0) == True +/// test_bit(#"8b765f", 1) == False +/// test_bit(#"8b765f", 2) == False +/// test_bit(#"8b765f", 3) == False +/// test_bit(#"8b765f", 7) == True +/// test_bit(#"8b765f", 8) == False +/// test_bit(#"8b765f", 20) == True +/// test_bit(#"8b765f", 21) == True +/// test_bit(#"8b765f", 22) == True +/// test_bit(#"8b765f", 23) == True +/// ``` +pub fn test_bit(self: ByteArray, ix: Int) -> Bool { + builtin.less_than_equals_bytearray( + #[128], + builtin.cons_bytearray( + builtin.index_bytearray(self, ix / 8) * math.pow2(ix % 8), + "", + ), + ) +} + +test test_bit_0() { + test_bit(#"8b765f", 0) +} + +test test_bit_1() { + !test_bit(#"8b765f", 1) +} + +test test_bit_2() { + !test_bit(#"8b765f", 2) +} + +test test_bit_3() { + !test_bit(#"8b765f", 3) +} + +test test_bit_7() { + test_bit(#"8b765f", 7) +} + +test test_bit_8() { + !test_bit(#"8b765f", 8) +} + +test test_bit_20_21_22_23() { + and { + test_bit(#"8b765f", 20), + test_bit(#"8b765f", 21), + test_bit(#"8b765f", 22), + test_bit(#"8b765f", 23), + } +} diff --git a/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/cbor.ak b/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/cbor.ak new file mode 100644 index 00000000..5a5ec2e6 --- /dev/null +++ b/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/cbor.ak @@ -0,0 +1,354 @@ +use aiken/builtin.{ + append_bytearray, choose_data, cons_bytearray, decode_utf8, index_bytearray, + length_of_bytearray, quotient_integer, remainder_integer, serialise_data, + un_b_data, un_constr_data, un_i_data, un_list_data, un_map_data, +} +use aiken/list + +/// Serialise any value to binary, encoding using [CBOR](https://www.rfc-editor.org/rfc/rfc8949). +/// +/// This is particularly useful in combination with hashing functions, as a way +/// to obtain a byte representation that matches the serialised representation +/// used by the ledger in the context of on-chain code. +/// +/// Note that the output matches the output of [`diagnostic`](#diagnostic), +/// though with a different encoding. [`diagnostic`](#diagnostic) is merely a +/// textual representation of the CBOR encoding that is human friendly and +/// useful for debugging. +/// +/// ```aiken +/// serialise(42) == #"182a" +/// serialise(#"a1b2") == #"42a1b2" +/// serialise([]) == #"80" +/// serialise((1, 2)) == #"9f0102ff" +/// serialise((1, #"ff", 3)) == #"9f0141ff03ff" +/// serialise([(1, #"ff")]) == #"a10141ff" +/// serialise(Some(42)) == #"d8799f182aff" +/// serialise(None) == #"d87a80" +/// ``` +pub fn serialise(self: Data) -> ByteArray { + serialise_data(self) +} + +test serialise_1() { + serialise(42) == #"182a" +} + +test serialise_2() { + serialise(#"a1b2") == #"42a1b2" +} + +test serialise_3() { + serialise([]) == #"80" +} + +test serialise_4() { + serialise((1, 2)) == #"9f0102ff" +} + +test serialise_5() { + serialise((1, #"ff", 3)) == #"9f0141ff03ff" +} + +test serialise_6() { + serialise([(1, #"ff")]) == #"9f9f0141ffffff" +} + +test serialise_7() { + serialise(Some(42)) == #"d8799f182aff" +} + +test serialise_8() { + serialise(None) == #"d87a80" +} + +test serialise_9() { + serialise([Pair(1, #"ff")]) == #"a10141ff" +} + +/// Obtain a String representation of _anything_. This is particularly (and only) useful for tracing +/// and debugging. This function is expensive and should not be used in any production code as it +/// will very likely explodes the validator's budget. +/// +/// The output is a [CBOR diagnostic](https://www.rfc-editor.org/rfc/rfc8949#name-diagnostic-notation) +/// of the underlying on-chain binary representation of the data. It's not as +/// easy to read as plain Aiken code, but it is handy for troubleshooting values +/// _at runtime_. Incidentally, getting familiar with reading CBOR diagnostic is +/// a good idea in the Cardano world. +/// +/// ```aiken +/// diagnostic(42) == "42" +/// diagnostic(#"a1b2") == "h'A1B2'" +/// diagnostic([1, 2, 3]) == "[_ 1, 2, 3]" +/// diagnostic([]) == "[]" +/// diagnostic((1, 2)) == "[_ 1, 2]" +/// diagnostic((1, #"ff", 3)) == "[_ 1, h'FF', 3]" +/// diagnostic([(1, #"ff")]) == "{_ 1: h'FF' }" +/// diagnostic(Some(42)) == "121([_ 42])" +/// diagnostic(None) == "122([])" +/// ``` +pub fn diagnostic(self: Data) -> String { + do_diagnostic(self, #"") + |> decode_utf8 +} + +/// UTF-8 lookup table. Comes in handy to decipher the code below. +/// +/// | Symbol | Decimal | Hex | +/// | --- | --- | --- | +/// | | 32 | 0x20 | +/// | ' | 39 | 0x27 | +/// | ( | 40 | 0x28 | +/// | ) | 41 | 0x29 | +/// | , | 44 | 0x2c | +/// | 0 | 48 | 0x30 | +/// | : | 58 | 0x3a | +/// | A | 65 | 0x41 | +/// | [ | 91 | 0x5b | +/// | ] | 93 | 0x5d | +/// | _ | 95 | 0x5f | +/// | h | 104 | 0x68 | +/// | { | 123 | 0x7b | +/// | } | 125 | 0x7d | +fn do_diagnostic(self: Data, builder: ByteArray) -> ByteArray { + choose_data( + self, + { + // -------- Constr + let Pair(constr, fields) = un_constr_data(self) + + // NOTE: This is fundamentally the same logic for serializing list. However, the compiler + // doesn't support mutual recursion just yet, so we can't extract that logic in a separate + // function. + // + // See [aiken-lang/aiken#389](https://github.com/aiken-lang/aiken/pull/389) + let builder = + when fields is { + [] -> append_bytearray(#"5b5d29", builder) + _ -> { + let (_, bytes) = + list.foldr( + fields, + (#"5d", append_bytearray(#"29", builder)), + fn(e: Data, st: (ByteArray, ByteArray)) { + (#"2c20", do_diagnostic(e, append_bytearray(st.1st, st.2nd))) + }, + ) + append_bytearray(#"5b5f20", bytes) + } + } + + let constr_tag = + if constr < 7 { + 121 + constr + } else if constr < 128 { + 1280 + constr - 7 + } else { + fail @"What are you doing? No I mean, seriously." + } + + builder + |> append_bytearray(#"28", _) + |> from_int(constr_tag, _) + }, + { + // -------- Map + + let elems = un_map_data(self) + when elems is { + [] -> append_bytearray(#"7b7d", builder) + _ -> { + let (_, bytes) = + list.foldr( + elems, + (#"207d", builder), + fn(e: Pair, st: (ByteArray, ByteArray)) { + let value = + do_diagnostic(e.2nd, append_bytearray(st.1st, st.2nd)) + ( + #"2c20", + do_diagnostic(e.1st, append_bytearray(#"3a20", value)), + ) + }, + ) + append_bytearray(#"7b5f20", bytes) + } + } + }, + { + // -------- List + + let elems = un_list_data(self) + when elems is { + [] -> append_bytearray(#"5b5d", builder) + _ -> { + let (_, bytes) = + list.foldr( + elems, + (#"5d", builder), + fn(e: Data, st: (ByteArray, ByteArray)) { + (#"2c20", do_diagnostic(e, append_bytearray(st.1st, st.2nd))) + }, + ) + append_bytearray(#"5b5f20", bytes) + } + } + }, + // -------- Integer + self + |> un_i_data + |> from_int(builder), + { + // -------- ByteArray + let bytes = un_b_data(self) + + bytes + |> encode_base16( + length_of_bytearray(bytes) - 1, + append_bytearray(#"27", builder), + ) + |> append_bytearray(#"6827", _) + }, + ) +} + +fn encode_base16(bytes: ByteArray, ix: Int, builder: ByteArray) -> ByteArray { + if ix < 0 { + builder + } else { + let byte = index_bytearray(bytes, ix) + let msb = byte / 16 + let lsb = byte % 16 + let builder = + cons_bytearray( + msb + if msb < 10 { + 48 + } else { + 55 + }, + cons_bytearray( + lsb + if lsb < 10 { + 48 + } else { + 55 + }, + builder, + ), + ) + encode_base16(bytes, ix - 1, builder) + } +} + +fn from_int(i: Int, digits: ByteArray) -> ByteArray { + if i == 0 { + append_bytearray(#"30", digits) + } else if i < 0 { + append_bytearray(#"2d", from_int(-i, digits)) + } else { + do_from_int( + quotient_integer(i, 10), + cons_bytearray(remainder_integer(i, 10) + 48, digits), + ) + } +} + +fn do_from_int(i: Int, digits: ByteArray) -> ByteArray { + if i <= 0 { + digits + } else { + do_from_int( + quotient_integer(i, 10), + cons_bytearray(remainder_integer(i, 10) + 48, digits), + ) + } +} + +test diagnostic_1() { + diagnostic(42) == @"42" +} + +test diagnostic_2() { + diagnostic(#"a1b2") == @"h'A1B2'" +} + +test diagnostic_3() { + diagnostic([1, 2, 3]) == @"[_ 1, 2, 3]" +} + +test diagnostic_4() { + diagnostic([]) == @"[]" +} + +test diagnostic_5() { + diagnostic((1, 2)) == @"[_ 1, 2]" +} + +test diagnostic_6() { + diagnostic((1, #"ff", 3)) == @"[_ 1, h'FF', 3]" +} + +test diagnostic_7() { + diagnostic([(1, #"ff")]) == @"[_ [_ 1, h'FF']]" +} + +test diagnostic_7_alt() { + diagnostic([Pair(1, #"ff")]) == @"{_ 1: h'FF' }" +} + +test diagnostic_8() { + diagnostic(Some(42)) == @"121([_ 42])" +} + +test diagnostic_9() { + diagnostic(None) == @"122([])" +} + +test diagnostic_10() { + let xs: List<(Int, Int)> = + [] + diagnostic(xs) == @"[]" +} + +test diagnostic_10_alt() { + let xs: Pairs = + [] + diagnostic(xs) == @"{}" +} + +type Foo { + foo: Bar, +} + +type Bar { + A + B(Int) +} + +test diagnostic_11() { + diagnostic(Foo { foo: A }) == @"121([_ 121([])])" +} + +test diagnostic_12() { + diagnostic(Foo { foo: B(42) }) == @"121([_ 122([_ 42])])" +} + +type Baz { + a0: Int, + b0: ByteArray, +} + +test diagnostic_13() { + diagnostic(Baz { a0: 14, b0: #"ff" }) == @"121([_ 14, h'FF'])" +} + +test diagnostic_14() { + diagnostic([0]) == @"[_ 0]" +} + +test diagnostic_15() { + diagnostic(-42) == @"-42" +} + +test diagnostic_16() { + diagnostic([-1, 0, 1]) == @"[_ -1, 0, 1]" +} diff --git a/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/dict.ak b/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/dict.ak new file mode 100644 index 00000000..d2804437 --- /dev/null +++ b/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/dict.ak @@ -0,0 +1,1117 @@ +//// A module for working with bytearray dictionaries. +//// +//// ### Important +//// +//// Dictionaries are **ordered sets** of key-value pairs, which thus +//// preserve some invariants. Specifically, each key is only present once in +//// the dictionary and all keys are stored in ascending lexicographic order. +//// +//// These invariants allow for more optimized functions to operate on `Dict`, +//// but as a trade-offs, prevent `Dict` from being serializable. To recover a `Dict` +//// from an unknown `Data`, you must first recover an `Pairs` and use +//// `dict.from_ascending_list`. + +use aiken/builtin + +/// An opaque `Dict`. The type is opaque because the module maintains some +/// invariant, namely: there's only one occurrence of a given key in the dictionary. +/// +/// Note that the `key` parameter is a phantom-type, and only present as a +/// means of documentation. Keys can be any type, yet will need to comparable +/// to use functions like `insert`. +/// +/// See for example: +/// +/// ```aiken +/// pub type Value = +/// Dict> +/// ``` +pub opaque type Dict { + inner: Pairs, +} + +/// Create a new empty Dict +/// ```aiken +/// dict.to_pairs(dict.new()) == [] +/// ``` +pub fn new() -> Dict { + Dict { inner: [] } +} + +const foo = #"666f6f" + +const bar = #"626172" + +const baz = #"62617a" + +fn fixture_1() { + new() + |> insert(foo, 42) + |> insert(bar, 14) +} + +/// Remove a key-value pair from the dictionary. If the key is not found, no changes are made. +/// +/// ```aiken +/// let result = +/// dict.new() +/// |> dict.insert(key: "a", value: 100) +/// |> dict.insert(key: "b", value: 200) +/// |> dict.delete(key: "a") +/// |> dict.to_pairs() +/// +/// result == [Pair("b", 200)] +/// ``` +pub fn delete(self: Dict, key: ByteArray) -> Dict { + Dict { inner: do_delete(self.inner, key) } +} + +fn do_delete( + self: Pairs, + key k: ByteArray, +) -> Pairs { + when self is { + [] -> + [] + [Pair(k2, v2), ..rest] -> + if builtin.less_than_equals_bytearray(k, k2) { + if k == k2 { + rest + } else { + self + } + } else { + [Pair(k2, v2), ..do_delete(rest, k)] + } + } +} + +test delete_1() { + delete(new(), foo) == new() +} + +test delete_2() { + let m = + new() + |> insert(foo, 14) + delete(m, foo) == new() +} + +test delete_3() { + let m = + new() + |> insert(foo, 14) + delete(m, bar) == m +} + +test delete_4() { + let m = + new() + |> insert(foo, 14) + |> insert(bar, 14) + !has_key(delete(m, foo), foo) +} + +test delete_5() { + let m = + new() + |> insert(foo, 14) + |> insert(bar, 14) + has_key(delete(m, bar), foo) +} + +test delete_6() { + let m = + new() + |> insert("aaa", 1) + |> insert("bbb", 2) + |> insert("ccc", 3) + |> insert("ddd", 4) + |> insert("eee", 5) + |> insert("fff", 6) + |> insert("ggg", 7) + |> insert("hhh", 8) + |> insert("iii", 9) + |> insert("jjj", 10) + + delete(m, "bcd") == m +} + +/// Keep only the key-value pairs that pass the given predicate. +/// +/// ```aiken +/// let result = +/// dict.new() +/// |> dict.insert(key: "a", value: 100) +/// |> dict.insert(key: "b", value: 200) +/// |> dict.insert(key: "c", value: 300) +/// |> dict.filter(fn(k, _v) { k != "a" }) +/// |> dict.to_pairs() +/// +/// result == [Pair("b", 200), Pair("c", 300)] +/// ``` +pub fn filter( + self: Dict, + with: fn(ByteArray, value) -> Bool, +) -> Dict { + Dict { inner: do_filter(self.inner, with) } +} + +fn do_filter( + self: Pairs, + with: fn(ByteArray, value) -> Bool, +) -> Pairs { + when self is { + [] -> + [] + [Pair(k, v), ..rest] -> + if with(k, v) { + [Pair(k, v), ..do_filter(rest, with)] + } else { + do_filter(rest, with) + } + } +} + +test filter_1() { + filter(new(), fn(_, _) { True }) == new() +} + +test filter_2() { + let expected = + new() + |> insert(foo, 42) + filter(fixture_1(), fn(_, v) { v > 14 }) == expected +} + +test filter_3() { + let expected = + new() + |> insert(bar, 14) + filter(fixture_1(), fn(k, _) { k == bar }) == expected +} + +/// Finds a value in the dictionary, and returns the first key found to have that value. +/// +/// ```aiken +/// let result = +/// dict.new() +/// |> dict.insert(key: "a", value: 42) +/// |> dict.insert(key: "b", value: 14) +/// |> dict.insert(key: "c", value: 42) +/// |> dict.find(42) +/// +/// result == Some("a") +/// ``` +pub fn find(self: Dict, value v: value) -> Option { + do_find(self.inner, v) +} + +fn do_find(self: Pairs, value v: value) -> Option { + when self is { + [] -> None + [Pair(k2, v2), ..rest] -> + if v == v2 { + Some(k2) + } else { + do_find(rest, v) + } + } +} + +test find_1() { + find(new(), foo) == None +} + +test find_2() { + find( + new() + |> insert(foo, 14), + 14, + ) == Some(foo) +} + +test find_3() { + find( + new() + |> insert(foo, 14), + 42, + ) == None +} + +test find_4() { + find( + new() + |> insert(foo, 14) + |> insert(bar, 42) + |> insert(baz, 14), + 14, + ) == Some(baz) +} + +/// Fold over the key-value pairs in a dictionary. The fold direction follows keys +/// in ascending order and is done from right-to-left. +/// +/// ```aiken +/// let result = +/// dict.new() +/// |> dict.insert(key: "a", value: 100) +/// |> dict.insert(key: "b", value: 200) +/// |> dict.insert(key: "c", value: 300) +/// |> dict.foldr(0, fn(_k, v, r) { v + r }) +/// +/// result == 600 +/// ``` +pub fn foldr( + self: Dict, + zero: result, + with: fn(ByteArray, value, result) -> result, +) -> result { + do_foldr(self.inner, zero, with) +} + +fn do_foldr( + self: Pairs, + zero: result, + with: fn(ByteArray, value, result) -> result, +) -> result { + when self is { + [] -> zero + [Pair(k, v), ..rest] -> with(k, v, do_foldr(rest, zero, with)) + } +} + +test foldr_1() { + foldr(new(), 14, fn(_, _, _) { 42 }) == 14 +} + +test foldr_2() { + foldr(fixture_1(), zero: 0, with: fn(_, v, total) { v + total }) == 56 +} + +/// Fold over the key-value pairs in a dictionary. The fold direction follows keys +/// in ascending order and is done from left-to-right. +/// +/// ```aiken +/// let result = +/// dict.new() +/// |> dict.insert(key: "a", value: 100) +/// |> dict.insert(key: "b", value: 200) +/// |> dict.insert(key: "c", value: 300) +/// |> dict.foldl(0, fn(_k, v, r) { v + r }) +/// +/// result == 600 +/// ``` +pub fn foldl( + self: Dict, + zero: result, + with: fn(ByteArray, value, result) -> result, +) -> result { + do_foldl(self.inner, zero, with) +} + +fn do_foldl( + self: Pairs, + zero: result, + with: fn(ByteArray, value, result) -> result, +) -> result { + when self is { + [] -> zero + [Pair(k, v), ..rest] -> do_foldl(rest, with(k, v, zero), with) + } +} + +test fold_1() { + foldl(new(), 14, fn(_, _, _) { 42 }) == 14 +} + +test fold_2() { + foldl(fixture_1(), zero: 0, with: fn(_, v, total) { v + total }) == 56 +} + +/// Construct a dictionary from a list of key-value pairs. Note that when a key is present +/// multiple times, the first occurrence prevails. +/// +/// ```aiken +/// let pairs = [Pair("a", 100), Pair("c", 300), Pair("b", 200)] +/// +/// let result = +/// dict.from_pairs(pairs) +/// |> dict.to_pairs() +/// +/// result == [Pair("a", 100), Pair("b", 200), Pair("c", 300)] +/// ``` +pub fn from_pairs(self: Pairs) -> Dict { + Dict { inner: do_from_pairs(self) } +} + +fn do_from_pairs(xs: Pairs) -> Pairs { + when xs is { + [] -> + [] + [Pair(k, v), ..rest] -> do_insert(do_from_pairs(rest), k, v) + } +} + +test from_list_1() { + from_pairs([]) == new() +} + +test from_list_2() { + from_pairs([Pair(foo, 42), Pair(bar, 14)]) == from_pairs( + [Pair(bar, 14), Pair(foo, 42)], + ) +} + +test from_list_3() { + from_pairs([Pair(foo, 42), Pair(bar, 14)]) == fixture_1() +} + +test from_list_4() { + from_pairs([Pair(foo, 42), Pair(bar, 14), Pair(foo, 1337)]) == fixture_1() +} + +test bench_from_pairs() { + let dict = + from_pairs( + [ + Pair("bbba", 8), + Pair("bbab", 12), + Pair("aabb", 13), + Pair("aaab", 9), + Pair("bbbb", 16), + Pair("aaaa", 1), + Pair("aaba", 5), + Pair("abab", 10), + Pair("baba", 7), + Pair("baab", 11), + Pair("abaa", 2), + Pair("baaa", 3), + Pair("bbaa", 4), + Pair("babb", 15), + Pair("abbb", 14), + Pair("abba", 6), + ], + ) + + size(dict) == 16 +} + +/// Like ['from_list'](from_list), but from an already sorted list by ascending +/// keys. This function fails (i.e. halt the program execution) if the list isn't +/// sorted. +/// +/// ```aiken +/// let pairs = [Pair("a", 100), Pair("b", 200), Pair("c", 300)] +/// +/// let result = +/// dict.from_ascending_pairs(pairs) +/// |> dict.to_pairs() +/// +/// result == [Pair("a", 100), Pair("b", 200), Pair("c", 300)] +/// ``` +/// +/// This is meant to be used to turn a list constructed off-chain into a `Dict` +/// which has taken care of maintaining interval invariants. This function still +/// performs a sanity check on all keys to avoid silly mistakes. It is, however, +/// considerably faster than ['from_list'](from_list) +pub fn from_ascending_pairs(xs: Pairs) -> Dict { + let Void = check_ascending_list(xs) + Dict { inner: xs } +} + +fn check_ascending_list(xs: Pairs) { + when xs is { + [] -> Void + [_] -> Void + [Pair(x0, _), Pair(x1, _) as e, ..rest] -> + if builtin.less_than_bytearray(x0, x1) { + check_ascending_list([e, ..rest]) + } else { + fail @"keys in associative list aren't in ascending order" + } + } +} + +/// Like [`from_ascending_pairs`](#from_ascending_list) but fails if **any** +/// value doesn't satisfy the predicate. +/// +/// ```aiken +/// let pairs = [Pair("a", 100), Pair("b", 200), Pair("c", 300)] +/// +/// dict.from_ascending_pairs_with(pairs, fn(x) { x <= 250 }) // fail +/// ``` +pub fn from_ascending_pairs_with( + xs: Pairs, + predicate: fn(value) -> Bool, +) -> Dict { + let Void = check_ascending_pairs_with(xs, predicate) + Dict { inner: xs } +} + +fn check_ascending_pairs_with( + xs: Pairs, + predicate: fn(value) -> Bool, +) { + when xs is { + [] -> Void + [Pair(_, v)] -> + if predicate(v) { + Void + } else { + fail @"value doesn't satisfy predicate" + } + [Pair(x0, v0), Pair(x1, _) as e, ..rest] -> + if builtin.less_than_bytearray(x0, x1) { + if predicate(v0) { + check_ascending_pairs_with([e, ..rest], predicate) + } else { + fail @"value doesn't satisfy predicate" + } + } else { + fail @"keys in pairs aren't in ascending order" + } + } +} + +test bench_from_ascending_pairs() { + let dict = + from_ascending_pairs( + [ + Pair("aaaa", 1), + Pair("aaab", 9), + Pair("aaba", 5), + Pair("aabb", 13), + Pair("abaa", 2), + Pair("abab", 10), + Pair("abba", 6), + Pair("abbb", 14), + Pair("baaa", 3), + Pair("baab", 11), + Pair("baba", 7), + Pair("babb", 15), + Pair("bbaa", 4), + Pair("bbab", 12), + Pair("bbba", 8), + Pair("bbbb", 16), + ], + ) + + size(dict) == 16 +} + +/// Get a value in the dict by its key. +/// +/// ```aiken +/// let result = +/// dict.new() +/// |> dict.insert(key: "a", value: "Aiken") +/// |> dict.get(key: "a") +/// +/// result == Some("Aiken") +/// ``` +pub fn get(self: Dict, key: ByteArray) -> Option { + do_get(self.inner, key) +} + +fn do_get(self: Pairs, key k: ByteArray) -> Option { + when self is { + [] -> None + [Pair(k2, v), ..rest] -> + if builtin.less_than_equals_bytearray(k, k2) { + if k == k2 { + Some(v) + } else { + None + } + } else { + do_get(rest, k) + } + } +} + +test get_1() { + get(new(), foo) == None +} + +test get_2() { + let m = + new() + |> insert(foo, "Aiken") + |> insert(bar, "awesome") + get(m, key: foo) == Some("Aiken") +} + +test get_3() { + let m = + new() + |> insert(foo, "Aiken") + |> insert(bar, "awesome") + get(m, key: baz) == None +} + +test get_4() { + let m = + new() + |> insert("aaa", "1") + |> insert("bbb", "2") + |> insert("ccc", "3") + |> insert("ddd", "4") + |> insert("eee", "5") + |> insert("fff", "6") + |> insert("ggg", "7") + |> insert("hhh", "8") + |> insert("iii", "9") + |> insert("jjj", "10") + + get(m, "bcd") == None +} + +test get_5() { + let m = + new() + |> insert("aaa", "1") + |> insert("bbb", "2") + |> insert("ccc", "3") + |> insert("ddd", "4") + |> insert("eee", "5") + |> insert("fff", "6") + |> insert("ggg", "7") + |> insert("hhh", "8") + |> insert("iii", "9") + |> insert("jjj", "10") + + get(m, "kkk") == None +} + +/// Check if a key exists in the dictionary. +/// +/// ```aiken +/// let result = +/// dict.new() +/// |> dict.insert(key: "a", value: "Aiken") +/// |> dict.has_key("a") +/// +/// result == True +/// ``` +pub fn has_key(self: Dict, key k: ByteArray) -> Bool { + do_has_key(self.inner, k) +} + +fn do_has_key(self: Pairs, key k: ByteArray) -> Bool { + when self is { + [] -> False + [Pair(k2, _), ..rest] -> + if builtin.less_than_equals_bytearray(k, k2) { + k == k2 + } else { + do_has_key(rest, k) + } + } +} + +test has_key_1() { + !has_key(new(), foo) +} + +test has_key_2() { + has_key( + new() + |> insert(foo, 14), + foo, + ) +} + +test has_key_3() { + !has_key( + new() + |> insert(foo, 14), + bar, + ) +} + +test has_key_4() { + has_key( + new() + |> insert(foo, 14) + |> insert(bar, 42), + bar, + ) +} + +/// Insert a value in the dictionary at a given key. If the key already exists, its value is **overridden**. If you need ways to combine keys together, use (`insert_with`)[#insert_with]. +/// +/// ```aiken +/// let result = +/// dict.new() +/// |> dict.insert(key: "a", value: 1) +/// |> dict.insert(key: "b", value: 2) +/// |> dict.insert(key: "a", value: 3) +/// |> dict.to_pairs() +/// +/// result == [Pair("a", 3), Pair("b", 2)] +/// ``` +pub fn insert( + self: Dict, + key k: ByteArray, + value v: value, +) -> Dict { + Dict { inner: do_insert(self.inner, k, v) } +} + +fn do_insert( + self: Pairs, + key k: ByteArray, + value v: value, +) -> Pairs { + when self is { + [] -> + [Pair(k, v)] + [Pair(k2, v2), ..rest] -> + if builtin.less_than_bytearray(k, k2) { + [Pair(k, v), ..self] + } else { + if k == k2 { + [Pair(k, v), ..rest] + } else { + [Pair(k2, v2), ..do_insert(rest, k, v)] + } + } + } +} + +test insert_1() { + let m1 = + new() + |> insert(foo, 42) + let m2 = + new() + |> insert(foo, 14) + insert(m1, foo, 14) == m2 +} + +test insert_2() { + let m1 = + new() + |> insert(foo, 42) + let m2 = + new() + |> insert(bar, 14) + insert(m1, bar, 14) == insert(m2, foo, 42) +} + +/// Insert a value in the dictionary at a given key. When the key already exist, the provided +/// merge function is called. The value existing in the dictionary is passed as the second argument +/// to the merge function, and the new value is passed as the third argument. +/// +/// ```aiken +/// let sum = +/// fn (_k, a, b) { Some(a + b) } +/// +/// let result = +/// dict.new() +/// |> dict.insert_with(key: "a", value: 1, with: sum) +/// |> dict.insert_with(key: "b", value: 2, with: sum) +/// |> dict.insert_with(key: "a", value: 3, with: sum) +/// |> dict.to_pairs() +/// +/// result == [Pair("a", 4), Pair("b", 2)] +/// ``` +pub fn insert_with( + self: Dict, + key k: ByteArray, + value v: value, + with: fn(ByteArray, value, value) -> Option, +) -> Dict { + Dict { + inner: do_insert_with(self.inner, k, v, fn(k, v1, v2) { with(k, v2, v1) }), + } +} + +test insert_with_1() { + let sum = + fn(_k, a, b) { Some(a + b) } + + let result = + new() + |> insert_with(key: "foo", value: 1, with: sum) + |> insert_with(key: "bar", value: 2, with: sum) + |> to_pairs() + + result == [Pair("bar", 2), Pair("foo", 1)] +} + +test insert_with_2() { + let sum = + fn(_k, a, b) { Some(a + b) } + + let result = + new() + |> insert_with(key: "foo", value: 1, with: sum) + |> insert_with(key: "bar", value: 2, with: sum) + |> insert_with(key: "foo", value: 3, with: sum) + |> to_pairs() + + result == [Pair("bar", 2), Pair("foo", 4)] +} + +test insert_with_3() { + let with = + fn(k, a, _b) { + if k == "foo" { + Some(a) + } else { + None + } + } + + let result = + new() + |> insert_with(key: "foo", value: 1, with: with) + |> insert_with(key: "bar", value: 2, with: with) + |> insert_with(key: "foo", value: 3, with: with) + |> insert_with(key: "bar", value: 4, with: with) + |> to_pairs() + + result == [Pair("foo", 1)] +} + +/// Efficiently checks whether a dictionary is empty. +/// ```aiken +/// dict.is_empty(dict.new()) == True +/// ``` +pub fn is_empty(self: Dict) -> Bool { + when self.inner is { + [] -> True + _ -> False + } +} + +test is_empty_1() { + is_empty(new()) +} + +/// Extract all the keys present in a given `Dict`. +/// +/// ```aiken +/// let result = +/// dict.new() +/// |> dict.insert("a", 14) +/// |> dict.insert("b", 42) +/// |> dict.insert("a", 1337) +/// |> dict.keys() +/// +/// result == ["a", "b"] +/// ``` +pub fn keys(self: Dict) -> List { + do_keys(self.inner) +} + +fn do_keys(self: Pairs) -> List { + when self is { + [] -> + [] + [Pair(k, _), ..rest] -> + [k, ..do_keys(rest)] + } +} + +test keys_1() { + keys(new()) == [] +} + +test keys_2() { + keys( + new() + |> insert(foo, 0) + |> insert(bar, 0), + ) == [bar, foo] +} + +/// Apply a function to all key-value pairs in a Dict. +/// +/// ```aiken +/// let result = +/// dict.new() +/// |> dict.insert("a", 100) +/// |> dict.insert("b", 200) +/// |> dict.insert("c", 300) +/// |> dict.map(fn(_k, v) { v * 2 }) +/// |> dict.to_pairs() +/// +/// result == [Pair("a", 200), Pair("b", 400), Pair("c", 600)] +/// ``` +pub fn map(self: Dict, with: fn(ByteArray, a) -> b) -> Dict { + Dict { inner: do_map(self.inner, with) } +} + +fn do_map( + self: Pairs, + with: fn(ByteArray, a) -> b, +) -> Pairs { + when self is { + [] -> + [] + [Pair(k, v), ..rest] -> + [Pair(k, with(k, v)), ..do_map(rest, with)] + } +} + +test map_1() { + let result = + fixture_1() + |> map(with: fn(k, _) { k }) + get(result, foo) == Some(foo) +} + +test map_2() { + let result = + fixture_1() + |> map(with: fn(_, v) { v + 1 }) + get(result, foo) == Some(43) && size(result) == size(fixture_1()) +} + +/// Get the inner list holding the dictionary data. +/// +/// ```aiken +/// let result = +/// dict.new() +/// |> dict.insert("a", 100) +/// |> dict.insert("b", 200) +/// |> dict.insert("c", 300) +/// |> dict.to_pairs() +/// +/// result == [Pair("a", 100), Pair("b", 200), Pair("c", 300)] +/// ``` +pub fn to_pairs(self: Dict) -> Pairs { + self.inner +} + +test to_list_1() { + to_pairs(new()) == [] +} + +test to_list_2() { + to_pairs(fixture_1()) == [Pair(bar, 14), Pair(foo, 42)] +} + +/// Return the number of key-value pairs in the dictionary. +/// +/// ```aiken +/// let result = +/// dict.new() +/// |> dict.insert("a", 100) +/// |> dict.insert("b", 200) +/// |> dict.insert("c", 300) +/// |> dict.size() +/// +/// result == 3 +/// ``` +pub fn size(self: Dict) -> Int { + do_size(self.inner) +} + +fn do_size(self: Pairs) -> Int { + when self is { + [] -> 0 + [_, ..rest] -> 1 + do_size(rest) + } +} + +test size_1() { + size(new()) == 0 +} + +test size_2() { + size( + new() + |> insert(foo, 14), + ) == 1 +} + +test size_3() { + size( + new() + |> insert(foo, 14) + |> insert(bar, 42), + ) == 2 +} + +/// Combine two dictionaries. If the same key exist in both the left and +/// right dictionary, values from the left are preferred (i.e. left-biaised). +/// +/// ```aiken +/// let left_dict = dict.from_pairs([Pair("a", 100), Pair("b", 200)]) +/// let right_dict = dict.from_pairs([Pair("a", 150), Pair("c", 300)]) +/// +/// let result = +/// dict.union(left_dict, right_dict) |> dict.to_pairs() +/// +/// result == [Pair("a", 100), Pair("b", 200), Pair("c", 300)] +/// ``` +pub fn union( + left: Dict, + right: Dict, +) -> Dict { + Dict { inner: do_union(left.inner, right.inner) } +} + +fn do_union( + left: Pairs, + right: Pairs, +) -> Pairs { + when left is { + [] -> right + [Pair(k, v), ..rest] -> do_union(rest, do_insert(right, k, v)) + } +} + +test union_1() { + union(fixture_1(), new()) == fixture_1() +} + +test union_2() { + union(new(), fixture_1()) == fixture_1() +} + +test union_3() { + let left = + new() + |> insert(foo, 14) + let right = + new() + |> insert(bar, 42) + |> insert(baz, 1337) + union(left, right) == from_pairs( + [Pair(foo, 14), Pair(baz, 1337), Pair(bar, 42)], + ) +} + +test union_4() { + let left = + new() + |> insert(foo, 14) + let right = + new() + |> insert(bar, 42) + |> insert(foo, 1337) + union(left, right) == from_pairs([Pair(foo, 14), Pair(bar, 42)]) +} + +/// Like [`union`](#union) but allows specifying the behavior to adopt when a key is present +/// in both dictionaries. The first value received correspond to the value in the left +/// dictionnary, whereas the second argument corresponds to the value in the right dictionnary. +/// +/// When passing `None`, the value is removed and not present in the union. +/// +/// ```aiken +/// let left_dict = dict.from_pairs([Pair("a", 100), Pair("b", 200)]) +/// let right_dict = dict.from_pairs([Pair("a", 150), Pair("c", 300)]) +/// +/// let result = +/// dict.union_with( +/// left_dict, +/// right_dict, +/// fn(_k, v1, v2) { Some(v1 + v2) }, +/// ) +/// |> dict.to_pairs() +/// +/// result == [Pair("a", 250), Pair("b", 200), Pair("c", 300)] +/// ``` +pub fn union_with( + left: Dict, + right: Dict, + with: fn(ByteArray, value, value) -> Option, +) -> Dict { + Dict { inner: do_union_with(left.inner, right.inner, with) } +} + +fn do_union_with( + left: Pairs, + right: Pairs, + with: fn(ByteArray, value, value) -> Option, +) -> Pairs { + when left is { + [] -> right + [Pair(k, v), ..rest] -> + do_union_with(rest, do_insert_with(right, k, v, with), with) + } +} + +fn do_insert_with( + self: Pairs, + key k: ByteArray, + value v: value, + with: fn(ByteArray, value, value) -> Option, +) -> Pairs { + when self is { + [] -> + [Pair(k, v)] + [Pair(k2, v2), ..rest] -> + if builtin.less_than_bytearray(k, k2) { + [Pair(k, v), ..self] + } else { + if k == k2 { + when with(k, v, v2) is { + Some(combined) -> + [Pair(k, combined), ..rest] + None -> rest + } + } else { + [Pair(k2, v2), ..do_insert_with(rest, k, v, with)] + } + } + } +} + +test union_with_1() { + let left = + new() + |> insert(foo, 14) + + let right = + new() + |> insert(bar, 42) + |> insert(foo, 1337) + + let result = union_with(left, right, with: fn(_, l, r) { Some(l + r) }) + + result == from_pairs([Pair(foo, 1351), Pair(bar, 42)]) +} + +/// Extract all the values present in a given `Dict`. +/// +/// ```aiken +/// let result = +/// dict.new() +/// |> dict.insert("a", 14) +/// |> dict.insert("b", 42) +/// |> dict.insert("c", 1337) +/// |> dict.values() +/// +/// result == [1337, 42] +/// ``` +pub fn values(self: Dict) -> List { + do_values(self.inner) +} + +fn do_values(self: Pairs) -> List { + when self is { + [] -> + [] + [Pair(_, v), ..rest] -> + [v, ..do_values(rest)] + } +} + +test values_1() { + values(new()) == [] +} + +test values_2() { + values( + new() + |> insert(foo, 3) + |> insert(bar, 4), + ) == [4, 3] +} diff --git a/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/hash.ak b/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/hash.ak new file mode 100644 index 00000000..4f860271 --- /dev/null +++ b/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/hash.ak @@ -0,0 +1,83 @@ +//// This module defines `Hash`, a self-documenting type-alias with a +//// phantom-type for readability. +//// +//// On-chain, any hash digest value is represented as a plain 'ByteArray'. +//// Though in practice, hashes come from different sources and have +//// different semantics. +//// +//// Hence, while this type-alias doesn't provide any strong type-guarantees, +//// it helps writing functions signatures with more meaningful types than mere +//// 'ByteArray'. +//// +//// Compare for example: +//// +//// ```aiken +//// pub type Credential { +//// VerificationKeyCredential(ByteArray) +//// ScriptCredential(ByteArray) +//// } +//// ``` +//// +//// with +//// +//// ```aiken +//// pub type Credential { +//// VerificationKeyCredential(Hash) +//// ScriptCredential(Hash) +//// } +//// ``` +//// +//// Both are strictly equivalent, but the second reads much better. + +use aiken/builtin + +/// A `Hash` is nothing more than a `ByteArray`, but it carries extra +/// information for readability. +pub type Hash = + ByteArray + +/// A blake2b-224 hash algorithm. +/// +/// Typically used for: +/// +/// - [`Credential`](../aiken/transaction/credential.html#Credential) +/// - [`PolicyId`](../aiken/transaction/value.html#PolicyId) +/// +/// Note: there's no function to calculate blake2b-224 hash digests on-chain. +pub opaque type Blake2b_224 { + Blake2b_224 +} + +/// A blake2b-256 hash algorithm. +/// +/// Typically used for: +/// +/// - [`TransactionId`](../aiken/transaction.html#TransactionId) +pub opaque type Blake2b_256 { + Blake2b_256 +} + +/// Compute the blake2b-256 hash digest of some data. +pub fn blake2b_256(bytes: ByteArray) -> Hash { + builtin.blake2b_256(bytes) +} + +/// A SHA2-256 hash algorithm. +pub opaque type Sha2_256 { + Sha2_256 +} + +/// Compute the sha2-256 hash digest of some data. +pub fn sha2_256(bytes: ByteArray) -> Hash { + builtin.sha2_256(bytes) +} + +/// A SHA3-256 hash algorithm. +pub opaque type Sha3_256 { + Sha3_256 +} + +/// Compute the sha3-256 hash digest of some data. +pub fn sha3_256(bytes: ByteArray) -> Hash { + builtin.sha3_256(bytes) +} diff --git a/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/int.ak b/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/int.ak new file mode 100644 index 00000000..fd602b62 --- /dev/null +++ b/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/int.ak @@ -0,0 +1,80 @@ +use aiken/bytearray +use aiken/math +use aiken/option + +/// Compare two integers. +/// +/// ```aiken +/// int.compare(14, 42) == Less +/// int.compare(14, 14) == Equal +/// int.compare(42, 14) == Greater +/// ``` +pub fn compare(left: Int, right: Int) -> Ordering { + if left < right { + Less + } else if left > right { + Greater + } else { + Equal + } +} + +/// Parse an integer from a utf-8 encoded 'ByteArray', when possible. +/// +/// ```aiken +/// int.from_utf8("14") == Some(14) +/// int.from_utf8("-42") == Some(-42) +/// int.from_utf8("007") == Some(7) +/// int.from_utf8("foo") == None +/// int.from_utf8("1.0") == None +/// int.from_utf8("1-2") == None +/// ``` +pub fn from_utf8(bytes: ByteArray) -> Option { + bytes + |> bytearray.foldr( + Some((0, 0)), + fn(byte, st) { + when st is { + None -> None + Some((n, e)) -> + if byte < 48 || byte > 57 { + if byte == 45 { + Some((-n, 0)) + } else { + None + } + } else if n < 0 { + None + } else { + let digit = byte - 48 + Some((n + digit * math.pow(10, e), e + 1)) + } + } + }, + ) + |> option.map(fn(tuple) { tuple.1st }) +} + +test from_utf8_1() { + from_utf8("0017") == Some(17) +} + +test from_utf8_2() { + from_utf8("42") == Some(42) +} + +test from_utf8_3() { + from_utf8("1337") == Some(1337) +} + +test from_utf8_4() { + from_utf8("-14") == Some(-14) +} + +test from_utf8_5() { + from_utf8("foo") == None +} + +test from_utf8_6() { + from_utf8("1-2") == None +} diff --git a/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/interval.ak b/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/interval.ak new file mode 100644 index 00000000..323d5a9a --- /dev/null +++ b/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/interval.ak @@ -0,0 +1,680 @@ +//// In a eUTxO-based blockchain like Cardano, the management of time can be +//// finicky. +//// +//// Indeed, in order to maintain a complete determinism in the execution of +//// scripts, it is impossible to introduce a notion of _"current time"_ since +//// the execution would then depend on factor that are external to the +//// transaction itself: the ineluctable stream of time flowing in our universe. +//// +//// Hence, to work around that, we typically define time intervals, which gives +//// window -- a.k.a intervals -- within which the transaction can be executed. +//// From within a script, it isn't possible to know when exactly the script is +//// executed, but we can reason about the interval bounds to validate pieces of +//// logic. + +/// A type to represent intervals of values. Interval are inhabited by a type +/// `a` which is useful for non-infinite intervals that have a finite +/// lower-bound and/or upper-bound. +/// +/// This allows to represent all kind of mathematical intervals: +/// +/// ```aiken +/// // [1; 10] +/// let i0: Interval = Interval +/// { lower_bound: +/// IntervalBound { bound_type: Finite(1), is_inclusive: True } +/// , upper_bound: +/// IntervalBound { bound_type: Finite(10), is_inclusive: True } +/// } +/// ``` +/// +/// ```aiken +/// // (20; infinity) +/// let i1: Interval = Interval +/// { lower_bound: +/// IntervalBound { bound_type: Finite(20), is_inclusive: False } +/// , upper_bound: +/// IntervalBound { bound_type: PositiveInfinity, is_inclusive: False } +/// } +/// ``` +pub type Interval
{ + lower_bound: IntervalBound, + upper_bound: IntervalBound, +} + +/// An interval bound, either inclusive or exclusive. +pub type IntervalBound { + bound_type: IntervalBoundType, + is_inclusive: Bool, +} + +/// Return the highest bound of the two. +/// +/// ```aiken +/// let ib1 = IntervalBound { bound_type: Finite(0), is_inclusive: False } +/// let ib2 = IntervalBound { bound_type: Finite(1), is_inclusive: False } +/// +/// interval.max(ib1, ib2) == ib2 +/// ``` +pub fn max( + left: IntervalBound, + right: IntervalBound, +) -> IntervalBound { + when compare_bound(left, right) is { + Less -> right + Equal -> left + Greater -> left + } +} + +/// Return the smallest bound of the two. +/// +/// ```aiken +/// let ib1 = IntervalBound { bound_type: Finite(0), is_inclusive: False } +/// let ib2 = IntervalBound { bound_type: Finite(1), is_inclusive: False } +/// +/// interval.min(ib1, ib2) == ib1 +/// ``` +pub fn min( + left: IntervalBound, + right: IntervalBound, +) -> IntervalBound { + when compare_bound(left, right) is { + Less -> left + Equal -> left + Greater -> right + } +} + +fn compare_bound( + left: IntervalBound, + right: IntervalBound, +) -> Ordering { + when compare_bound_type(left.bound_type, right.bound_type) is { + Less -> Less + Greater -> Greater + Equal -> + if left.is_inclusive == right.is_inclusive { + Equal + } else if left.is_inclusive { + Greater + } else { + Less + } + } +} + +/// A type of interval bound. Where finite, a value of type `a` must be +/// provided. `a` will typically be an `Int`, representing a number of seconds or +/// milliseconds. +pub type IntervalBoundType { + NegativeInfinity + Finite(a) + PositiveInfinity +} + +fn compare_bound_type( + left: IntervalBoundType, + right: IntervalBoundType, +) -> Ordering { + when left is { + NegativeInfinity -> + when right is { + NegativeInfinity -> Equal + _ -> Less + } + PositiveInfinity -> + when right is { + PositiveInfinity -> Equal + _ -> Greater + } + Finite(left) -> + when right is { + NegativeInfinity -> Greater + PositiveInfinity -> Less + Finite(right) -> + if left < right { + Less + } else if left == right { + Equal + } else { + Greater + } + } + } +} + +// TODO: Replace 'Int' with a generic 'a' once we have comparable traits. + +/// Checks whether an element is contained within the interval. +/// +/// ```aiken +/// let iv = +/// Interval { +/// lower_bound: IntervalBound { +/// bound_type: Finite(14), +/// is_inclusive: True +/// }, +/// upper_bound: IntervalBound { +/// bound_type: Finite(42), +/// is_inclusive: False +/// }, +/// } +/// +/// interval.contains(iv, 25) == True +/// interval.contains(iv, 0) == False +/// interval.contains(iv, 14) == True +/// interval.contains(iv, 42) == False +/// ``` +pub fn contains(self: Interval, elem: Int) -> Bool { + let is_greater_than_lower_bound = + when self.lower_bound.bound_type is { + NegativeInfinity -> True + Finite(lower_bound) -> + if self.lower_bound.is_inclusive { + elem >= lower_bound + } else { + elem > lower_bound + } + PositiveInfinity -> False + } + + let is_smaller_than_upper_bound = + when self.upper_bound.bound_type is { + NegativeInfinity -> False + Finite(upper_bound) -> + if self.upper_bound.is_inclusive { + elem <= upper_bound + } else { + elem < upper_bound + } + PositiveInfinity -> True + } + + is_greater_than_lower_bound && is_smaller_than_upper_bound +} + +test contains_1() { + let iv = everything() + contains(iv, 14) +} + +test contains_2() { + let iv = entirely_before(15) + contains(iv, 14) +} + +test contains_3() { + let iv = before(14) + contains(iv, 14) +} + +test contains_4() { + let iv = entirely_before(14) + !contains(iv, 14) +} + +test contains_5() { + let iv = entirely_after(13) + contains(iv, 14) +} + +test contains_6() { + let iv = after(14) + contains(iv, 14) +} + +test contains_7() { + let iv = entirely_after(14) + !contains(iv, 14) +} + +test contains_8() { + let iv = between(42, 1337) + !contains(iv, 14) +} + +test contains_9() { + let iv = between(0, 42) + contains(iv, 14) +} + +test contains_10() { + let iv = between(0, 42) + contains(iv, 42) +} + +test contains_11() { + let iv = entirely_between(0, 42) + !contains(iv, 0) +} + +test contains_12() { + let iv = empty() + !contains(iv, 14) +} + +/// Create an interval that contains every possible values. i.e. (-INF, +INF) +/// +/// ```aiken +/// interval.contains(everything(), 0) == True +/// interval.contains(everything(), 1000) == True +/// ``` +pub fn everything() -> Interval { + Interval { + lower_bound: IntervalBound { + bound_type: NegativeInfinity, + is_inclusive: True, + }, + upper_bound: IntervalBound { + bound_type: PositiveInfinity, + is_inclusive: True, + }, + } +} + +/// Create an empty interval that contains no value. +/// +/// ```aiken +/// interval.contains(empty(), 0) == False +/// interval.contains(empty(), 1000) == False +/// ``` +pub fn empty() -> Interval { + Interval { + lower_bound: IntervalBound { + bound_type: PositiveInfinity, + is_inclusive: True, + }, + upper_bound: IntervalBound { + bound_type: NegativeInfinity, + is_inclusive: True, + }, + } +} + +/// Create an interval that includes all values between two bounds, including the bounds. i.e. [lower_bound, upper_bound] +/// +/// ```aiken +/// interval.between(10, 100) == Interval { +/// lower_bound: IntervalBound { bound_type: Finite(10), is_inclusive: True }, +/// upper_bound: IntervalBound { bound_type: Finite(100), is_inclusive: True }, +/// } +/// ``` +pub fn between(lower_bound: a, upper_bound: a) -> Interval { + Interval { + lower_bound: IntervalBound { + bound_type: Finite(lower_bound), + is_inclusive: True, + }, + upper_bound: IntervalBound { + bound_type: Finite(upper_bound), + is_inclusive: True, + }, + } +} + +/// Create an interval that includes all values between two bounds, excluding the bounds. i.e. (lower_bound, upper_bound) +/// +/// ```aiken +/// interval.entirely_between(10, 100) == Interval { +/// lower_bound: IntervalBound { bound_type: Finite(10), is_inclusive: False }, +/// upper_bound: IntervalBound { bound_type: Finite(100), is_inclusive: False }, +/// } +/// ``` +pub fn entirely_between(lower_bound: a, upper_bound: a) -> Interval { + Interval { + lower_bound: IntervalBound { + bound_type: Finite(lower_bound), + is_inclusive: False, + }, + upper_bound: IntervalBound { + bound_type: Finite(upper_bound), + is_inclusive: False, + }, + } +} + +/// Create an interval that includes all values greater than the given bound. i.e [lower_bound, +INF) +/// +/// ```aiken +/// interval.after(10) == Interval { +/// lower_bound: IntervalBound { bound_type: Finite(10), is_inclusive: True }, +/// upper_bound: IntervalBound { bound_type: PositiveInfinity, is_inclusive: True }, +/// } +/// ``` +pub fn after(lower_bound: a) -> Interval { + Interval { + lower_bound: IntervalBound { + bound_type: Finite(lower_bound), + is_inclusive: True, + }, + upper_bound: IntervalBound { + bound_type: PositiveInfinity, + is_inclusive: True, + }, + } +} + +// TODO: Replace 'Int' with a generic 'a' once we have comparable traits. + +/// Check whether the interval is entirely after the point "a" +/// +/// ```aiken +/// interval.is_entirely_after(interval.after(10), 5) == True +/// interval.is_entirely_after(interval.after(10), 10) == False +/// interval.is_entirely_after(interval.after(10), 15) == False +/// interval.is_entirely_after(interval.between(10, 20), 30) == False +/// interval.is_entirely_after(interval.between(10, 20), 5) == True +pub fn is_entirely_after(self: Interval, point: Int) -> Bool { + when self.lower_bound.bound_type is { + Finite(low) -> + if self.lower_bound.is_inclusive { + point < low + } else { + point <= low + } + _ -> False + } +} + +test is_entirely_after_1() { + is_entirely_after(after(10), 5) +} + +test is_entirely_after_2() { + !is_entirely_after(after(10), 10) +} + +test is_entirely_after_3() { + !is_entirely_after(after(10), 15) +} + +test is_entirely_after_4() { + !is_entirely_after(between(10, 20), 30) +} + +test is_entirely_after_5() { + is_entirely_after(between(10, 20), 5) +} + +test is_entirely_after_6() { + is_entirely_after(entirely_after(10), 10) +} + +test is_entirely_after_7() { + !is_entirely_after(before(10), 5) +} + +test is_entirely_after_8() { + !is_entirely_after(before(10), 15) +} + +test is_entirely_after_9() { + !is_entirely_after(entirely_before(10), 5) +} + +// TODO: Replace 'Int' with a generic 'a' once we have comparable traits. + +/// Check whether the interval is entirely before the point "a" +/// +/// ```aiken +/// interval.is_entirely_before(interval.before(10), 15) == True +/// interval.is_entirely_before(interval.before(10), 10) == False +/// interval.is_entirely_before(interval.before(10), 5) == False +/// interval.is_entirely_before(interval.between(10, 20), 30) == True +/// interval.is_entirely_before(interval.between(10, 20), 5) == False +pub fn is_entirely_before(self: Interval, point: Int) -> Bool { + when self.upper_bound.bound_type is { + Finite(hi) -> + if self.upper_bound.is_inclusive { + hi < point + } else { + hi <= point + } + _ -> False + } +} + +test is_entirely_before_1() { + is_entirely_before(before(10), 15) +} + +test is_entirely_before_2() { + !is_entirely_before(before(10), 10) +} + +test is_entirely_before_3() { + !is_entirely_before(before(10), 5) +} + +test is_entirely_before_4() { + is_entirely_before(between(10, 20), 30) +} + +test is_entirely_before_5() { + !is_entirely_before(between(10, 20), 5) +} + +test is_entirely_before_6() { + is_entirely_before(entirely_before(10), 10) +} + +test is_entirely_before_7() { + !is_entirely_before(after(10), 15) +} + +test is_entirely_before_8() { + !is_entirely_before(after(10), 5) +} + +test is_entirely_before_9() { + !is_entirely_before(entirely_after(10), 5) +} + +/// Create an interval that includes all values after (and not including) the given bound. i.e (lower_bound, +INF) +/// +/// ```aiken +/// interval.entirely_after(10) == Interval { +/// lower_bound: IntervalBound { bound_type: Finite(10), is_inclusive: False }, +/// upper_bound: IntervalBound { bound_type: PositiveInfinity, is_inclusive: True }, +/// } +/// ``` +pub fn entirely_after(lower_bound: a) -> Interval { + Interval { + lower_bound: IntervalBound { + bound_type: Finite(lower_bound), + is_inclusive: False, + }, + upper_bound: IntervalBound { + bound_type: PositiveInfinity, + is_inclusive: True, + }, + } +} + +/// Create an interval that includes all values before (and including) the given bound. i.e (-INF, upper_bound] +/// +/// ```aiken +/// interval.before(100) == Interval { +/// lower_bound: IntervalBound { bound_type: NegativeInfinity, is_inclusive: True }, +/// upper_bound: IntervalBound { bound_type: Finite(100), is_inclusive: True }, +/// } +/// ``` +pub fn before(upper_bound: a) -> Interval { + Interval { + lower_bound: IntervalBound { + bound_type: NegativeInfinity, + is_inclusive: True, + }, + upper_bound: IntervalBound { + bound_type: Finite(upper_bound), + is_inclusive: True, + }, + } +} + +/// Create an interval that includes all values before (and not including) the given bound. i.e (-INF, upper_bound) +/// +/// ```aiken +/// interval.entirely_before(10) == Interval { +/// lower_bound: IntervalBound { bound_type: NegativeInfinity, is_inclusive: True }, +/// upper_bound: IntervalBound { bound_type: Finite(10), is_inclusive: False }, +/// } +/// ``` +pub fn entirely_before(upper_bound: a) -> Interval { + Interval { + lower_bound: IntervalBound { + bound_type: NegativeInfinity, + is_inclusive: True, + }, + upper_bound: IntervalBound { + bound_type: Finite(upper_bound), + is_inclusive: False, + }, + } +} + +/// Tells whether an interval is empty; i.e. that is contains no value. +/// +/// ```aiken +/// let iv1 = interval.empty() +/// +/// let iv2 = Interval { +/// lower_bound: IntervalBound { bound_type: Finite(0), is_inclusive: False }, +/// upper_bound: IntervalBound { bound_type: Finite(0), is_inclusive: False }, +/// } +/// +/// let iv3 = Interval { +/// lower_bound: IntervalBound { bound_type: Finite(0), is_inclusive: False }, +/// upper_bound: IntervalBound { bound_type: Finite(100), is_inclusive: False }, +/// } +/// +/// interval.is_empty(iv1) == True +/// interval.is_empty(iv2) == True +/// interval.is_empty(iv3) == False +/// +/// // Note: Two empty intervals are not necessarily equal. +/// iv1 != iv2 +/// ``` +pub fn is_empty(self: Interval) -> Bool { + let ordering = + compare_bound_type(self.lower_bound.bound_type, self.upper_bound.bound_type) + + when ordering is { + Greater -> True + Equal -> !(self.lower_bound.is_inclusive && self.upper_bound.is_inclusive) + Less -> { + let is_open_interval = + !self.lower_bound.is_inclusive && !self.upper_bound.is_inclusive + if is_open_interval { + when (self.lower_bound.bound_type, self.upper_bound.bound_type) is { + (Finite(lower_bound), Finite(upper_bound)) -> + lower_bound + 1 == upper_bound + _ -> False + } + } else { + False + } + } + } +} + +/// Computes the largest interval contains in the two given intervals, if any. +/// +/// ```aiken +/// let iv1 = interval.between(0, 10) +/// let iv2 = interval.between(2, 14) +/// interval.intersection(iv1, iv2) == interval.between(2, 10) +/// +/// let iv1 = interval.entirely_before(10) +/// let iv2 = interval.entirely_after(0) +/// interval.intersection(iv1, iv2) == interval.entirely_between(0, 10) +/// +/// let iv1 = interval.between(0, 1) +/// let iv2 = interval.between(2, 3) +/// interval.intersection(iv1, iv2) |> interval.is_empty +/// ``` +pub fn intersection(iv1: Interval, iv2: Interval) -> Interval { + Interval { + lower_bound: max(iv1.lower_bound, iv2.lower_bound), + upper_bound: min(iv1.upper_bound, iv2.upper_bound), + } +} + +test intersection_1() { + let iv1 = between(0, 10) + let iv2 = between(2, 14) + intersection(iv1, iv2) == between(2, 10) +} + +test intersection_2() { + let iv1 = between(0, 1) + let iv2 = between(1, 2) + intersection(iv1, iv2) == between(1, 1) +} + +test intersection_3() { + let iv1 = between(0, 1) + let iv2 = entirely_between(1, 2) + intersection(iv1, iv2) + |> is_empty +} + +test intersection_4() { + let iv1 = entirely_between(0, 1) + let iv2 = entirely_between(1, 2) + intersection(iv1, iv2) + |> is_empty +} + +test intersection_5() { + let iv1 = between(0, 10) + let iv2 = before(4) + intersection(iv1, iv2) == between(0, 4) +} + +test intersection_6() { + let iv1 = entirely_before(10) + let iv2 = entirely_after(0) + intersection(iv1, iv2) == entirely_between(0, 10) +} + +/// Computes the smallest interval containing the two given intervals, if any +/// +/// ```aiken +/// let iv1 = between(0, 10) +/// let iv2 = between(2, 14) +/// hull(iv1, iv2) == between(0, 14) +/// +/// let iv1 = between(5, 10) +/// let iv2 = before(0) +/// hull(iv1, iv2) == before(10) +/// +/// let iv1 = entirely_after(0) +/// let iv2 = between(10, 42) +/// hull(iv1, iv2) = entirely_after(0) +/// ``` +pub fn hull(iv1: Interval, iv2: Interval) -> Interval { + Interval { + lower_bound: min(iv1.lower_bound, iv2.lower_bound), + upper_bound: max(iv1.upper_bound, iv2.upper_bound), + } +} + +test hull_1() { + let iv1 = between(0, 10) + let iv2 = between(2, 14) + hull(iv1, iv2) == between(0, 14) +} + +test hull_2() { + let iv1 = between(5, 10) + let iv2 = before(0) + hull(iv1, iv2) == before(10) +} + +test hull_3() { + let iv1 = entirely_after(0) + let iv2 = between(10, 42) + hull(iv1, iv2) == entirely_after(0) +} diff --git a/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/list.ak b/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/list.ak new file mode 100644 index 00000000..b8bb4b8c --- /dev/null +++ b/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/list.ak @@ -0,0 +1,1337 @@ +use aiken/builtin +use aiken/bytearray +use aiken/int + +/// Determine if all elements of the list satisfy the given predicate. +/// +/// Note: an empty list always satisfies the predicate. +/// +/// ```aiken +/// list.all([], fn(n) { n > 0 }) == True +/// list.all([1, 2, 3], fn(n) { n > 0 }) == True +/// list.all([1, 2, 3], fn(n) { n == 2 }) == False +/// ``` +pub fn all(self: List, predicate: fn(a) -> Bool) -> Bool { + when self is { + [] -> True + [x, ..xs] -> predicate(x) && all(xs, predicate) + } +} + +test all_1() { + all([1, 2, 3], fn(n) { n > 0 }) == True +} + +test all_2() { + all([1, 2, 3], fn(n) { n > 42 }) == False +} + +test all_3() { + all([], fn(n) { n == 42 }) == True +} + +/// Determine if at least one element of the list satisfies the given predicate. +/// +/// Note: an empty list never satisfies the predicate. +/// +/// ```aiken +/// list.any([], fn(n) { n > 2 }) == False +/// list.any([1, 2, 3], fn(n) { n > 0 }) == True +/// list.any([1, 2, 3], fn(n) { n == 2 }) == True +/// list.any([1, 2, 3], fn(n) { n < 0 }) == False +/// ``` +pub fn any(self: List, predicate: fn(a) -> Bool) -> Bool { + when self is { + [] -> False + [x, ..xs] -> predicate(x) || any(xs, predicate) + } +} + +test any_1() { + any([1, 2, 3], fn(n) { n > 0 }) == True +} + +test any_2() { + any([1, 2, 3], fn(n) { n > 42 }) == False +} + +test any_3() { + any([], fn(n) { n == 42 }) == False +} + +/// Count how many items in the list satisfy the given predicate. +/// +/// ```aiken +/// list.count([], fn(a) { a > 2}) == 0 +/// list.count([1, 2, 3], fn(a) { n > 0 }) == 3 +/// list.count([1, 2, 3], fn(a) { n >= 2 }) == 2 +/// list.count([1, 2, 3], fn(a) { n > 5 }) == 0 +/// ``` +pub fn count(self: List, predicate: fn(a) -> Bool) -> Int { + foldr( + self, + 0, + fn(item, total) { + if predicate(item) { + total + 1 + } else { + total + } + }, + ) +} + +test count_empty() { + count([], fn(a) { a > 2 }) == 0 +} + +test count_all() { + count([1, 2, 3], fn(a) { a > 0 }) == 3 +} + +test count_some() { + count([1, 2, 3], fn(a) { a >= 2 }) == 2 +} + +test count_none() { + count([1, 2, 3], fn(a) { a > 5 }) == 0 +} + +/// Return Some(item) at the index or None if the index is out of range. The index is 0-based. +/// +/// ```aiken +/// list.at([1, 2, 3], 1) == Some(2) +/// list.at([1, 2, 3], 42) == None +/// ``` +pub fn at(self: List, index: Int) -> Option { + when self is { + [] -> None + [x, ..xs] -> + if index == 0 { + Some(x) + } else { + at(xs, index - 1) + } + } +} + +test at_1() { + at([1, 2, 3], -1) == None +} + +test at_2() { + at([], 0) == None +} + +test at_3() { + at([1, 2, 3], 3) == None +} + +test at_4() { + at([1], 0) == Some(1) +} + +test at_5() { + at([1, 2, 3], 2) == Some(3) +} + +/// Merge two lists together. +/// +/// ```aiken +/// list.concat([], []) == [] +/// list.concat([], [1, 2, 3]) == [1, 2, 3] +/// list.concat([1, 2, 3], [4, 5, 6]) == [1, 2, 3, 4, 5, 6] +/// ``` +pub fn concat(left: List, right: List) -> List { + when left is { + [] -> right + [x, ..xs] -> + [x, ..concat(xs, right)] + } +} + +test concat_1() { + concat([1, 2, 3], [4, 5, 6]) == [1, 2, 3, 4, 5, 6] +} + +test concat_2() { + concat([1, 2, 3], []) == [1, 2, 3] +} + +test concat_3() { + concat([], [1, 2, 3]) == [1, 2, 3] +} + +/// Remove the first occurrence of the given element from the list. +/// +/// ```aiken +/// list.delete([1, 2, 3, 1], 1) == [2, 3, 1] +/// list.delete([1, 2, 3], 14) == [1, 2, 3] +/// ``` +pub fn delete(self: List, elem: a) -> List { + when self is { + [] -> + [] + [x, ..xs] -> + if x == elem { + xs + } else { + [x, ..delete(xs, elem)] + } + } +} + +test delete_1() { + delete([], 42) == [] +} + +test delete_2() { + delete([1, 2, 3, 1], 1) == [2, 3, 1] +} + +test delete_3() { + delete([1, 2, 3], 14) == [1, 2, 3] +} + +test delete_4() { + delete([2], 2) == [] +} + +/// Remove the first occurrence of each element of the second list from the first one. +/// +/// ``` +/// list.difference(["h", "e", "l", "l", "o"], ["l", "e", "l"]) == ["h", "o"] +/// list.difference([1, 2, 3, 4, 5], [1, 1, 2]) == [3, 4, 5] +/// list.difference([1, 2, 3], []) == [1, 2, 3] +/// ``` +pub fn difference(self: List, with: List) -> List { + when with is { + [] -> self + [x, ..xs] -> difference(delete(self, x), xs) + } +} + +test difference_1() { + difference(["h", "e", "l", "l", "o"], ["l", "e", "l"]) == ["h", "o"] +} + +test difference_2() { + difference([1, 2, 3, 4, 5], [1, 1, 2]) == [3, 4, 5] +} + +test difference_3() { + difference([1, 2, 3], []) == [1, 2, 3] +} + +test difference_4() { + difference([], [1, 2, 3]) == [] +} + +/// Drop the first `n` elements of a list. +/// +/// ```aiken +/// list.drop([1, 2, 3], 2) == [3] +/// list.drop([], 42) == [] +/// list.drop([1, 2, 3], 42) == [] +/// ``` +pub fn drop(self: List, n: Int) -> List { + if n <= 0 { + self + } else { + when self is { + [] -> + [] + [_x, ..xs] -> drop(xs, n - 1) + } + } +} + +test drop_1() { + drop([], 42) == [] +} + +test drop_2() { + drop([1, 2, 3], 2) == [3] +} + +/// Returns the suffix of the given list after removing all elements that satisfy the predicate. +/// +/// ```aiken +/// list.drop_while([1, 2, 3], fn(x) { x < 2 }) == [2, 3] +/// list.drop_while([], fn(x) { x > 2 }) == [] +/// list.drop_while([1, 2, 3], fn(x) { x == 3 }) == [1, 2, 3] +/// ``` +pub fn drop_while(self: List, predicate: fn(a) -> Bool) -> List { + when self is { + [] -> + [] + [x, ..xs] -> + if predicate(x) { + drop_while(xs, predicate) + } else { + self + } + } +} + +test drop_while_1() { + drop_while([], fn(x) { x > 2 }) == [] +} + +test drop_while_2() { + let xs = + [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] + drop_while(xs, fn(x) { x > 5 }) == [5, 4, 3, 2, 1] +} + +test drop_while_3() { + let xs = + [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] + drop_while(xs, fn(x) { x == 42 }) == xs +} + +test drop_while_4() { + let xs = + [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] + drop_while(xs, fn(x) { x < 42 }) == [] +} + +/// Produce a list of elements that satisfy a predicate. +/// +/// ```aiken +/// list.filter([1, 2, 3], fn(x) { x >= 2 }) == [2, 3] +/// list.filter([], fn(x) { x > 2 }) == [] +/// list.filter([1, 2, 3], fn(x) { x == 3 }) == [3] +/// ``` +pub fn filter(self: List, predicate: fn(a) -> Bool) -> List { + when self is { + [] -> + [] + [x, ..xs] -> + if predicate(x) { + [x, ..filter(xs, predicate)] + } else { + filter(xs, predicate) + } + } +} + +test filter_1() { + filter([], fn(x) { x > 0 }) == [] +} + +test filter_2() { + let xs = + [1, 2, 3, 4, 5, 6] + filter(xs, fn(x) { builtin.mod_integer(x, 2) == 0 }) == [2, 4, 6] +} + +test filter_3() { + let filter_foldr = + fn(xs, f) { + foldr( + xs, + [], + fn(x, ys) { + if f(x) { + [x, ..ys] + } else { + ys + } + }, + ) + } + + let is_odd = + fn(n) { builtin.mod_integer(n, 2) != 0 } + + filter_foldr([1, 2, 3], is_odd) == filter([1, 2, 3], is_odd) +} + +/// Produce a list of transformed elements that satisfy a predicate. +/// +/// ```aiken +/// let transform = fn(x) { if x % 2 == 0 { None } else { Some(3*x) } } +/// list.filter_map([1, 2, 3], transform) == [3, 9] +/// ``` +pub fn filter_map(self: List, predicate: fn(a) -> Option) -> List { + when self is { + [] -> + [] + [x, ..xs] -> + when predicate(x) is { + None -> filter_map(xs, predicate) + Some(y) -> + [y, ..filter_map(xs, predicate)] + } + } +} + +test filter_map_1() { + filter_map([], fn(_) { Some(42) }) == [] +} + +test filter_map_2() { + filter_map( + [1, 2, 3, 4, 5, 6], + fn(x) { + if builtin.mod_integer(x, 2) != 0 { + Some(3 * x) + } else { + None + } + }, + ) == [3, 9, 15] +} + +/// Find the first element satisfying the given predicate, if any. +/// +/// ```aiken +/// list.find([1, 2, 3], fn(x) { x == 2 }) == Some(2) +/// list.find([4, 5, 6], fn(x) { x == 2 }) == None +/// ``` +pub fn find(self: List, predicate: fn(a) -> Bool) -> Option { + when self is { + [] -> None + [x, ..xs] -> + if predicate(x) { + Some(x) + } else { + find(xs, predicate) + } + } +} + +test find_1() { + find([1, 2, 3], fn(x) { x == 1 }) == Some(1) +} + +test find_2() { + find([1, 2, 3], fn(x) { x > 42 }) == None +} + +test find_3() { + find([], fn(_) { True }) == None +} + +/// Map elements of a list into a new list and flatten the result. +/// +/// ```aiken +/// list.flat_map([1, 2, 3], fn(a) { [a, 2*a] }) == [1, 2, 2, 4, 3, 6] +/// ``` +pub fn flat_map(self: List, with: fn(a) -> List) -> List { + foldr(self, [], fn(x, xs) { concat(with(x), xs) }) +} + +test flat_map_1() { + flat_map([], fn(a) { [a] }) == [] +} + +test flat_map_2() { + flat_map([1, 2, 3], fn(a) { [a, a] }) == [1, 1, 2, 2, 3, 3] +} + +/// Reduce a list from left to right. +/// +/// ```aiken +/// list.foldl([1, 2, 3], 0, fn(n, total) { n + total }) == 6 +/// list.foldl([1, 2, 3], [], fn(x, xs) { [x, ..xs] }) == [3, 2, 1] +/// ``` +pub fn foldl(self: List, zero: b, with: fn(a, b) -> b) -> b { + when self is { + [] -> zero + [x, ..xs] -> foldl(xs, with(x, zero), with) + } +} + +test foldl_1() { + foldl([], 0, fn(_, _) { 1 }) == 0 +} + +test foldl_2() { + foldl([1, 2, 3, 4, 5], 0, fn(n, total) { n + total }) == 15 +} + +test foldl_3() { + foldl([1, 2, 3, 4], [], fn(x, xs) { [x, ..xs] }) == [4, 3, 2, 1] +} + +/// Reduce a list from right to left. +/// +/// ```aiken +/// list.foldr([1, 2, 3], 0, fn(n, total) { n + total }) == 6 +/// list.foldr([1, 2, 3], [], fn(x, xs) { [x, ..xs] }) == [1, 2, 3] +/// ``` +pub fn foldr(self: List, zero: b, with: fn(a, b) -> b) -> b { + when self is { + [] -> zero + [x, ..xs] -> with(x, foldr(xs, zero, with)) + } +} + +test foldr_1() { + foldr([1, 2, 3, 4, 5], 0, fn(n, total) { n + total }) == 15 +} + +test foldr_2() { + foldr( + [1, 2, 3], + "", + fn(n, _str) { + if builtin.mod_integer(n, 2) == 0 { + "foo" + } else { + "bar" + } + }, + ) == "bar" +} + +test foldr_3() { + foldr([1, 2, 3, 4], [], fn(x, xs) { [x, ..xs] }) == [1, 2, 3, 4] +} + +/// Return all elements except the last one. +/// +/// ```aiken +/// list.init([]) == None +/// list.init([1, 2, 3]) == Some([1, 2]) +/// ``` +pub fn init(self: List) -> Option> { + when self is { + [] -> None + _ -> Some(do_init(self)) + } +} + +fn do_init(self: List) -> List { + when self is { + [] -> fail @"unreachable" + [_] -> + [] + [x, ..xs] -> + [x, ..do_init(xs)] + } +} + +test init_1() { + init([]) == None +} + +test init_2() { + init([1]) == Some([]) +} + +test init_3() { + init([1, 2, 3, 4]) == Some([1, 2, 3]) +} + +/// Figures out whether a list contain the given element. +/// +/// ```aiken +/// list.has([1, 2, 3], 2) == True +/// list.has([1, 2, 3], 14) == False +/// list.has([], 14) == False +/// ``` +pub fn has(self: List, elem: a) -> Bool { + when self is { + [] -> False + [x, ..xs] -> + if x == elem { + True + } else { + has(xs, elem) + } + } +} + +test has_1() { + has([1, 2, 3], 1) == True +} + +test has_2() { + has([1, 2, 3], 14) == False +} + +test has_3() { + has([], 14) == False +} + +/// Gets the index of an element of a list, if any. Otherwise, returns None. +/// +/// ```aiken +/// list.index_of([1, 5, 2], 2) == Some(2) +/// list.index_of([1, 7, 3], 4) == None +/// list.index_of([1, 0, 9, 6], 6) == 3 +/// list.index_of([], 6) == None +/// ``` +pub fn index_of(self: List, elem: a) -> Option { + do_index_of(self, elem, 0) +} + +fn do_index_of(self: List, elem: a, i: Int) -> Option { + when self is { + [] -> None + [x, ..xs] -> + if x == elem { + Some(i) + } else { + do_index_of(xs, elem, i + 1) + } + } +} + +test index_of_1() { + index_of([1, 5, 2], 2) == Some(2) +} + +test index_of_2() { + index_of([1, 7, 3], 4) == None +} + +test index_of_3() { + index_of([1, 0, 9, 6], 6) == Some(3) +} + +test index_of_4() { + index_of([], 6) == None +} + +/// Get the first element of a list +/// +/// ```aiken +/// list.head([1, 2, 3]) == Some(1) +/// list.head([]) == None +/// ``` +pub fn head(self: List) -> Option { + when self is { + [] -> None + _ -> Some(builtin.head_list(self)) + } +} + +test head_1() { + head([1, 2, 3]) == Some(1) +} + +test head_2() { + head([]) == None +} + +/// Like [`foldr`](#foldr), but also provides the position (0-based) of the elements when iterating. +/// +/// ```aiken +/// let group = fn(i, x, xs) { [(i, x), ..xs] } +/// list.indexed_foldr(["a", "b", "c"], [], group) == [ +/// (0, "a"), +/// (1, "b"), +/// (2, "c") +/// ] +/// ``` +pub fn indexed_foldr( + self: List, + zero: result, + with: fn(Int, a, result) -> result, +) -> result { + do_indexed_foldr(0, self, zero, with) +} + +fn do_indexed_foldr( + n: Int, + self: List, + zero: result, + with: fn(Int, a, result) -> result, +) -> result { + when self is { + [] -> zero + [x, ..xs] -> with(n, x, do_indexed_foldr(n + 1, xs, zero, with)) + } +} + +test indexed_foldr_1() { + indexed_foldr([], 0, fn(i, x, xs) { i + x + xs }) == 0 +} + +test indexed_foldr_2() { + let letters = + ["a", "b", "c"] + indexed_foldr(letters, [], fn(i, x, xs) { [(i, x), ..xs] }) == [ + (0, "a"), + (1, "b"), + (2, "c"), + ] +} + +/// List [`map`](#map) but provides the position (0-based) of the elements while iterating. +/// +/// ```aiken +/// list.indexed_map([1, 2, 3], fn(i, x) { i + x }) == [1, 3, 5] +/// ``` +pub fn indexed_map(self: List, with: fn(Int, a) -> result) -> List { + do_indexed_map(0, self, with) +} + +fn do_indexed_map( + n: Int, + self: List, + with: fn(Int, a) -> result, +) -> List { + when self is { + [] -> + [] + [x, ..xs] -> + [with(n, x), ..do_indexed_map(n + 1, xs, with)] + } +} + +test indexed_map_1() { + indexed_map([], fn(i, _n) { i }) == [] +} + +test indexed_map_2() { + indexed_map( + [4, 8, 13, 2], + fn(i, n) { + if n == 8 { + n + } else { + i + } + }, + ) == [0, 8, 2, 3] +} + +/// Checks whether a list is empty. +/// +/// ```aiken +/// list.is_empty([]) == True +/// list.is_empty([1, 2, 3]) == False +/// ``` +pub fn is_empty(self: List) -> Bool { + when self is { + [] -> True + _ -> False + } +} + +test is_empty_1() { + is_empty([]) == True +} + +test is_empty_2() { + is_empty([1, 2, 3]) == False +} + +/// Get the last in the given list, if any. +/// +/// ```aiken +/// list.last([]) == None +/// list.last([1, 2, 3]) == Some(3) +/// ``` +pub fn last(self: List) -> Option { + when self is { + [] -> None + [x] -> Some(x) + [_, ..xs] -> last(xs) + } +} + +test last_1() { + last([]) == None +} + +test last_2() { + last([1]) == Some(1) +} + +test last_3() { + last([1, 2, 3, 4]) == Some(4) +} + +/// Get the number of elements in the given list. +/// +/// ```aiken +/// list.length([]) == 0 +/// list.length([1, 2, 3]) == 3 +/// ``` +pub fn length(self: List) -> Int { + when self is { + [] -> 0 + [_, ..xs] -> 1 + length(xs) + } +} + +test length_1() { + length([]) == 0 +} + +test length_2() { + length([1, 2, 3]) == 3 +} + +/// Apply a function to each element of a list. +/// +/// ```aiken +/// list.map([1, 2, 3, 4], fn(n) { n + 1 }) == [2, 3, 4, 5] +/// ``` +pub fn map(self: List, with: fn(a) -> result) -> List { + when self is { + [] -> + [] + [x, ..xs] -> + [with(x), ..map(xs, with)] + } +} + +test map_1() { + map([], fn(n) { n + 1 }) == [] +} + +test map_2() { + map([1, 2, 3, 4], fn(n) { n + 1 }) == [2, 3, 4, 5] +} + +/// Apply a function of two arguments, combining elements from two lists. +/// +/// Note: if one list is longer, the extra elements are dropped. +/// +/// ```aiken +/// list.map2([1, 2, 3], [1, 2], fn(a, b) { a + b }) == [2, 4] +/// ``` +pub fn map2( + self: List, + bs: List, + with: fn(a, b) -> result, +) -> List { + when self is { + [] -> + [] + [x, ..xs] -> + when bs is { + [] -> + [] + [y, ..ys] -> + [with(x, y), ..map2(xs, ys, with)] + } + } +} + +test map2_1() { + map2([], [1, 2, 3], fn(a, b) { a + b }) == [] +} + +test map2_2() { + map2([1, 2, 3], [1, 2], fn(a, b) { a + b }) == [2, 4] +} + +test map2_3() { + map2([42], [1, 2, 3], fn(_a, b) { Some(b) }) == [Some(1)] +} + +/// Apply a function of three arguments, combining elements from three lists. +/// +/// Note: if one list is longer, the extra elements are dropped. +/// +/// ```aiken +/// list.map3([1, 2, 3], [1, 2], [1, 2, 3], fn(a, b, c) { a + b + c }) == [3, 6] +/// ``` +pub fn map3( + self: List, + bs: List, + cs: List, + with: fn(a, b, c) -> result, +) -> List { + when self is { + [] -> + [] + [x, ..xs] -> + when bs is { + [] -> + [] + [y, ..ys] -> + when cs is { + [] -> + [] + [z, ..zs] -> + [with(x, y, z), ..map3(xs, ys, zs, with)] + } + } + } +} + +test map3_1() { + map3([], [], [1, 2, 3], fn(a, b, c) { a + b + c }) == [] +} + +test map3_2() { + map3([1, 2, 3], [1, 2], [1, 2, 3], fn(a, b, c) { a + b + c }) == [3, 6] +} + +/// Add an element in front of the list. Sometimes useful when combined with +/// other functions. +/// +/// ```aiken +/// list.push([2, 3], 1) == [1, ..[2, 3]] == [1, 2, 3] +/// ``` +pub fn push(self: List, elem: a) -> List { + [elem, ..self] +} + +test push_1() { + push([], 0) == [0] +} + +test push_2() { + push([2, 3], 1) == [1, 2, 3] +} + +/// Construct a list of a integer from a given range. +/// +/// ```aiken +/// list.range(0, 3) == [0, 1, 2, 3] +/// list.range(-1, 1) == [-1, 0, 1] +/// ``` +pub fn range(from: Int, to: Int) -> List { + if from > to { + [] + } else { + [from, ..range(from + 1, to)] + } +} + +test range_1() { + range(0, 3) == [0, 1, 2, 3] +} + +test range_2() { + range(-1, 1) == [-1, 0, 1] +} + +/// Construct a list filled with n copies of a value. +/// +/// ```aiken +/// list.repeat("na", 3) == ["na", "na", "na"] +/// ``` +pub fn repeat(elem: a, n_times: Int) -> List { + if n_times <= 0 { + [] + } else { + [elem, ..repeat(elem, n_times - 1)] + } +} + +test repeat_1() { + repeat(42, 0) == [] +} + +test repeat_2() { + repeat(14, 3) == [14, 14, 14] +} + +/// Return the list with its elements in the reserve order. +/// +/// ```aiken +/// list.reverse([1, 2, 3]) == [3, 2, 1] +/// ``` +pub fn reverse(self: List) -> List { + foldl(self, [], fn(x, xs) { [x, ..xs] }) +} + +test reverse_1() { + reverse([]) == [] +} + +test reverse_2() { + reverse([1, 2, 3]) == [3, 2, 1] +} + +/// Returns a tuple with all elements that satisfy the predicate at first +/// element, and the rest as second element. +/// +/// ```aiken +/// list.partition([1, 2, 3, 4], fn(x) { x % 2 == 0 }) == ([2, 4], [1, 3]) +/// ``` +pub fn partition(self: List, predicate: fn(a) -> Bool) -> (List, List) { + when self is { + [] -> ([], []) + [x, ..xs] -> { + let (left, right) = partition(xs, predicate) + if predicate(x) { + ([x, ..left], right) + } else { + (left, [x, ..right]) + } + } + } +} + +test partition_1() { + partition([], fn(x) { x > 2 }) == ([], []) +} + +test partition_2() { + let xs = + [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] + partition(xs, fn(x) { x > 5 }) == ([10, 9, 8, 7, 6], [5, 4, 3, 2, 1]) +} + +test partition_3() { + let xs = + [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] + partition(xs, fn(x) { x == 42 }) == ([], xs) +} + +test partition_4() { + let xs = + [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] + partition(xs, fn(x) { x < 42 }) == (xs, []) +} + +test partition_5() { + partition([1, 2, 3, 4], fn(x) { x % 2 == 0 }) == ([2, 4], [1, 3]) +} + +/// Extract a sublist from the given list using 0-based indexes. Negative +/// indexes wrap over, so `-1` refers to the last element of the list. +/// +/// ```aiken +/// list.slice([1, 2, 3, 4, 5, 6], from: 2, to: 4) == [3, 4, 5] +/// list.slice([1, 2, 3, 4, 5, 6], from: -2, to: -1) == [5, 6] +/// list.slice([1, 2, 3, 4, 5, 6], from: 1, to: -1) == [2, 3, 4, 5, 6] +/// ``` +pub fn slice(self: List, from: Int, to: Int) { + let (i, l) = + if from >= 0 { + (from, None) + } else { + let l = length(self) + (l + from, Some(l)) + } + + let j = + if to >= 0 { + to - i + 1 + } else { + when l is { + Some(l) -> l + to - i + 1 + None -> length(self) + to - i + 1 + } + } + + self + |> drop(i) + |> take(j) +} + +test slice_1() { + slice([1, 2, 3], 0, 2) == [1, 2, 3] +} + +test slice_2() { + slice([1, 2, 3, 4, 5, 6], from: 2, to: 4) == [3, 4, 5] +} + +test slice_3() { + slice([1, 2, 3, 4, 5, 6], from: -2, to: -1) == [5, 6] +} + +test slice_4() { + slice([1, 2, 3, 4, 5, 6], from: 1, to: -1) == [2, 3, 4, 5, 6] +} + +test slice_5() { + slice([1, 2, 3, 4, 5, 6], from: -4, to: -3) == [3, 4] +} + +test slice_6() { + slice([1, 2, 3, 4, 5, 6], from: -2, to: 1) == [] +} + +/// Sort a list in ascending order using the given comparison function. +/// +/// ```aiken +/// use aiken/int +/// +/// sort([3, 1, 4, 0, 2], int.compare) == [0, 1, 2, 3, 4] +/// sort([1, 2, 3], int.compare) == [1, 2, 3] +/// ``` +pub fn sort(self: List, compare: fn(a, a) -> Ordering) -> List { + when self is { + [] -> + [] + [x, ..xs] -> insert(sort(xs, compare), x, compare) + } +} + +fn insert(self: List, e: a, compare: fn(a, a) -> Ordering) -> List { + when self is { + [] -> + [e] + [x, ..xs] -> + if compare(e, x) == Less { + [e, ..self] + } else { + [x, ..insert(xs, e, compare)] + } + } +} + +test sort_1() { + let xs = + [6, 7, 5, 4, 1, 3, 9, 8, 0, 2] + sort(xs, int.compare) == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +} + +test sort_2() { + let xs = + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + sort(xs, int.compare) == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +} + +test sort_3() { + let xs = + [9, 8, 7, 6, 5, 4, 3, 2, 1, 0] + sort(xs, int.compare) == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +} + +test sort_4() { + sort([], int.compare) == [] +} + +/// Cut a list in two, such that the first list contains the given number of / +/// elements and the second list contains the rest. +/// +/// Fundamentally equivalent to (but more efficient): +/// +/// ```aiken +/// // span(xs, n) == (take(xs, n), drop(xs, n)) +/// span([1, 2, 3, 4, 5], 3) == ([1, 2, 3], [4, 5]) +/// ``` +pub fn span(self: List, n: Int) -> (List, List) { + when self is { + [] -> ([], []) + [x, ..xs] -> + if n <= 0 { + ([], self) + } else { + let (left, right) = span(xs, n - 1) + ([x, ..left], right) + } + } +} + +test span_1() { + span([], 2) == ([], []) +} + +test span_2() { + span([1, 2, 3], 2) == ([1, 2], [3]) +} + +test span_3() { + span([1, 2, 3], -1) == ([], [1, 2, 3]) +} + +test span_4() { + span([1, 2, 3], 42) == ([1, 2, 3], []) +} + +/// Get elements of a list after the first one, if any. +/// +/// ```aiken +/// list.tail([]) == None +/// list.tail([1, 2, 3]) == Some([2, 3]) +/// ``` +pub fn tail(self: List) -> Option> { + when self is { + [] -> None + [_, ..xs] -> Some(xs) + } +} + +test tail_1() { + tail([1, 2, 3]) == Some([2, 3]) +} + +test tail_2() { + tail([]) == None +} + +/// Get the first `n` elements of a list. +/// +/// ```aiken +/// list.take([1, 2, 3], 2) == [1, 2] +/// list.take([1, 2, 3], 14) == [1, 2, 3] +/// ``` +pub fn take(self: List, n: Int) -> List { + if n <= 0 { + [] + } else { + when self is { + [] -> + [] + [x, ..xs] -> + [x, ..take(xs, n - 1)] + } + } +} + +test take_1() { + take([], 42) == [] +} + +test take_2() { + take([1, 2, 3], 2) == [1, 2] +} + +/// Returns the longest prefix of the given list where all elements satisfy the predicate. +/// +/// ```aiken +/// list.take_while([1, 2, 3], fn(x) { x > 2 }) == [] +/// list.take_while([1, 2, 3], fn(x) { x < 2 }) == [1] +/// ``` +pub fn take_while(self: List, predicate: fn(a) -> Bool) -> List { + when self is { + [] -> + [] + [x, ..xs] -> + if predicate(x) { + [x, ..take_while(xs, predicate)] + } else { + [] + } + } +} + +test take_while_1() { + take_while([], fn(x) { x > 2 }) == [] +} + +test take_while_2() { + let xs = + [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] + take_while(xs, fn(x) { x > 5 }) == [10, 9, 8, 7, 6] +} + +test take_while_3() { + let xs = + [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] + take_while(xs, fn(x) { x == 42 }) == [] +} + +test take_while_4() { + let xs = + [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] + take_while(xs, fn(x) { x < 42 }) == xs +} + +/// Removes duplicate elements from a list. +/// +/// ```aiken +/// list.unique([1, 2, 3, 1]) == [1, 2, 3] +/// ``` +pub fn unique(self: List) -> List { + when self is { + [] -> + [] + [x, ..xs] -> + [x, ..unique(filter(xs, fn(y) { y != x }))] + } +} + +test unique_1() { + unique([]) == [] +} + +test unique_2() { + let xs = + [1, 2, 3, 1, 1, 3, 4, 1, 2, 3, 2, 4, 5, 6, 7, 8, 9, 10, 9] + unique(xs) == [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] +} + +/// Decompose a list of tuples into a tuple of lists. +/// +/// ``` +/// list.unzip([(1, "a"), (2, "b")]) == ([1, 2], ["a", "b"]) +/// ``` +pub fn unzip(self: List<(a, b)>) -> (List, List) { + when self is { + [] -> ([], []) + [(a, b), ..xs] -> { + let (a_tail, b_tail) = unzip(xs) + ([a, ..a_tail], [b, ..b_tail]) + } + } +} + +test unzip_1() { + unzip([]) == ([], []) +} + +test unzip_2() { + unzip([(1, "a"), (2, "b")]) == ([1, 2], ["a", "b"]) +} + +/// Combine two lists together. +/// +/// Note: if one list is longer, the extra elements are dropped. +/// +/// ```aiken +/// list.zip([1, 2], ["a", "b", "c"]) == [(1, "a"), (2, "b")] +/// ``` +pub fn zip(self: List, bs: List) -> List<(a, b)> { + when self is { + [] -> + [] + [x, ..xs] -> + when bs is { + [] -> + [] + [y, ..ys] -> + [(x, y), ..zip(xs, ys)] + } + } +} + +test zip_1() { + zip([], [1, 2, 3]) == [] +} + +test zip_2() { + zip([1, 2, 3], []) == [] +} + +test zip_3() { + zip([1, 2], ["a", "b", "c"]) == [(1, "a"), (2, "b")] +} + +/// Reduce a list from left to right using the accumulator as left operand. +/// Said differently, this is [`foldl`](#foldl) with callback arguments swapped. +/// +/// ```aiken +/// list.reduce([#[1], #[2], #[3]], #[0], bytearray.concat) == #[0, 1, 2, 3] +/// list.reduce([True, False, True], False, fn(b, a) { or { b, a } }) == True +/// ``` +pub fn reduce(self: List, zero: b, with: fn(b, a) -> b) -> b { + foldl(self, zero, flip(with)) +} + +test reduce_1() { + reduce([], 0, fn(n, total) { n + total }) == 0 +} + +test reduce_2() { + reduce([1, 2, 3], 0, fn(n, total) { n + total }) == 6 +} + +test reduce_3() { + reduce([True, False, True], False, fn(left, right) { left || right }) == True +} + +test reduce_4() { + reduce( + [#[1], #[2], #[3]], + #[9], + fn(left, right) { bytearray.concat(left, right) }, + ) == #[9, 1, 2, 3] +} diff --git a/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/math.ak b/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/math.ak new file mode 100644 index 00000000..764152bf --- /dev/null +++ b/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/math.ak @@ -0,0 +1,372 @@ +//// This module contains some basic Math utilities. Standard arithmetic +//// operations on integers are available through native operators: +//// +//// Operator | Description +//// --- | :--- +//// `+` | Arithmetic sum +//// `-` | Arithmetic difference +//// `/` | Whole division +//// `*` | Arithmetic multiplication +//// `%` | Remainder by whole division +//// +//// Here are a few examples: +//// +//// ```aiken +//// 1 + 1 // 2 +//// 10 - 2 // 8 +//// 40 / 14 // 2 +//// 3 * 4 // 12 +//// 10 % 3 // 1 + +use aiken/builtin + +/// Calculate the absolute value of an integer. +/// +/// ```aiken +/// math.abs(-42) == 42 +/// math.abs(14) == 14 +/// ``` +pub fn abs(self: Int) -> Int { + if self < 0 { + 0 - self + } else { + self + } +} + +test abs_1() { + abs(14) == 14 +} + +test abs_2() { + abs(-42) == 42 +} + +/// Restrict the value of an integer between two min and max bounds +/// +/// ```aiken +/// math.clamp(14, min: 0, max: 10) == 10 +/// ``` +pub fn clamp(self: Int, min: Int, max: Int) -> Int { + if self < min { + min + } else { + if self > max { + max + } else { + self + } + } +} + +test clamp_1() { + clamp(14, min: 0, max: 10) == 10 +} + +test clamp_2() { + clamp(7, min: 0, max: 10) == 7 +} + +test clamp_3() { + clamp(7, min: 10, max: 100) == 10 +} + +/// Return the maximum of two integers. +pub fn max(a: Int, b: Int) -> Int { + if a > b { + a + } else { + b + } +} + +test max_1() { + max(0, 0) == 0 +} + +test max_2() { + max(14, 42) == 42 +} + +test max_3() { + max(42, 14) == 42 +} + +/// Return the minimum of two integers. +pub fn min(a: Int, b: Int) -> Int { + if a > b { + b + } else { + a + } +} + +test min_1() { + min(0, 0) == 0 +} + +test min_2() { + min(14, 42) == 14 +} + +test min_3() { + min(42, 14) == 14 +} + +/// Calculates a number to the power of `e` using the exponentiation by +/// squaring method. +/// +/// ```aiken +/// math.pow(3, 5) == 243 +/// math.pow(7, 2) == 49 +/// math.pow(3, -4) == 0 +/// math.pow(0, 0) == 1 +/// math.pow(513, 3) == 135005697 +/// ``` +pub fn pow(self: Int, e: Int) -> Int { + if e < 0 { + 0 + } else if e == 0 { + 1 + } else if e % 2 == 0 { + pow(self * self, e / 2) + } else { + self * pow(self * self, ( e - 1 ) / 2) + } +} + +test pow_3_5() { + pow(3, 5) == 243 +} + +test pow_7_2() { + pow(7, 2) == 49 +} + +test pow_3__4() { + // negative powers round to zero + pow(3, -4) == 0 +} + +test pow_0_0() { + // sorry math + pow(0, 0) == 1 +} + +test pow_513_3() { + pow(513, 3) == 135005697 +} + +test pow_2_4() { + pow(2, 4) == 16 +} + +test pow_2_42() { + pow(2, 42) == 4398046511104 +} + +/// Calculates the power of 2 for a given exponent `e`. Much cheaper than +/// using `pow(2, _)` for small exponents (0 < e < 256). +/// +/// ```aiken +/// math.pow2(-2) == 0 +/// math.pow2(0) == 1 +/// math.pow2(1) == 2 +/// math.pow2(4) == 16 +/// math.pow2(42) == 4398046511104 +/// ``` +pub fn pow2(e: Int) -> Int { + // do_pow2(e, 1) + if e < 8 { + if e < 0 { + 0 + } else { + builtin.index_bytearray(#[1, 2, 4, 8, 16, 32, 64, 128], e) + } + } else if e < 32 { + 256 * pow2(e - 8) + } else { + 4294967296 * pow2(e - 32) + } +} + +test pow2_neg() { + pow2(-2) == 0 +} + +test pow2_0() { + pow2(0) == 1 +} + +test pow2_1() { + pow2(1) == 2 +} + +test pow2_4() { + pow2(4) == 16 +} + +test pow2_42() { + pow2(42) == 4398046511104 +} + +test pow2_256() { + pow2(256) == 115792089237316195423570985008687907853269984665640564039457584007913129639936 +} + +/// The logarithm in base `b` of an element using integer divisions. +/// +/// ```aiken +/// math.log(10, base: 2) == 3 +/// math.log(42, base: 2) == 5 +/// math.log(42, base: 3) == 3 +/// math.log(5, base: 0) == 0 +/// math.log(4, base: 4) == 1 +/// math.log(4, base: 42) == 0 +/// ``` +pub fn log(self: Int, base: Int) -> Int { + if base <= 0 { + 0 + } else if self == base { + 1 + } else if self < base { + 0 + } else { + 1 + log(self / base, base) + } +} + +test log_10_2() { + log(10, base: 2) == 3 +} + +test log_42_2() { + log(42, base: 2) == 5 +} + +test log_42_3() { + log(42, base: 3) == 3 +} + +test log_5_0() { + log(5, base: 0) == 0 +} + +test log_4_4() { + log(4, base: 4) == 1 +} + +test log_4_43() { + log(4, base: 43) == 0 +} + +/// The greatest common divisor of two integers. +/// +/// ```aiken +/// math.gcd(42, 14) == 14 +/// math.gcd(14, 42) == 14 +/// math.gcd(0, 0) == 0 +/// ``` +pub fn gcd(x: Int, y: Int) -> Int { + abs(do_gcd(x, y)) +} + +fn do_gcd(x: Int, y: Int) -> Int { + when y is { + 0 -> x + _ -> do_gcd(y, x % y) + } +} + +test gcd_test1() { + gcd(10, 300) == 10 +} + +test gcd_test2() { + gcd(-10, 300) == 10 +} + +test gcd_test3() { + gcd(42, 14) == 14 +} + +/// Calculates the square root of an integer using the [Babylonian +/// method](https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method). This returns either the exact result or the smallest integer +/// nearest to the square root. +/// +/// Returns `None` for negative values. +/// +/// ```aiken +/// math.sqrt(0) == Some(0) +/// math.sqrt(25) == Some(5) +/// math.sqrt(44203) == Some(210) +/// math.sqrt(-42) == None +/// ``` +pub fn sqrt(self: Int) -> Option { + if self < 0 { + None + } else if self <= 1 { + Some(self) + } else { + Some(sqrt_babylonian(self, self, ( self + 1 ) / 2)) + } +} + +// The basic idea is that if x is an overestimate to the square root of a +// non-negative real number S then S/x will be an underestimate, or vice versa, +// and so the average of these two numbers may reasonably be expected to provide a +// better approximation (though the formal proof of that assertion depends on the +// inequality of arithmetic and geometric means that shows this average is always +// an overestimate of the square root. +fn sqrt_babylonian(self: Int, x: Int, y: Int) -> Int { + if y >= x { + x + } else { + sqrt_babylonian(self, y, ( y + self / y ) / 2) + } +} + +test sqrt1() { + sqrt(0) == Some(0) +} + +test sqrt2() { + sqrt(1) == Some(1) +} + +test sqrt3() { + sqrt(25) == Some(5) +} + +test sqrt4() { + sqrt(44203) == Some(210) +} + +test sqrt5() { + sqrt(975461057789971041) == Some(987654321) +} + +test sqrt6() { + sqrt(-42) == None +} + +/// Checks if an integer has a given integer square root x. +/// The check has constant time complexity (O(1)). +/// +/// ```aiken +/// math.is_sqrt(0, 0) +/// math.is_sqrt(25, 5) +/// ! math.is_sqrt(25, -5) +/// math.is_sqrt(44203, 210) +/// ``` +pub fn is_sqrt(self: Int, x: Int) -> Bool { + x * x <= self && ( x + 1 ) * ( x + 1 ) > self +} + +test is_sqrt1() { + is_sqrt(44203, 210) +} + +test is_sqrt2() { + is_sqrt(975461057789971041, 987654321) +} diff --git a/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/math/rational.ak b/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/math/rational.ak new file mode 100644 index 00000000..6bca6434 --- /dev/null +++ b/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/math/rational.ak @@ -0,0 +1,795 @@ +//// This module implements operations between rational numbers. Internally, rational aren't +//// automatically reduced as this is **only done on-demand**. +//// +//// Thus, for example: +//// +//// ```aiken +//// rational.new(2, 3) != rational.new(4, 6) +//// ``` +//// +//// Comparing rational values should, therefore, only happen after reduction (see [reduce](#reduce)) or via the [compare](#compare) method. + +use aiken/builtin +use aiken/list +use aiken/math +use aiken/option + +/// Opaque type used to ensure the sign of the Rational is managed strictly in the numerator. +pub opaque type Rational { + numerator: Int, + denominator: Int, +} + +/// An unsafe constructor for `Rational` values. Assumes that the following invariants are +/// enforced: +/// +/// - the denominator is positive (the sign is managed in the numerator); +/// - the denominator is not null. +/// +/// This function is mainly used as a quick way to construct rationals from literal values. +fn ratio(numerator: Int, denominator: Int) -> Rational { + Rational { numerator, denominator } +} + +/// Make a `Rational` number from the ratio of two integers. +/// +/// Returns `None` when the denominator is null. +/// +/// ```aiken +/// rational.new(14, 42) == Some(r) +/// rational.new(14, 0) == None +/// ``` +pub fn new(numerator: Int, denominator: Int) -> Option { + if denominator == 0 { + None + } else if denominator < 0 { + Some(Rational { numerator: -numerator, denominator: -denominator }) + } else { + Some(Rational { numerator, denominator }) + } +} + +test new_1() { + and { + (new(2, 0) == None)?, + (new(2, 3) == Some(ratio(2, 3)))?, + (new(-2, 3) == Some(ratio(-2, 3)))?, + (new(2, -3) == Some(ratio(-2, 3)))?, + (new(2, 4) == Some(ratio(2, 4)))?, + (new(-2, -3) == Some(ratio(2, 3)))?, + (new(-2, -4) == Some(ratio(2, 4)))?, + } +} + +/// Get the numerator of a rational value. +/// +/// ```aiken +/// expect Some(x) = rational.new(2, 3) +/// rational.numerator(x) == 2 +/// ``` +pub fn numerator(self: Rational) -> Int { + self.numerator +} + +test numerator_1() { + expect Some(x) = new(2, 3) + expect Some(y) = new(-2, 3) + expect Some(z) = new(2, -3) + expect Some(w) = new(-2, -3) + + and { + (numerator(x) == 2)?, + (numerator(y) == -2)?, + (numerator(z) == -2)?, + (numerator(w) == 2)?, + } +} + +/// Get the denominator of a rational value. +/// +/// ```aiken +/// expect Some(x) = rational.new(2, 3) +/// rational.denominator(x) == 3 +/// ``` +pub fn denominator(self: Rational) -> Int { + self.denominator +} + +test denominator_1() { + expect Some(x) = new(2, 3) + expect Some(y) = new(-2, 3) + expect Some(z) = new(2, -3) + expect Some(w) = new(-2, -3) + and { + (denominator(x) == 3)?, + (denominator(y) == 3)?, + (denominator(z) == 3)?, + (denominator(w) == 3)?, + } +} + +/// A null `Rational`. +pub fn zero() -> Rational { + Rational { numerator: 0, denominator: 1 } +} + +test zero_1() { + zero() == ratio(0, 1) +} + +/// Multiplication: the product of two rational values. +/// +/// ```aiken +/// expect Some(x) = rational.new(2, 3) +/// expect Some(y) = rational.new(3, 4) +/// +/// Some(rational.mul(x, y)) == rational.new(6, 12) +/// ``` +pub fn mul(left: Rational, right: Rational) -> Rational { + let Rational { numerator: a_n, denominator: a_d } = left + let Rational { numerator: b_n, denominator: b_d } = right + Rational { numerator: a_n * b_n, denominator: a_d * b_d } +} + +test mul_1() { + mul(ratio(2, 3), ratio(3, 4)) == ratio(6, 12) +} + +test mul_2() { + mul(ratio(-2, 3), ratio(-3, 4)) == ratio(6, 12) +} + +test mul_3() { + let result = + ratio(2, 5) + |> mul(ratio(1, 8)) + |> mul(ratio(3, 10)) + |> mul(ratio(21, 100)) + |> mul(ratio(3, 5)) + |> mul(ratio(2, 8)) + |> mul(ratio(4, 10)) + |> mul(ratio(22, 100)) + |> reduce + + result == ratio(2079, 50000000) +} + +/// Division: quotient of two rational values. Returns `None` when the second +/// value is null. +/// +/// ```aiken +/// expect Some(x) = rational.new(2, 3) +/// expect Some(y) = rational.new(3, 4) +/// +/// rational.div(x, y) == rational.new(8, 9) +/// ``` +pub fn div(left: Rational, right: Rational) -> Option { + reciprocal(right) |> option.map(mul(left, _)) +} + +test div_1() { + div(ratio(2, 3), ratio(3, 4)) == new(8, 9) +} + +test div_2() { + div(ratio(2, 3), ratio(-3, 4)) == new(-8, 9) +} + +/// Addition: sum of two rational values +/// +/// ```aiken +/// expect Some(x) = rational.new(2, 3) +/// expect Some(y) = rational.new(3, 4) +/// +/// Some(rational.add(x, y)) == rational.new(17, 12) +/// ``` +pub fn add(left: Rational, right: Rational) -> Rational { + let Rational { numerator: a_n, denominator: a_d } = left + let Rational { numerator: b_n, denominator: b_d } = right + Rational { numerator: a_n * b_d + b_n * a_d, denominator: a_d * b_d } +} + +test add_1() { + add(ratio(2, 3), ratio(3, 4)) == ratio(17, 12) +} + +test add_2() { + add(ratio(-2, 3), ratio(3, 4)) == ratio(1, 12) +} + +/// Subtraction: difference of two rational values +/// +/// ```aiken +/// expect Some(x) = rational.new(2, 3) +/// expect Some(y) = rational.new(3, 4) +/// +/// Some(rational.sub(x, y)) == rational.new(-1, 12) +/// ``` +pub fn sub(left: Rational, right: Rational) -> Rational { + let Rational { numerator: a_n, denominator: a_d } = left + let Rational { numerator: b_n, denominator: b_d } = right + Rational { numerator: a_n * b_d - b_n * a_d, denominator: a_d * b_d } +} + +test sub_1() { + sub(ratio(2, 3), ratio(3, 4)) == ratio(-1, 12) +} + +test sub_2() { + sub(ratio(2, 3), ratio(-3, 4)) == ratio(17, 12) +} + +test sub_3() { + sub(ratio(-2, 3), ratio(3, 4)) == ratio(-17, 12) +} + +/// Create a new `Rational` from an `Int`. +/// +/// ```aiken +/// Some(rational.from_int(14)) == rational.new(14, 1) +/// Some(rational.from_int(-5)) == rational.new(-5, 1) +/// Some(rational.from_int(0)) == rational.new(0, 1) +/// ``` +pub fn from_int(numerator: Int) -> Rational { + Rational { numerator, denominator: 1 } +} + +test from_int_1() { + and { + (from_int(14) == ratio(14, 1))?, + (from_int(-5) == ratio(-5, 1))?, + (from_int(0) == ratio(0, 1))?, + } +} + +/// Returns the nearest `Int` between zero and a given `Rational`. +/// +/// ```aiken +/// expect Some(x) = rational.new(2, 3) +/// rational.truncate(x) == 0 +/// +/// expect Some(y) = rational.new(44, 14) +/// rational.truncate(y) == 3 +/// +/// expect Some(z) = rational.new(-14, 3) +/// rational.truncate(z) == -4 +/// ``` +pub fn truncate(self: Rational) -> Int { + let Rational { numerator: a_n, denominator: a_d } = self + builtin.quotient_integer(a_n, a_d) +} + +test truncate_1() { + and { + (truncate(ratio(5, 2)) == 2)?, + (truncate(ratio(5, 3)) == 1)?, + (truncate(ratio(5, 4)) == 1)?, + (truncate(ratio(5, 5)) == 1)?, + (truncate(ratio(5, 6)) == 0)?, + (truncate(ratio(8, 3)) == 2)?, + (truncate(ratio(-14, 3)) == -4)?, + } +} + +/// Returns the greatest `Int` no greater than a given `Rational` +/// +/// ```aiken +/// expect Some(x) = rational.new(2, 3) +/// rational.floor(x) == 0 +/// +/// expect Some(y) = rational.new(44, 14) +/// rational.floor(y) == 3 +/// +/// expect Some(z) = rational.new(-14, 3) +/// rational.floor(z) == -5 +/// ``` +pub fn floor(self: Rational) -> Int { + let Rational { numerator: a_n, denominator: a_d } = self + a_n / a_d +} + +test floor_1() { + and { + (floor(ratio(5, 2)) == 2)?, + (floor(ratio(5, 3)) == 1)?, + (floor(ratio(5, 4)) == 1)?, + (floor(ratio(5, 5)) == 1)?, + (floor(ratio(5, 6)) == 0)?, + (floor(ratio(8, 3)) == 2)?, + (floor(ratio(-14, 3)) == -5)?, + } +} + +/// Returns the smallest `Int` not less than a given `Rational` +/// +/// ```aiken +/// expect Some(x) = rational.new(2, 3) +/// rational.ceil(x) == 1 +/// +/// expect Some(y) = rational.new(44, 14) +/// rational.ceil(y) == 4 +/// +/// expect Some(z) = rational.new(-14, 3) +/// rational.ceil(z) == -4 +/// ``` +pub fn ceil(self: Rational) -> Int { + let Rational { numerator, denominator } = self + if builtin.remainder_integer(numerator, denominator) > 0 { + builtin.quotient_integer(numerator, denominator) + 1 + } else { + builtin.quotient_integer(numerator, denominator) + } +} + +test ceil_1() { + and { + (ceil(ratio(13, 5)) == 3)?, + (ceil(ratio(15, 5)) == 3)?, + (ceil(ratio(16, 5)) == 4)?, + (ceil(ratio(-3, 5)) == 0)?, + (ceil(ratio(-5, 5)) == -1)?, + (ceil(ratio(-14, 3)) == -4)?, + (ceil(ratio(-14, 6)) == -2)?, + (ceil(ratio(44, 14)) == 4)?, + } +} + +/// Returns the proper fraction of a given `Rational` `r`. That is, a 2-tuple of +/// an `Int` and `Rational` (n, f) such that: +/// +/// - `r = n + f`; +/// - `n` and `f` have the same sign as `r`; +/// - `f` has an absolute value less than 1. +pub fn proper_fraction(self: Rational) -> (Int, Rational) { + let Rational { numerator, denominator } = self + ( + builtin.quotient_integer(numerator, denominator), + Rational { + numerator: builtin.remainder_integer(numerator, denominator), + denominator, + }, + ) +} + +test proper_fraction_1() { + let r = ratio(10, 7) + let (n, f) = proper_fraction(r) + and { + (n == 1)?, + (f == ratio(3, 7))?, + (r == add(from_int(n), f))?, + } +} + +test proper_fraction_2() { + let r = ratio(-10, 7) + let (n, f) = proper_fraction(r) + and { + (n == -1)?, + (f == ratio(-3, 7))?, + (r == add(from_int(n), f))?, + } +} + +test proper_fraction_3() { + let r = ratio(4, 2) + let (n, f) = proper_fraction(r) + and { + (n == 2)?, + (f == ratio(0, 2))?, + (r == add(from_int(n), f))?, + } +} + +/// Round the argument to the nearest whole number. If the argument is +/// equidistant between two values, the greater value is returned (it +/// rounds half towards positive infinity). +/// +/// ```aiken +/// expect Some(x) = rational.new(2, 3) +/// rational.round(x) == 1 +/// +/// expect Some(y) = rational.new(3, 2) +/// rational.round(y) == 2 +/// +/// expect Some(z) = rational.new(-3, 2) +/// rational.round(z) == -1 +/// ``` +/// +/// ⚠️ This behaves differently than _Haskell_. If you're coming from +/// `PlutusTx`, beware that in Haskell, rounding on equidistant values depends on the +/// whole number being odd or even. If you need this behaviour, use [`round_even`](#round_even). +pub fn round(self: Rational) -> Int { + let (n, f) = proper_fraction(self) + + let is_negative = f.numerator < 0 + + when compare(abs(f), ratio(1, 2)) is { + Less -> n + Equal if is_negative -> n + Equal -> n + 1 + Greater if is_negative -> n - 1 + Greater -> n + 1 + } +} + +test round_1() { + and { + (round(ratio(10, 7)) == 1)?, + (round(ratio(11, 7)) == 2)?, + (round(ratio(3, 2)) == 2)?, + (round(ratio(5, 2)) == 3)?, + (round(ratio(-3, 2)) == -1)?, + (round(ratio(-2, 3)) == -1)?, + (round(ratio(-10, 7)) == -1)?, + (round(ratio(4, 2)) == 2)?, + } +} + +/// Round the argument to the nearest whole number. If the argument is +/// equidistant between two values, it returns the value that is even (it +/// rounds half to even, also known as 'banker's rounding'). +/// +/// ```aiken +/// expect Some(w) = rational.new(2, 3) +/// rational.round_even(w) == 1 +/// +/// expect Some(x) = rational.new(3, 2) +/// rational.round_even(x) == 2 +/// +/// expect Some(y) = rational.new(5, 2) +/// rational.round_even(y) == 2 +/// +/// expect Some(y) = rational.new(-3, 2) +/// rational.round_even(y) == -2 +/// ``` +pub fn round_even(self: Rational) -> Int { + let (n, f) = proper_fraction(self) + + let m = + when compare(f, ratio(0, 1)) is { + Less -> -1 + _ -> 1 + } + + let is_even = n % 2 == 0 + + when compare(abs(f), ratio(1, 2)) is { + Less -> n + Equal if is_even -> n + Equal -> n + m + Greater -> n + m + } +} + +test round_even_1() { + and { + (round_even(ratio(10, 7)) == 1)?, + (round_even(ratio(11, 7)) == 2)?, + (round_even(ratio(3, 2)) == 2)?, + (round_even(ratio(5, 2)) == 2)?, + (round_even(ratio(-3, 2)) == -2)?, + (round_even(ratio(-2, 3)) == -1)?, + (round_even(ratio(-10, 7)) == -1)?, + (round_even(ratio(4, 2)) == 2)?, + } +} + +/// Change the sign of a `Rational`. +/// +/// ```aiken +/// expect Some(x) = rational.new(3, 2) +/// expect Some(y) = rational.new(-3, 2) +/// +/// rational.negate(x) == y +/// rational.negate(y) == x +/// ``` +pub fn negate(a: Rational) -> Rational { + let Rational { numerator: a_n, denominator: a_d } = a + Rational { numerator: -a_n, denominator: a_d } +} + +test negate_1() { + and { + (negate(ratio(5, 2)) == ratio(-5, 2))?, + (negate(ratio(-5, 2)) == ratio(5, 2))?, + (negate(negate(ratio(5, 2))) == ratio(5, 2))?, + } +} + +/// Absolute value of a `Rational`. +/// +/// ```aiken +/// expect Some(x) = rational.new(3, 2) +/// expect Some(y) = rational.new(-3, 2) +/// +/// rational.abs(x) == x +/// rational.abs(y) == x +/// ``` +pub fn abs(self: Rational) -> Rational { + let Rational { numerator: a_n, denominator: a_d } = self + Rational { numerator: math.abs(a_n), denominator: a_d } +} + +test abs_examples() { + and { + (abs(ratio(5, 2)) == ratio(5, 2))?, + (abs(ratio(-5, 2)) == ratio(5, 2))?, + (abs(ratio(5, 2)) == abs(ratio(-5, 2)))?, + } +} + +/// Reciprocal of a `Rational` number. That is, a new `Rational` where the +/// numerator and denominator have been swapped. +/// +/// ```aiken +/// expect Some(x) = rational.new(2, 5) +/// rational.reciprocal(x) == rational.new(5, 2) +/// +/// let y = rational.zero() +/// rational.reciprocal(y) == None +/// ``` +pub fn reciprocal(self: Rational) -> Option { + let Rational { numerator: a_n, denominator: a_d } = self + if a_n < 0 { + Some(Rational { numerator: -a_d, denominator: -a_n }) + } else if a_n > 0 { + Some(Rational { numerator: a_d, denominator: a_n }) + } else { + None + } +} + +test reciprocal_1() { + and { + (reciprocal(ratio(5, 2)) == new(2, 5))?, + (reciprocal(ratio(-5, 2)) == new(-2, 5))?, + (reciprocal(ratio(0, 2)) == None)?, + (reciprocal(ratio(2, 3)) == new(3, 2))?, + (reciprocal(ratio(-2, 3)) == new(-3, 2))?, + } +} + +/// Reduce a rational to its irreducible form. This operation makes the +/// numerator and denominator coprime. +/// +/// ```aiken +/// expect Some(x) = rational.new(80, 200) +/// Some(rational.reduce(x)) == rational.new(2, 5) +/// ``` +pub fn reduce(self: Rational) -> Rational { + let Rational { numerator: a_n, denominator: a_d } = self + let d = math.gcd(a_n, a_d) + Rational { numerator: a_n / d, denominator: a_d / d } +} + +test reduce_1() { + and { + (reduce(ratio(80, 200)) == ratio(2, 5))?, + (reduce(ratio(-5, 1)) == ratio(-5, 1))?, + (reduce(ratio(0, 3)) == ratio(0, 1))?, + } +} + +/// Compare two rationals for an ordering. This is safe to use even for +/// non-reduced rationals. +/// +/// ```aiken +/// expect Some(x) = rational.new(2, 3) +/// expect Some(y) = rational.new(3, 4) +/// expect Some(z) = rational.new(4, 6) +/// +/// compare(x, y) == Less +/// compare(y, x) == Greater +/// compare(x, x) == Equal +/// compare(x, z) == Equal +/// ``` +pub fn compare(left: Rational, right: Rational) -> Ordering { + let Rational { numerator: a_n, denominator: a_d } = left + let Rational { numerator: b_n, denominator: b_d } = right + + let l = a_n * b_d + let r = b_n * a_d + + if l < r { + Less + } else if l > r { + Greater + } else { + Equal + } +} + +test compare_1() { + expect Some(x) = new(2, 3) + expect Some(y) = new(3, 4) + expect Some(z) = new(4, 6) + and { + compare(x, y) == Less, + compare(y, x) == Greater, + compare(x, x) == Equal, + compare(x, z) == Equal, + } +} + +/// Comparison of two rational values using a chosen heuristic. For example: +/// +/// ```aiken +/// expect Some(x) = rational.new(2, 3) +/// expect Some(y) = rational.new(3, 4) +/// +/// rational.compare_with(x, >, y) == False +/// rational.compare_with(y, >, x) == True +/// rational.compare_with(x, >, x) == False +/// rational.compare_with(x, >=, x) == True +/// rational.compare_with(x, ==, x) == True +/// rational.compare_with(x, ==, y) == False +/// ``` +pub fn compare_with( + left: Rational, + with: fn(Int, Int) -> Bool, + right: Rational, +) -> Bool { + let Rational { numerator: a_n, denominator: a_d } = left + let Rational { numerator: b_n, denominator: b_d } = right + with(a_n * b_d, b_n * a_d) +} + +// TODO: Rewrite tests using binary-operator as first-class functions once aiken-lang/aiken#619 is merged. + +test compare_with_eq() { + let eq = + compare_with(_, fn(l, r) { l == r }, _) + + expect Some(x) = new(2, 3) + expect Some(y) = new(3, 4) + + !eq(x, y)? && !eq(y, x)? && eq(x, x)? +} + +test compare_with_neq() { + let neq = + compare_with(_, fn(l, r) { l != r }, _) + + expect Some(x) = new(2, 3) + expect Some(y) = new(3, 4) + + neq(x, y)? && neq(y, x)? && !neq(x, x)? +} + +test compare_with_gte() { + let gte = + compare_with(_, fn(l, r) { l >= r }, _) + + expect Some(x) = new(2, 3) + expect Some(y) = new(3, 4) + + !gte(x, y)? && gte(y, x)? && gte(x, x)? +} + +test compare_with_gt() { + let gt = + compare_with(_, fn(l, r) { l > r }, _) + + expect Some(x) = new(2, 3) + expect Some(y) = new(3, 4) + + !gt(x, y)? && gt(y, x)? && !gt(x, x)? +} + +test compare_with_lte() { + let lte = + compare_with(_, fn(l, r) { l <= r }, _) + + expect Some(x) = new(2, 3) + expect Some(y) = new(3, 4) + + lte(x, y)? && !lte(y, x)? && lte(x, x)? +} + +test compare_with_lt() { + let lt = + compare_with(_, fn(l, r) { l < r }, _) + + expect Some(x) = new(2, 3) + expect Some(y) = new(3, 4) + + lt(x, y)? && !lt(y, x)? && !lt(x, x)? +} + +/// Calculate the arithmetic mean between two `Rational` values. +/// +/// ```aiken +/// let x = rational.from_int(0) +/// let y = rational.from_int(1) +/// let z = rational.from_int(2) +/// +/// expect Some(result) = rational.arithmetic_mean([x, y, z]) +/// +/// rational.compare(result, y) == Equal +/// ``` +pub fn arithmetic_mean(self: List) -> Option { + div(list.foldr(self, zero(), add), from_int(list.length(self))) +} + +test arithmetic_mean_1() { + let x = ratio(1, 2) + let y = ratio(1, 2) + expect Some(z) = arithmetic_mean([x, y]) + reduce(z) == ratio(1, 2) +} + +test arithmetic_mean_2() { + let x = ratio(1, 1) + let y = ratio(2, 1) + expect Some(z) = arithmetic_mean([x, y]) + reduce(z) == ratio(3, 2) +} + +test arithmetic_mean_3() { + let xs = + [ + ratio(1, 1), + ratio(2, 1), + ratio(3, 1), + ratio(4, 1), + ratio(5, 1), + ratio(6, 1), + ] + expect Some(z) = arithmetic_mean(xs) + reduce(z) == ratio(7, 2) +} + +/// Calculate the geometric mean between two `Rational` values. This returns +/// either the exact result or the smallest integer nearest to the square root +/// for the numerator and denominator. +/// +/// ```aiken +/// expect Some(x) = rational.new(1, 3) +/// expect Some(y) = rational.new(1, 6) +/// +/// rational.geometric_mean(x, y) == rational.new(1, 4) +/// ``` +pub fn geometric_mean(left: Rational, right: Rational) -> Option { + let Rational { numerator: a_n, denominator: a_d } = left + let Rational { numerator: b_n, denominator: b_d } = right + when math.sqrt(a_n * b_n) is { + Some(numerator) -> + when math.sqrt(a_d * b_d) is { + Some(denominator) -> Some(Rational { numerator, denominator }) + None -> None + } + None -> None + } +} + +test geometric_mean1() { + expect Some(x) = new(1, 2) + expect Some(y) = new(1, 2) + geometric_mean(x, y) == new(1, 2) +} + +test geometric_mean2() { + expect Some(x) = new(-1, 2) + expect Some(y) = new(1, 2) + geometric_mean(x, y) == None +} + +test geometric_mean3() { + expect Some(x) = new(1, 2) + expect Some(y) = new(-1, 2) + geometric_mean(x, y) == None +} + +test geometric_mean4() { + expect Some(x) = new(1, 3) + expect Some(y) = new(1, 6) + geometric_mean(x, y) == new(1, 4) +} + +test geometric_mean5() { + expect Some(x) = new(67, 2500) + expect Some(y) = new(35331, 1000) + expect Some(yi) = reciprocal(y) + geometric_mean(x, yi) == new(258, 9398) +} diff --git a/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/option.ak b/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/option.ak new file mode 100644 index 00000000..8ad35fd0 --- /dev/null +++ b/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/option.ak @@ -0,0 +1,301 @@ +/// Picks the first element which is not None. If there's no such element, return None. +/// +/// ```aiken +/// option.choice([]) == None +/// option.choice([Some(14), Some(42)]) == Some(14) +/// option.choice([None, Some(42)]) == Some(42) +/// option.choice([None, None]) == None +/// ``` +pub fn choice(self: List>) -> Option { + when self is { + [] -> None + [head, ..others] -> + when head is { + None -> choice(others) + _ -> head + } + } +} + +test choice_1() { + Some(1) == choice([Some(1), Some(2)]) +} + +test choice_2() { + None == choice([]) +} + +test choice_3() { + Some(1) == choice([None, Some(1)]) +} + +/// Provide a default value, turning an optional value into a normal value. +/// +/// ```aiken +/// option.or_else(None, "aiken") == "aiken" +/// option.or_else(Some(42), 14) == 42 +/// ``` +pub fn or_else(self: Option, default: a) -> a { + when self is { + None -> default + Some(a) -> a + } +} + +test or_else_1() { + or_else(None, "aiken") == "aiken" +} + +test or_else_2() { + or_else(Some(42), 14) == 42 +} + +/// Like [`or_else`](#or_else) but allows returning an `Option`. +/// This is effectively mapping the error branch. +/// +/// ```aiken +/// option.or_try(None, fn(_) { Some("aiken") }) == Some("aiken") +/// option.or_try(Some(42), fn(_) { Some(14) }) == Some(42) +/// ``` +pub fn or_try(self: Option, compute_default: fn() -> Option) -> Option { + when self is { + None -> compute_default() + _ -> self + } +} + +test or_try_1() { + or_try(None, fn() { Some("aiken") }) == Some("aiken") +} + +test or_try_2() { + or_try(Some(42), fn() { fail }) == Some(42) +} + +/// Apply a function to the inner value of an [`Option`](#option) +/// +/// ```aiken +/// option.map(None, fn(n) { n * 2 }) == None +/// option.map(Some(14), fn(n) { n * 2 }) == Some(28) +/// ``` +pub fn map(self: Option, with: fn(a) -> result) -> Option { + when self is { + None -> None + Some(a) -> Some(with(a)) + } +} + +test map_1() { + map(None, fn(_) { Void }) == None +} + +test map_2() { + map(Some(14), fn(n) { n + 1 }) == Some(15) +} + +/// Combine two [`Option`](#option) together. +/// +/// ```aiken +/// type Foo { +/// Foo(Int, Int) +/// } +/// +/// option.map2(Some(14), Some(42), Foo) == Some(Foo(14, 42)) +/// option.map2(None, Some(42), Foo) == None +/// option.map2(Some(14), None, Foo) == None +/// ``` +pub fn map2( + opt_a: Option, + opt_b: Option, + with: fn(a, b) -> result, +) -> Option { + when opt_a is { + None -> None + Some(a) -> + when opt_b is { + None -> None + Some(b) -> Some(with(a, b)) + } + } +} + +test map2_1() { + map2(None, Some(42), fn(_, _) { 14 }) == None +} + +test map2_2() { + map2(Some(42), None, fn(_, _) { 14 }) == None +} + +test map2_3() { + map2(Some(14), Some(42), fn(a, b) { (a, b) }) == Some((14, 42)) +} + +/// Combine three [`Option`](#option) together. +/// +/// ```aiken +/// type Foo { +/// Foo(Int, Int, Int) +/// } +/// +/// option.map3(Some(14), Some(42), Some(1337), Foo) == Some(Foo(14, 42, 1337)) +/// option.map3(None, Some(42), Some(1337), Foo) == None +/// option.map3(Some(14), None, None, Foo) == None +/// ``` +pub fn map3( + opt_a: Option, + opt_b: Option, + opt_c: Option, + with: fn(a, b, c) -> result, +) -> Option { + when opt_a is { + None -> None + Some(a) -> + when opt_b is { + None -> None + Some(b) -> + when opt_c is { + None -> None + Some(c) -> Some(with(a, b, c)) + } + } + } +} + +test map3_1() { + map3(None, Some(42), None, fn(_, _, _) { 14 }) == None +} + +test map3_2() { + map3(Some(42), None, None, fn(_, _, _) { 14 }) == None +} + +test map3_3() { + map3(Some(14), Some(42), Some(1337), fn(a, b, c) { c - a + b }) == Some(1365) +} + +/// Chain together many computations that may fail. +/// +/// ```aiken +/// self +/// |> dict.get(policy_id) +/// |> option.and_then(dict.get(_, asset_name)) +/// |> option.or_else(0) +/// ``` +pub fn and_then( + self: Option, + then: fn(a) -> Option, +) -> Option { + when self is { + None -> None + Some(a) -> then(a) + } +} + +fn try_decrement(n: Int) -> Option { + if n > 0 { + Some(n - 1) + } else { + None + } +} + +test and_then_1() { + let result = + None + |> and_then(try_decrement) + result == None +} + +test and_then_2() { + let result = + Some(14) + |> and_then(try_decrement) + result == Some(13) +} + +test and_then_3() { + let result = + Some(0) + |> and_then(try_decrement) + result == None +} + +/// Converts from `Option>` to `Option`. +/// +/// ```aiken +/// option.flatten(Some(Some(42))) == Some(42) +/// option.flatten(Some(None)) == None +/// option.flatten(None) == None +/// ``` +/// +/// Flattening only removes one level of nesting at a time: +/// +/// ```aiken +/// flatten(Some(Some(Some(42)))) == Some(Some(42)) +/// Some(Some(Some(42))) |> flatten |> flatten == Some(42) +/// ``` +pub fn flatten(opt: Option>) -> Option { + when opt is { + Some(inner) -> inner + None -> None + } +} + +test flatten_1() { + let x: Option> = Some(Some(6)) + Some(6) == flatten(x) +} + +test flatten_2() { + let x: Option> = Some(None) + None == flatten(x) +} + +test flatten_3() { + let x: Option> = None + None == flatten(x) +} + +test flatten_4() { + let x: Option>> = Some(Some(Some(6))) + + let result = + x + |> flatten + |> flatten + + Some(6) == result +} + +/// Asserts whether an option is `Some`, irrespective of the value it contains. +pub fn is_some(self: Option) -> Bool { + when self is { + Some(_) -> True + _ -> False + } +} + +test is_some_1() { + is_some(Some(0)) == True +} + +test is_some_2() { + is_some(None) == False +} + +/// Asserts whether an option is `None`. +pub fn is_none(self: Option) -> Bool { + when self is { + Some(_) -> False + _ -> True + } +} + +test is_none_1() { + is_none(Some(0)) == False +} + +test is_none_2() { + is_none(None) == True +} diff --git a/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/pairs.ak b/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/pairs.ak new file mode 100644 index 00000000..61849dbd --- /dev/null +++ b/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/pairs.ak @@ -0,0 +1,589 @@ +//// A module for working with associative lists (a.k.a `Pairs`). +//// +//// While any function that works on `List` also work on `Pairs`, this module provides some extra helpers +//// that are specifically tailored to working with associative lists. Fundamentally, a `Pairs` is +//// a type-alias to `List>`. +//// +//// ### Important +//// +//// Unlike dictionnaries (a.k.a. `Dict`), associative lists make no assumption +//// about the ordering of elements within the list. As a result, lookup +//// functions do traverse the entire list when invoked. They are also not _sets_, +//// and thus allow for duplicate keys. This is reflected in the functions used +//// to interact with them. + +/// Remove a single key-value pair from the `Pairs`. If the key is not found, no changes are made. +/// Duplicate keys are not removed. Only the **first** key found is removed. +/// +/// ```aiken +/// alist.remove_first([], "a") == [] +/// alist.remove_first([Pair("a", 1)], "a") == [] +/// alist.remove_first([Pair("a", 1), Pair("b", 2)], "a") == [Pair("b", 2)] +/// alist.remove_first([Pair("a", 1), Pair("b", 2), Pair("a", 3)], "a") == [Pair("b", 2), Pair("a", 3)] +/// ``` +pub fn remove_first(self: Pairs, key k: key) -> Pairs { + when self is { + [] -> + [] + [Pair(k2, v2), ..rest] -> + if k == k2 { + rest + } else { + [Pair(k2, v2), ..remove_first(rest, k)] + } + } +} + +test remove_first_1() { + remove_first([], "a") == [] +} + +test remove_first_2() { + remove_first([Pair("a", 14)], "a") == [] +} + +test remove_first_3() { + let fixture = + [Pair("a", 14)] + remove_first(fixture, "b") == fixture +} + +test remove_first_4() { + let fixture = + [Pair("a", 1), Pair("b", 2), Pair("a", 3)] + remove_first(fixture, "a") == [Pair("b", 2), Pair("a", 3)] +} + +/// Remove a single key-value pair from the Pairs. If the key is not found, no changes are made. +/// Duplicate keys are not removed. Only the **last** key found is removed. +/// +/// ```aiken +/// alist.remove_last([], "a") == [] +/// alist.remove_last([Pair("a", 1)], "a") == [] +/// alist.remove_last([Pair("a", 1), Pair("b", 2)], "a") == [Pair("b", 2)] +/// alist.remove_last([Pair("a", 1), Pair("b", 2), Pair("a", 3)], "a") == [Pair("a", 1), Pair("b", 2)] +/// ``` +pub fn remove_last(self: Pairs, key k: key) -> Pairs { + when self is { + [] -> + [] + [Pair(k2, v2), ..rest] -> + if k == k2 { + let tail = remove_last(rest, k) + if tail == rest { + rest + } else { + [Pair(k2, v2), ..tail] + } + } else { + [Pair(k2, v2), ..remove_last(rest, k)] + } + } +} + +test remove_last_1() { + remove_last([], "a") == [] +} + +test remove_last_2() { + remove_last([Pair("a", 14)], "a") == [] +} + +test remove_last_3() { + let fixture = + [Pair("a", 14)] + remove_last(fixture, "b") == fixture +} + +test remove_last_4() { + let fixture = + [Pair("a", 1), Pair("b", 2), Pair("a", 3)] + remove_last(fixture, "a") == [Pair("a", 1), Pair("b", 2)] +} + +/// Remove all key-value pairs matching the key from the Pairs. If the key is not found, no changes are made. +/// +/// ```aiken +/// alist.remove_all([], "a") == [] +/// alist.remove_all([Pair("a", 1)], "a") == [] +/// alist.remove_all([Pair("a", 1), Pair("b", 2)], "a") == [Pair("b", 2)] +/// alist.remove_all([Pair("a", 1), Pair("b", 2), Pair("a", 3)], "a") == [Pair("b", 2)] +/// ``` +pub fn remove_all(self: Pairs, key k: key) -> Pairs { + when self is { + [] -> + [] + [Pair(k2, v2), ..rest] -> + if k == k2 { + remove_all(rest, k) + } else { + [Pair(k2, v2), ..remove_all(rest, k)] + } + } +} + +test remove_all_1() { + remove_all([], "a") == [] +} + +test remove_all_2() { + remove_all([Pair("a", 14)], "a") == [] +} + +test remove_all_3() { + let fixture = + [Pair("a", 14)] + remove_all(fixture, "b") == fixture +} + +test remove_all_4() { + let fixture = + [Pair("a", 1), Pair("b", 2), Pair("a", 3)] + remove_all(fixture, "a") == [Pair("b", 2)] +} + +/// Finds the first key in the alist associated with a given value, if any. +/// +/// ```aiken +/// alist.find_first([], 1) == None +/// alist.find_first([Pair("a", 1)], 1) == Some("a") +/// alist.find_first([Pair("a", 1), Pair("b", 2)], 1) == Some("a") +/// alist.find_first([Pair("a", 1), Pair("b", 2), Pair("c", 1)], 1) == Some("a") +/// ``` +pub fn find_first(self: Pairs, v: value) -> Option { + when self is { + [] -> None + [Pair(k2, v2), ..rest] -> + if v == v2 { + Some(k2) + } else { + find_first(rest, v) + } + } +} + +test find_first_1() { + find_first([], "a") == None +} + +test find_first_2() { + find_first([Pair("a", 14)], 14) == Some("a") +} + +test find_first_3() { + find_first([Pair("a", 14)], 42) == None +} + +test find_first_4() { + find_first([Pair("a", 14), Pair("b", 42), Pair("c", 14)], 14) == Some("a") +} + +/// Finds the last key in the alist associated with a given value, if any. +/// +/// ```aiken +/// alist.find_last([], 1) == None +/// alist.find_last([Pair("a", 1)], 1) == Some("a") +/// alist.find_last([Pair("a", 1), Pair("b", 2)], 1) == Some("a") +/// alist.find_last([Pair("a", 1), Pair("b", 2), Pair("c", 1)], 1) == Some("c") +/// ``` +pub fn find_last(self: Pairs, v: value) -> Option { + when self is { + [] -> None + [Pair(k2, v2), ..rest] -> + if v == v2 { + when find_last(rest, v) is { + None -> Some(k2) + some -> some + } + } else { + find_last(rest, v) + } + } +} + +test find_last_1() { + find_last([], "a") == None +} + +test find_last_2() { + find_last([Pair("a", 14)], 14) == Some("a") +} + +test find_last_3() { + find_last([Pair("a", 14)], 42) == None +} + +test find_last_4() { + find_last([Pair("a", 14), Pair("b", 42), Pair("c", 14)], 14) == Some("c") +} + +/// Finds all keys in the alist associated with a given value. +/// +/// ```aiken +/// alist.find_all([], 1) == [] +/// alist.find_all([Pair("a", 1)], 1) == ["a"] +/// alist.find_all([Pair("a", 1), Pair("b", 2)], 1) == ["a"] +/// alist.find_all([Pair("a", 1), Pair("b", 2), Pair("c", 1)], 1) == ["a", "c"] +/// ``` +pub fn find_all(self: Pairs, v: value) -> List { + when self is { + [] -> + [] + [Pair(k2, v2), ..rest] -> + if v == v2 { + [k2, ..find_all(rest, v)] + } else { + find_all(rest, v) + } + } +} + +test find_all_1() { + find_all([], "a") == [] +} + +test find_all_2() { + find_all([Pair("a", 14)], 14) == ["a"] +} + +test find_all_3() { + find_all([Pair("a", 14)], 42) == [] +} + +test find_all_4() { + find_all([Pair("a", 14), Pair("b", 42), Pair("c", 14)], 14) == ["a", "c"] +} + +/// Fold over the key-value pairs in a Pairs. The fold direction follows the +/// order of elements in the Pairs and is done from right-to-left. +/// +/// ```aiken +/// let fixture = [ +/// Pair(1, 100), +/// Pair(2, 200), +/// Pair(3, 300), +/// ] +/// +/// alist.foldr(fixture, 0, fn(k, v, result) { k * v + result }) == 1400 +/// ``` +pub fn foldr( + self: Pairs, + zero: result, + with: fn(key, value, result) -> result, +) -> result { + when self is { + [] -> zero + [Pair(k, v), ..rest] -> with(k, v, foldr(rest, zero, with)) + } +} + +test foldr_1() { + foldr([], 14, fn(_, _, _) { 42 }) == 14 +} + +test foldr_2() { + foldr( + [Pair("a", 42), Pair("b", 14)], + zero: 0, + with: fn(_, v, total) { v + total }, + ) == 56 +} + +test foldr_3() { + let fixture = + [Pair(1, 100), Pair(2, 200), Pair(3, 300)] + + foldr(fixture, 0, fn(k, v, result) { k * v + result }) == 1400 +} + +/// Fold over the key-value pairs in a alist. The fold direction follows keys +/// in ascending order and is done from left-to-right. +/// +/// ```aiken +/// let fixture = [ +/// Pair(1, 100), +/// Pair(2, 200), +/// Pair(3, 300), +/// ] +/// +/// alist.foldl(fixture, 0, fn(k, v, result) { k * v + result }) == 1400 +/// ``` +pub fn foldl( + self: Pairs, + zero: result, + with: fn(key, value, result) -> result, +) -> result { + when self is { + [] -> zero + [Pair(k, v), ..rest] -> foldl(rest, with(k, v, zero), with) + } +} + +test foldl_1() { + foldl([], 14, fn(_, _, _) { 42 }) == 14 +} + +test foldl_2() { + foldl( + [Pair("a", 42), Pair("b", 14)], + zero: 0, + with: fn(_, v, total) { v + total }, + ) == 56 +} + +/// Get the value in the alist by its key. +/// If multiple values with the same key exist, only the first one is returned. +/// +/// ```aiken +/// alist.get_first([], "a") == None +/// alist.get_first([Pair("a", 1)], "a") == Some(1) +/// alist.get_first([Pair("a", 1), Pair("b", 2)], "a") == Some(1) +/// alist.get_first([Pair("a", 1), Pair("b", 2), Pair("a", 3)], "a") == Some(1) +/// ``` +pub fn get_first(self: Pairs, key k: key) -> Option { + when self is { + [] -> None + [Pair(k2, v), ..rest] -> + if k == k2 { + Some(v) + } else { + get_first(rest, k) + } + } +} + +test get_first_1() { + get_first([], "a") == None +} + +test get_first_2() { + get_first([Pair("a", 1)], "a") == Some(1) +} + +test get_first_3() { + get_first([Pair("a", 1), Pair("b", 2)], "a") == Some(1) +} + +test get_first_4() { + get_first([Pair("a", 1), Pair("b", 2), Pair("a", 3)], "a") == Some(1) +} + +test get_first_5() { + get_first([Pair("a", 1), Pair("b", 2), Pair("c", 3)], "d") == None +} + +/// Get the value in the alist by its key. +/// If multiple values with the same key exist, only the last one is returned. +/// +/// ```aiken +/// alist.get_last([], "a") == None +/// alist.get_last([Pair("a", 1)], "a") == Some(1) +/// alist.get_last([Pair("a", 1), Pair("b", 2)], "a") == Some(1) +/// alist.get_last([Pair("a", 1), Pair("b", 2), Pair("a", 3)], "a") == Some(3) +/// ``` +pub fn get_last(self: Pairs, key k: key) -> Option { + when self is { + [] -> None + [Pair(k2, v), ..rest] -> + if k == k2 { + when get_last(rest, k) is { + None -> Some(v) + some -> some + } + } else { + get_last(rest, k) + } + } +} + +test get_last_1() { + get_last([], "a") == None +} + +test get_last_2() { + get_last([Pair("a", 1)], "a") == Some(1) +} + +test get_last_3() { + get_last([Pair("a", 1), Pair("b", 2)], "a") == Some(1) +} + +test get_last_4() { + get_last([Pair("a", 1), Pair("b", 2), Pair("a", 3)], "a") == Some(3) +} + +test get_last_5() { + get_last([Pair("a", 1), Pair("b", 2), Pair("c", 3)], "d") == None +} + +/// Get all values in the alist associated with a given key. +/// +/// ```aiken +/// alist.get_all([], "a") == [] +/// alist.get_all([Pair("a", 1)], "a") == [1] +/// alist.get_all([Pair("a", 1), Pair("b", 2)], "a") == [1] +/// alist.get_all([Pair("a", 1), Pair("b", 2), Pair("a", 3)], "a") == [1, 3] +/// ``` +pub fn get_all(self: Pairs, key k: key) -> List { + when self is { + [] -> + [] + [Pair(k2, v), ..rest] -> + if k == k2 { + [v, ..get_all(rest, k)] + } else { + get_all(rest, k) + } + } +} + +test get_all_1() { + get_all([], "a") == [] +} + +test get_all_2() { + get_all([Pair("a", 1)], "a") == [1] +} + +test get_all_3() { + get_all([Pair("a", 1), Pair("b", 2)], "a") == [1] +} + +test get_all_4() { + get_all([Pair("a", 1), Pair("b", 2), Pair("a", 3)], "a") == [1, 3] +} + +test get_all_5() { + get_all([Pair("a", 1), Pair("b", 2), Pair("c", 3)], "d") == [] +} + +/// Check if a key exists in the alist. +/// +/// ```aiken +/// alist.has_key([], "a") == False +/// alist.has_key([Pair("a", 1)], "a") == True +/// alist.has_key([Pair("a", 1), Pair("b", 2)], "a") == True +/// alist.has_key([Pair("a", 1), Pair("b", 2), Pair("a", 3)], "a") == True +/// ``` +pub fn has_key(self: Pairs, k: key) -> Bool { + when self is { + [] -> False + // || is lazy so this is fine + [Pair(k2, _), ..rest] -> k == k2 || has_key(rest, k) + } +} + +test has_key_1() { + !has_key([], "a") +} + +test has_key_2() { + has_key([Pair("a", 14)], "a") +} + +test has_key_3() { + !has_key([Pair("a", 14)], "b") +} + +test has_key_4() { + has_key([Pair("a", 14), Pair("b", 42)], "b") +} + +test has_key_5() { + has_key([Pair("a", 14), Pair("b", 42), Pair("a", 42)], "a") +} + +/// Extract all the keys present in a given `Pairs`. +/// +/// ```aiken +/// alist.keys([]) == [] +/// alist.keys([Pair("a", 1)]) == ["a"] +/// alist.keys([Pair("a", 1), Pair("b", 2)]) == ["a", "b"] +/// alist.keys([Pair("a", 1), Pair("b", 2), Pair("a", 3)]) == ["a", "b", "a"] +/// ``` +pub fn keys(self: Pairs) -> List { + when self is { + [] -> + [] + [Pair(k, _), ..rest] -> + [k, ..keys(rest)] + } +} + +test keys_1() { + keys([]) == [] +} + +test keys_2() { + keys([Pair("a", 0)]) == ["a"] +} + +test keys_3() { + keys([Pair("a", 0), Pair("b", 0)]) == ["a", "b"] +} + +/// Apply a function to all key-value pairs in a alist, replacing the values. +/// +/// ```aiken +/// let fixture = [Pair("a", 100), Pair("b", 200)] +/// +/// alist.map(fixture, fn(_k, v) { v * 2 }) == [Pair("a", 200), Pair("b", 400)] +/// ``` +pub fn map( + self: Pairs, + with: fn(key, value) -> result, +) -> Pairs { + when self is { + [] -> + [] + [Pair(k, v), ..rest] -> + [Pair(k, with(k, v)), ..map(rest, with)] + } +} + +test map_1() { + let fixture = + [Pair("a", 1), Pair("b", 2)] + + map(fixture, with: fn(k, _) { k }) == [Pair("a", "a"), Pair("b", "b")] +} + +test map_2() { + let fixture = + [Pair("a", 1), Pair("b", 2)] + + map(fixture, with: fn(_, v) { v + 1 }) == [Pair("a", 2), Pair("b", 3)] +} + +/// Extract all the values present in a given `Pairs`. +/// +/// ```aiken +/// alist.values([]) == [] +/// alist.values([Pair("a", 1)]) == [1] +/// alist.values([Pair("a", 1), Pair("b", 2)]) == [1, 2] +/// alist.values([Pair("a", 1), Pair("b", 2), Pair("a", 3)]) == [1, 2, 3] +/// ``` +pub fn values(self: Pairs) -> List { + when self is { + [] -> + [] + [Pair(_, v), ..rest] -> + [v, ..values(rest)] + } +} + +test values_1() { + values([]) == [] +} + +test values_2() { + values([Pair("a", 1)]) == [1] +} + +test values_3() { + values([Pair("a", 1), Pair("b", 2)]) == [1, 2] +} + +test values_4() { + values([Pair("a", 1), Pair("b", 2), Pair("a", 3)]) == [1, 2, 3] +} diff --git a/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/string.ak b/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/string.ak new file mode 100644 index 00000000..b9eee08c --- /dev/null +++ b/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/string.ak @@ -0,0 +1,134 @@ +use aiken/builtin.{ + append_bytearray, append_string, decode_utf8, encode_utf8, length_of_bytearray, +} +use aiken/cbor + +/// Combine two `String` together. +/// +/// ```aiken +/// string.concat(left: @"Hello", right: @", World!") == @"Hello, World!" +/// ``` +pub fn concat(left: String, right: String) -> String { + append_string(left, right) +} + +test concat_1() { + concat(@"", @"") == @"" +} + +test concat_2() { + concat(@"", @"foo") == concat(@"foo", @"") +} + +test concat_3() { + concat(left: @"Hello", right: @", World!") == @"Hello, World!" +} + +/// Convert a `ByteArray` into a `String` +/// +///
⚠️
WARNING
| This functions fails if the underlying `ByteArray` isn't UTF-8-encoded.
In particular, you cannot convert arbitrary hash digests using this function.
For converting arbitrary `ByteArray`s, use [bytearray.to_hex](/stdlib/aiken/bytearray.html#to_hex). +/// --- | --- +/// +/// ```aiken +/// string.from_bytearray("foo") == @"foo" +/// +/// string.from_bytearray(#"666f6f") == @"foo" +/// +/// string.from_bytearray(some_hash) -> fail +/// ``` +pub fn from_bytearray(bytes: ByteArray) -> String { + decode_utf8(bytes) +} + +test from_bytearray_1() { + from_bytearray(#[]) == @"" +} + +test from_bytearray_2() { + from_bytearray(#[65, 66, 67]) == @"ABC" +} + +test from_bytearray_3() { + from_bytearray("ABC") == @"ABC" +} + +/// Convert an `Int` to its `String` representation. +/// +/// ```aiken +/// string.from_int(42) == @"42" +/// ``` +pub fn from_int(n: Int) -> String { + cbor.diagnostic(n) +} + +test from_int_1() { + from_int(0) == @"0" +} + +test from_int_2() { + from_int(5) == @"5" +} + +test from_int_3() { + from_int(42) == @"42" +} + +test from_int_4() { + from_int(200) == @"200" +} + +/// Join a list of strings, separated by a given _delimiter_. +/// +/// ```aiken +/// string.join([], @"+") == @"" +/// string.join([@"a", @"b", @"c"], @",") == @"a,b,c" +/// ``` +pub fn join(list: List, delimiter: String) -> String { + do_join(list, encode_utf8(delimiter), #"") + |> decode_utf8 +} + +fn do_join(xs, delimiter, bytes) { + when xs is { + [] -> bytes + [x, ..rest] -> + do_join( + rest, + delimiter, + if length_of_bytearray(bytes) == 0 { + encode_utf8(x) + } else { + append_bytearray(bytes, append_bytearray(delimiter, encode_utf8(x))) + }, + ) + } +} + +test join_1() { + join([], @",") == @"" +} + +test join_2() { + join([@"a", @"b", @"c"], @",") == @"a,b,c" +} + +/// Convert a `String` into a `ByteArray` +/// +/// ```aiken +/// string.to_bytearray(@"foo") == "foo" +/// ``` +pub fn to_bytearray(self: String) -> ByteArray { + encode_utf8(self) +} + +test to_bytearray_1() { + to_bytearray(@"") == "" +} + +test to_bytearray_2() { + to_bytearray(@"ABC") == #[65, 66, 67] +} + +test to_bytearray_3() { + to_bytearray(@"ABC") == "ABC" +} diff --git a/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/time.ak b/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/time.ak new file mode 100644 index 00000000..f1818151 --- /dev/null +++ b/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/time.ak @@ -0,0 +1,3 @@ +/// A number of milliseconds since 00:00:00 UTC on 1 January 1970. +pub type PosixTime = + Int diff --git a/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/transaction.ak b/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/transaction.ak new file mode 100644 index 00000000..eb1572e9 --- /dev/null +++ b/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/transaction.ak @@ -0,0 +1,224 @@ +use aiken/builtin +use aiken/dict.{Dict} +use aiken/hash.{Blake2b_224, Blake2b_256, Hash, blake2b_256} +use aiken/interval.{Interval} +use aiken/list +use aiken/option +use aiken/time.{PosixTime} +use aiken/transaction/certificate.{Certificate} +use aiken/transaction/credential.{ + Address, Script, ScriptCredential, StakeCredential, VerificationKey, + VerificationKeyCredential, +} +use aiken/transaction/value.{MintedValue, PolicyId, Value} + +/// A context given to a script by the Cardano ledger when being executed. +/// +/// The context contains information about the entire transaction that contains +/// the script. The transaction may also contain other scripts; to distinguish +/// between multiple scripts, the `ScriptContext` also contains a `purpose` +/// which indicates which script (or, for what purpose) of the transaction is +/// being executed. +pub type ScriptContext { + transaction: Transaction, + purpose: ScriptPurpose, +} + +/// Characterizes the kind of script being executed. +pub type ScriptPurpose { + /// For scripts executed as minting/burning policies, to insert + /// or remove assets from circulation. It's parameterized by the identifier + /// of the associated policy. + Mint(PolicyId) + /// For scripts that are used as payment credentials for addresses in + /// transaction outputs. They govern the rule by which the output they + /// reference can be spent. + Spend(OutputReference) + /// For scripts that validate reward withdrawals from a reward account. + /// + /// The argument identifies the target reward account. + WithdrawFrom(StakeCredential) + /// Needed when delegating to a pool using stake credentials defined as a + /// Plutus script. This purpose is also triggered when de-registering such + /// stake credentials. + /// + /// It embeds the certificate that's being validated. + Publish(Certificate) +} + +/// A Cardano `Transaction`, as seen by Plutus scripts. +/// +/// Note that this is a representation of a transaction, and not the 1:1 +/// translation of the transaction as seen by the ledger. In particular, +/// Plutus scripts can't see inputs locked by bootstrap addresses, outputs +/// to bootstrap addresses or just transaction metadata. +pub type Transaction { + inputs: List, + reference_inputs: List, + outputs: List, + fee: Value, + mint: MintedValue, + certificates: List, + withdrawals: Pairs, + validity_range: ValidityRange, + extra_signatories: List>, + redeemers: Pairs, + datums: Dict, Data>, + id: TransactionId, +} + +/// A placeholder / empty `Transaction` to serve as a base in a transaction +/// builder. This is particularly useful for constructing test transactions. +/// +/// Every field is empty or null, and we have in particular: +/// +/// ```aiken +/// use aiken/transaction +/// +/// transaction.placeholder().id == TransactionId { +/// hash: #"0000000000000000000000000000000000000000000000000000000000000000", +/// } +/// +/// transaction.placeholder().validity_range == interval.everything() +/// ``` +pub fn placeholder() -> Transaction { + Transaction { + inputs: [], + reference_inputs: [], + outputs: [], + fee: value.zero(), + mint: value.zero() |> value.to_minted_value(), + certificates: [], + withdrawals: [], + validity_range: interval.everything(), + extra_signatories: [], + redeemers: [], + datums: dict.new(), + id: TransactionId { + hash: #"0000000000000000000000000000000000000000000000000000000000000000", + }, + } +} + +/// An interval of POSIX time, measured in number milliseconds since 1970-01-01T00:00:00Z. +pub type ValidityRange = + Interval + +/// A unique transaction identifier, as the hash of a transaction body. Note that the transaction id +/// isn't a direct hash of the `Transaction` as visible on-chain. Rather, they correspond to hash +/// digests of transaction body as they are serialized on the network. +pub type TransactionId { + hash: Hash, +} + +/// An `Input` made of an output reference and, the resolved value associated with that output. +pub type Input { + output_reference: OutputReference, + output: Output, +} + +/// An `OutputReference` is a unique reference to an output on-chain. The `output_index` +/// corresponds to the position in the output list of the transaction (identified by its id) +/// that produced that output +pub type OutputReference { + transaction_id: TransactionId, + output_index: Int, +} + +/// A transaction `Output`, with an address, a value and optional datums and script references. +pub type Output { + address: Address, + value: Value, + datum: Datum, + reference_script: Option>, +} + +/// An output `Datum`. +pub type Datum { + NoDatum + /// A datum referenced by its hash digest. + DatumHash(Hash) + /// A datum completely inlined in the output. + InlineDatum(Data) +} + +/// A type-alias for Redeemers, passed to scripts for validation. The `Data` is +/// opaque because it is user-defined and it is the script's responsibility to +/// parse it into its expected form. +pub type Redeemer = + Data + +/// Find an input by its [`OutputReference`](#OutputReference). This is typically used in +/// combination with the `Spend` [`ScriptPurpose`](#ScriptPurpose) to find a script's own +/// input. +/// +/// ```aiken +/// validator { +/// fn(datum, redeemer, ctx: ScriptContext) { +/// expect Spend(my_output_reference) = +/// ctx.purpose +/// +/// expect Some(input) = +/// ctx.transaction.inputs +/// |> transaction.find_input(my_output_reference) +/// } +/// } +/// ``` +pub fn find_input( + inputs: List, + output_reference: OutputReference, +) -> Option { + inputs + |> list.find(fn(input) { input.output_reference == output_reference }) +} + +/// Find a [`Datum`](#Datum) by its hash, if present. The function looks first for +/// datums in the witness set, and then for inline datums if it doesn't find any in +/// witnesses. +pub fn find_datum( + outputs: List, + datums: Dict, Data>, + datum_hash: Hash, +) -> Option { + datums + |> dict.get(datum_hash) + |> option.or_try( + fn() { + outputs + |> list.filter_map( + fn(output) { + when output.datum is { + InlineDatum(data) -> + if + blake2b_256(builtin.serialise_data(data)) == datum_hash{ + + Some(data) + } else { + None + } + _ -> None + } + }, + ) + |> list.head + }, + ) +} + +/// Find all outputs that are paying into the given script hash, if any. This is useful for +/// contracts running over multiple transactions. +pub fn find_script_outputs( + outputs: List, + script_hash: Hash, +) -> List { + outputs + |> list.filter( + fn(output) { + when output.address.payment_credential is { + ScriptCredential(addr_script_hash) -> + script_hash == addr_script_hash + VerificationKeyCredential(_) -> False + } + }, + ) +} diff --git a/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/transaction/certificate.ak b/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/transaction/certificate.ak new file mode 100644 index 00000000..a5d9c8e7 --- /dev/null +++ b/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/transaction/certificate.ak @@ -0,0 +1,15 @@ +use aiken/hash.{Blake2b_224, Hash} +use aiken/transaction/credential.{PoolId, StakeCredential, VerificationKey} + +/// An on-chain certificate attesting of some operation. Publishing +/// certificates / triggers different kind of rules; most of the time, +/// they require signatures from / specific keys. +pub type Certificate { + CredentialRegistration { delegator: StakeCredential } + CredentialDeregistration { delegator: StakeCredential } + CredentialDelegation { delegator: StakeCredential, delegatee: PoolId } + PoolRegistration { pool_id: PoolId, vrf: Hash } + PoolDeregistration { pool_id: PoolId, epoch: Int } + Governance + TreasuryMovement +} diff --git a/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/transaction/credential.ak b/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/transaction/credential.ak new file mode 100644 index 00000000..fa6327dd --- /dev/null +++ b/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/transaction/credential.ak @@ -0,0 +1,112 @@ +use aiken/builtin +use aiken/hash.{Blake2b_224, Hash} + +/// A general structure for representing an on-chain `Credential`. +/// +/// Credentials are always one of two kinds: a direct public/private key +/// pair, or a script (native or Plutus). +pub type Credential { + VerificationKeyCredential(Hash) + ScriptCredential(Hash) +} + +/// A Cardano `Address` typically holding one or two credential references. +/// +/// Note that legacy bootstrap addresses (a.k.a. 'Byron addresses') are +/// completely excluded from Plutus contexts. Thus, from an on-chain +/// perspective only exists addresses of type 00, 01, ..., 07 as detailed +/// in [CIP-0019 :: Shelley Addresses](https://github.com/cardano-foundation/CIPs/tree/master/CIP-0019/#shelley-addresses). +pub type Address { + payment_credential: PaymentCredential, + stake_credential: Option, +} + +/// Smart-constructor for an [Address](#Address) from a [verification key](#VerificationKey) hash. The resulting address has no delegation rights whatsoever. +pub fn from_verification_key(vk: Hash) -> Address { + Address { + payment_credential: VerificationKeyCredential(vk), + stake_credential: None, + } +} + +/// Smart-constructor for an [Address](#Address) from a [script](#Script) hash. The address has no delegation rights whatsoever. +pub fn from_script(script: Hash) -> Address { + Address { + payment_credential: ScriptCredential(script), + stake_credential: None, + } +} + +/// Set (or reset) the delegation part of an [Address](#Address) using a [verification key](#VerificationKey) hash. This is useful when combined with [`from_verification_key`](#from_verification_key) and/or [`from_script`](#from_script). +pub fn with_delegation_key( + self: Address, + vk: Hash, +) -> Address { + Address { + payment_credential: self.payment_credential, + stake_credential: Some(Inline(VerificationKeyCredential(vk))), + } +} + +/// Set (or reset) the delegation part of an [Address](#Address) using a [script](#Script) hash. This is useful when combined with [`from_verification_key`](#from_verification_key) and/or [`from_script`](#from_script). +pub fn with_delegation_script( + self: Address, + script: Hash, +) -> Address { + Address { + payment_credential: self.payment_credential, + stake_credential: Some(Inline(ScriptCredential(script))), + } +} + +/// Represent a type of object that can be represented either inline (by hash) +/// or via a reference (i.e. a pointer to an on-chain location). +/// +/// This is mainly use for capturing pointers to a stake credential +/// registration certificate in the case of so-called pointer addresses. +pub type Referenced
{ + Inline(a) + Pointer { slot_number: Int, transaction_index: Int, certificate_index: Int } +} + +pub type VerificationKey = + ByteArray + +pub type Script = + ByteArray + +pub type Signature = + ByteArray + +/// Verify an Ed25519 signature using the given verification key. +/// Returns `True` when the signature is valid. +pub fn verify_signature( + key: VerificationKey, + msg: ByteArray, + sig: Signature, +) -> Bool { + builtin.verify_ed25519_signature(key, msg, sig) +} + +/// A `StakeCredential` represents the delegation and rewards withdrawal conditions +/// associated with some stake address / account. +/// +/// A `StakeCredential` is either provided inline, or, by reference using an +/// on-chain pointer. +/// +/// Read more about pointers in [CIP-0019 :: Pointers](https://github.com/cardano-foundation/CIPs/tree/master/CIP-0019/#pointers). +pub type StakeCredential = + Referenced + +/// A 'PaymentCredential' represents the spending conditions associated with +/// some output. Hence, +/// +/// - a `VerificationKeyCredential` captures an output locked by a public/private key pair; +/// - and a `ScriptCredential` captures an output locked by a native or Plutus script. +/// +pub type PaymentCredential = + Credential + +/// A unique stake pool identifier, as a hash of its owner verification key. +pub type PoolId = + Hash diff --git a/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/transaction/value.ak b/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/transaction/value.ak new file mode 100644 index 00000000..eac22032 --- /dev/null +++ b/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/aiken-lang-stdlib/lib/aiken/transaction/value.ak @@ -0,0 +1,686 @@ +use aiken/dict.{Dict, from_ascending_pairs_with} +use aiken/hash.{Blake2b_224, Hash} +use aiken/list +use aiken/option +use aiken/transaction/credential.{Script} + +/// A type-alias for a `PolicyId`. A `PolicyId` is always 28-byte long +pub type PolicyId = + Hash + +/// Ada, the native currency, isn't associated with any `PolicyId` (it's not +/// possible to mint Ada!). +/// +/// By convention, it is an empty `ByteArray`. +pub const ada_policy_id = #"" + +/// A type-alias for 'AssetName`, which are free-form byte-arrays between +/// 0 and 32 bytes. +pub type AssetName = + ByteArray + +/// Ada, the native currency, isn't associated with any `AssetName` (it's not +/// possible to mint Ada!). +/// +/// By convention, it is an empty `ByteArray`. +pub const ada_asset_name = #"" + +/// A multi-asset output `Value`. Contains tokens indexed by [PolicyId](#PolicyId) and [AssetName](#AssetName). +/// +/// This type maintain some invariants by construction; in particular, a `Value` will never contain a +/// zero quantity of a particular token. +pub opaque type Value { + inner: Dict>, +} + +/// Construct an empty `Value` with nothing in it. +pub fn zero() -> Value { + Value { inner: dict.new() } +} + +/// Check is a `Value` is zero. That is, it has no assets and holds no Ada/Lovelace. +pub fn is_zero(self: Value) -> Bool { + self == zero() +} + +/// Construct a `Value` from an asset identifier (i.e. `PolicyId` + `AssetName`) +/// and a given quantity. +pub fn from_asset( + policy_id: PolicyId, + asset_name: AssetName, + quantity: Int, +) -> Value { + if quantity == 0 { + dict.new() + |> Value + } else { + let asset = + dict.new() + |> dict.insert(asset_name, quantity) + dict.new() + |> dict.insert(policy_id, asset) + |> Value + } +} + +/// Construct a `Value` from a lovelace quantity. +/// +/// Friendly reminder: 1 Ada = 1.000.000 Lovelace +pub fn from_lovelace(quantity: Int) -> Value { + from_asset(ada_policy_id, ada_asset_name, quantity) +} + +/// Get a `Value` excluding Ada. +pub fn without_lovelace(self: Value) -> Value { + dict.delete(self.inner, ada_policy_id) + |> Value +} + +test without_lovelace_1() { + let v = from_lovelace(1000000) + without_lovelace(v) == zero() +} + +test without_lovelace_2() { + let v = from_lovelace(1000000) + let v2 = from_lovelace(50000000) + without_lovelace(v) == without_lovelace(v2) +} + +test without_lovelace_3() { + let v = + from_asset(#"010203", #"040506", 100) + |> add(ada_policy_id, ada_asset_name, 100000000) + let v2 = from_asset(#"010203", #"040506", 100) + without_lovelace(v) == without_lovelace(v2) && without_lovelace(v) == v2 +} + +/// Negates quantities of all tokens (including Ada) in that `Value`. +/// +/// ``` +/// v1 +/// |> value.negate +/// |> value.merge(v1) +/// |> value.is_zero +/// // True +/// ``` +pub fn negate(self: Value) -> Value { + dict.map(self.inner, fn(_, a) { dict.map(a, fn(_, q) { 0 - q }) }) + |> Value +} + +/// Combine two `Value` together. +pub fn merge(left v0: Value, right v1: Value) -> Value { + Value( + dict.union_with( + v0.inner, + v1.inner, + fn(_, a0, a1) { + let result = + dict.union_with( + a0, + a1, + fn(_, q0, q1) { + let q = q0 + q1 + if q == 0 { + None + } else { + Some(q) + } + }, + ) + if dict.is_empty(result) { + None + } else { + Some(result) + } + }, + ), + ) +} + +test merge_1() { + let v1 = from_lovelace(1) + let v2 = from_lovelace(-1) + merge(v1, v2) == zero() +} + +test merge_2() { + let v1 = from_asset(#"00", #"", 1) + let v2 = from_asset(#"01", #"", 2) + let v3 = from_asset(#"02", #"", 3) + let v = + from_lovelace(42) + |> merge(v3) + |> merge(v1) + |> merge(v2) + + flatten(v) == [ + (#"", #"", 42), + (#"00", #"", 1), + (#"01", #"", 2), + (#"02", #"", 3), + ] +} + +test merge_3() { + let v1 = from_asset(#"00", #"", 1) + let v2 = from_asset(#"00", #"", -1) + let v3 = from_asset(#"01", #"", 1) + + let v = + zero() + |> merge(v1) + |> merge(v2) + |> merge(v3) + + flatten(v) == [(#"01", #"", 1)] +} + +test merge_4() { + let v1 = from_asset(#"00", #"", 1) + let v2 = from_asset(#"00", #"", -1) + + merge(v1, v2) == zero() +} + +test merge_5() { + let v = + zero() + |> add(#"acab", #"beef", 0) + + merge(zero(), v) == zero() +} + +/// Add a (positive or negative) quantity of a single token to a value. +/// This is more efficient than [`merge`](#merge) for a single asset. +pub fn add( + self: Value, + policy_id: PolicyId, + asset_name: AssetName, + quantity: Int, +) -> Value { + if quantity == 0 { + self + } else { + let helper = + fn(_, left, _right) { + let inner_result = + dict.insert_with( + left, + asset_name, + quantity, + fn(_k, ql, qr) { + let q = ql + qr + if q == 0 { + None + } else { + Some(q) + } + }, + ) + if dict.is_empty(inner_result) { + None + } else { + Some(inner_result) + } + } + + Value( + dict.insert_with( + self.inner, + policy_id, + dict.from_ascending_pairs([Pair(asset_name, quantity)]), + helper, + ), + ) + } +} + +test add_1() { + let v = + zero() + |> add(#"acab", #"beef", 321) + |> add(#"acab", #"beef", -321) + v == zero() +} + +test add_2() { + let v = + from_lovelace(123) + |> add(#"acab", #"beef", 321) + |> add(#"acab", #"beef", -1 * 321) + v == from_lovelace(123) +} + +test add_3() { + let v = + from_lovelace(1) + |> add(ada_policy_id, ada_asset_name, 2) + |> add(ada_policy_id, ada_asset_name, 3) + v == from_lovelace(6) +} + +test add_4() { + let v = + zero() + |> add(#"acab", #"beef", 0) + v == zero() +} + +test add_5() { + let v = + zero() + |> add(#"acab", #"beef", 0) + |> add(#"acab", #"beef", 0) + v == zero() +} + +/// Extract the quantity of a given asset. +pub fn quantity_of( + self: Value, + policy_id: PolicyId, + asset_name: AssetName, +) -> Int { + self.inner + |> dict.get(policy_id) + |> option.and_then(dict.get(_, asset_name)) + |> option.or_else(0) +} + +/// A specialized version of `quantity_of` for the Ada currency. +pub fn lovelace_of(self: Value) -> Int { + quantity_of(self, ada_policy_id, ada_asset_name) +} + +/// Get all tokens associated with a given policy. +pub fn tokens(self: Value, policy_id: PolicyId) -> Dict { + self.inner + |> dict.get(policy_id) + |> option.or_else(dict.new()) +} + +/// A list of all token policies in that Value with non-zero tokens. +pub fn policies(self: Value) -> List { + dict.keys(self.inner) +} + +/// Flatten a value as list of 3-tuple (PolicyId, AssetName, Quantity). +/// +/// Handy to manipulate values as uniform lists. +pub fn flatten(self: Value) -> List<(PolicyId, AssetName, Int)> { + dict.foldr( + self.inner, + [], + fn(policy_id, asset_list, value) { + dict.foldr( + asset_list, + value, + fn(asset_name, quantity, xs) { + [(policy_id, asset_name, quantity), ..xs] + }, + ) + }, + ) +} + +/// Flatten a value as a list of results, possibly discarding some along the way. +/// +/// When the transform function returns `None`, the result is discarded altogether. +pub fn flatten_with( + self: Value, + with: fn(PolicyId, AssetName, Int) -> Option, +) -> List { + dict.foldr( + self.inner, + [], + fn(policy_id, asset_list, value) { + dict.foldr( + asset_list, + value, + fn(asset_name, quantity, xs) { + when with(policy_id, asset_name, quantity) is { + None -> xs + Some(x) -> + [x, ..xs] + } + }, + ) + }, + ) +} + +test flatten_with_1() { + flatten_with(zero(), fn(p, a, q) { Some((p, a, q)) }) == [] +} + +test flatten_with_2() { + let v = + zero() + |> add("a", "1", 14) + |> add("b", "", 42) + |> add("a", "2", 42) + + flatten_with( + v, + fn(p, a, q) { + if q == 42 { + Some((p, a)) + } else { + None + } + }, + ) == [("a", "2"), ("b", "")] +} + +/// Reduce a value into a single result +/// +/// ``` +/// value.zero() +/// |> value.add("a", "1", 10) +/// |> value.add("b", "2", 20) +/// |> value.reduce(v, 0, fn(_, _, quantity, acc) { acc + quantity }) +/// // 30 +/// ``` +pub fn reduce( + self: Value, + start: acc, + with: fn(PolicyId, AssetName, Int, acc) -> acc, +) -> acc { + dict.foldr( + self.inner, + start, + fn(policy_id, asset_list, result) { + dict.foldr(asset_list, result, with(policy_id, _, _, _)) + }, + ) +} + +test reduce_1() { + let v = + zero() + |> add("a", "1", 10) + |> add("b", "2", 20) + let result = reduce(v, 0, fn(_, _, quantity, acc) { acc + quantity }) + result == 30 +} + +test reduce_2() { + let v = + zero() + |> add("a", "1", 5) + |> add("a", "2", 15) + |> add("b", "", 10) + let result = + reduce( + v, + [], + fn(policy_id, asset_name, _, acc) { [(policy_id, asset_name), ..acc] }, + ) + result == [("a", "1"), ("a", "2"), ("b", "")] +} + +test reduce_3() { + let v = zero() + let result = reduce(v, 1, fn(_, _, quantity, acc) { acc + quantity }) + result == 1 +} + +/// Promote an arbitrary list of assets into a `Value`. This function fails +/// (i.e. halt the program execution) if: +/// +/// - there's any duplicate amongst `PolicyId`; +/// - there's any duplicate amongst `AssetName`; +/// - the `AssetName` aren't sorted in ascending lexicographic order; or +/// - any asset quantity is null. +/// +/// This function is meant to turn arbitrary user-defined `Data` into safe `Value`, +/// while checking for internal invariants. +pub fn from_asset_list(xs: Pairs>) -> Value { + xs + |> list.foldr( + dict.new(), + fn(inner, acc) { + expect Pair(p, [_, ..] as x) = inner + x + |> from_ascending_pairs_with(fn(v) { v != 0 }) + |> dict.insert_with( + acc, + p, + _, + fn(_, _, _) { + fail @"Duplicate policy in the asset list." + }, + ) + }, + ) + |> Value +} + +test from_asset_list_1() { + let v = from_asset_list([]) + v == zero() +} + +test from_asset_list_2() fail { + let v = from_asset_list([Pair(#"33", [])]) + v == zero() +} + +test from_asset_list_3() fail { + let v = from_asset_list([Pair(#"33", [Pair(#"", 0)])]) + v != zero() +} + +test from_asset_list_4() { + let v = from_asset_list([Pair(#"33", [Pair(#"", 1)])]) + flatten(v) == [(#"33", #"", 1)] +} + +test from_asset_list_5() { + let v = from_asset_list([Pair(#"33", [Pair(#"", 1), Pair(#"33", 1)])]) + flatten(v) == [(#"33", #"", 1), (#"33", #"33", 1)] +} + +test from_asset_list_6() fail { + let v = + from_asset_list( + [ + Pair(#"33", [Pair(#"", 1), Pair(#"33", 1)]), + Pair(#"33", [Pair(#"", 1), Pair(#"33", 1)]), + ], + ) + v != zero() +} + +test from_asset_list_7() fail { + let v = + from_asset_list( + [ + Pair(#"33", [Pair(#"", 1), Pair(#"33", 1)]), + Pair(#"34", [Pair(#"", 1), Pair(#"", 1)]), + ], + ) + v != zero() +} + +test from_asset_list_8() { + let v = + from_asset_list( + [ + Pair(#"33", [Pair(#"", 1), Pair(#"33", 1)]), + Pair(#"34", [Pair(#"31", 1)]), + Pair(#"35", [Pair(#"", 1)]), + ], + ) + flatten(v) == [ + (#"33", #"", 1), + (#"33", #"33", 1), + (#"34", #"31", 1), + (#"35", #"", 1), + ] +} + +test from_asset_list_9() { + let v = + from_asset_list( + [ + Pair(#"35", [Pair(#"", 1)]), + Pair(#"33", [Pair(#"", 1), Pair(#"33", 1)]), + Pair(#"34", [Pair(#"31", 1)]), + ], + ) + flatten(v) == [ + (#"33", #"", 1), + (#"33", #"33", 1), + (#"34", #"31", 1), + (#"35", #"", 1), + ] +} + +/// Convert the value into a dictionary of dictionaries. +pub fn to_dict(self: Value) -> Dict> { + self.inner +} + +/// A multi-asset value that can be found when minting transaction. It always holds +/// a null quantity of _Ada_. Note that because of historical reasons, this is slightly +/// different from `Value` found in transaction outputs. +/// +/// Note that you're never expected to construct a `MintedValue` yourself. If you need to +/// manipulate multi-asset values, use [Value](#Value) +/// +/// See also [`from_minted_value`](#from_minted_value). +pub opaque type MintedValue { + inner: Dict>, +} + +/// Convert minted value into a dictionary of dictionaries. +pub fn minted_to_dict(self: MintedValue) -> Dict> { + self.inner +} + +/// Convert a [`MintedValue`](#MintedValue) into a [`Value`](#Value). +pub fn from_minted_value(self: MintedValue) -> Value { + self.inner |> dict.delete(ada_policy_id) |> Value +} + +test from_minted_value_1() { + flatten(from_minted_value(from_internal_list([]))) == [] +} + +test from_minted_value_2() { + flatten(from_minted_value(from_internal_list([("p0", "a0", 1)]))) == [ + ("p0", "a0", 1), + ] +} + +test from_minted_value_3() { + let assets = + [("p0", "a0", 1), ("p1", "a0", 1), ("p0", "a0", 1), ("p1", "a1", 1)] + + let result = + [("p0", "a0", 2), ("p1", "a0", 1), ("p1", "a1", 1)] + + flatten(from_minted_value(from_internal_list(assets))) == result +} + +test from_minted_value_4() { + let assets = + [ + ("", "", 0), + ("p0", "a0", 1), + ("p1", "a0", 1), + ("p0", "a0", 1), + ("p1", "a1", 1), + ] + + let result = + [("p0", "a0", 2), ("p1", "a0", 1), ("p1", "a1", 1)] + + flatten(from_minted_value(from_internal_list(assets))) == result +} + +test from_minted_value_5() { + let assets = + [ + ("p0", "a0", 1), + ("p0", "a1", 1), + ("p1", "a0", 1), + ("p1", "a1", 1), + ("p1", "a2", 1), + ("p2", "a0", 1), + ("p2", "a1", 1), + ("p3", "a0", 1), + ("p3", "a1", 1), + ("p3", "a2", 1), + ("p3", "a3", 1), + ("p3", "a4", 1), + ("p3", "a5", 1), + ("p3", "a6", 1), + ("p3", "a7", 1), + ] + + flatten(from_minted_value(from_internal_list(assets))) == assets +} + +/// Convert a [`Value`](#Value) into a [`MintedValue`](#MintedValue). +pub fn to_minted_value(self: Value) -> MintedValue { + self.inner + |> dict.insert(ada_policy_id, dict.insert(dict.new(), ada_asset_name, 0)) + |> MintedValue +} + +test to_minted_value_1() { + let minted_value = to_minted_value(zero()) + ( minted_value.inner |> dict.to_pairs |> list.length ) == 1 +} + +test to_minted_value_2() { + let minted_value = to_minted_value(from_lovelace(42)) + ( + minted_value.inner + |> dict.get(ada_policy_id) + |> option.and_then(dict.get(_, ada_asset_name)) + ) == Some(0) +} + +/// Convert a list of tokens into a `MintedValue`. +/// +/// NOTE: Not exposed because we do not want people to construct `MintedValue`. Only +/// get them from the script context. +fn from_internal_list(xs: List<(PolicyId, AssetName, Int)>) -> MintedValue { + list.foldr( + xs, + MintedValue(dict.new()), + fn(elem, st) { + let (policy_id, asset_name, quantity) = elem + unchecked_add(st, policy_id, asset_name, quantity) + }, + ) +} + +fn unchecked_add( + self: MintedValue, + policy_id: PolicyId, + asset_name: AssetName, + quantity: Int, +) -> MintedValue { + MintedValue( + dict.insert_with( + self.inner, + policy_id, + dict.from_ascending_pairs([Pair(asset_name, quantity)]), + fn(_, left, _right) { + Some( + dict.insert_with( + left, + asset_name, + quantity, + fn(_k, ql, qr) { Some(ql + qr) }, + ), + ) + }, + ), + ) +} diff --git a/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/packages.toml b/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/packages.toml new file mode 100644 index 00000000..565eeeb8 --- /dev/null +++ b/example-web/aiken/build/packages/sidan-lab-vodka/build/packages/packages.toml @@ -0,0 +1,4 @@ +[[packages]] +name = "aiken-lang/stdlib" +version = "1.9.0" +source = "github" diff --git a/example-web/aiken/build/packages/sidan-lab-vodka/lib/mocktail.ak b/example-web/aiken/build/packages/sidan-lab-vodka/lib/mocktail.ak new file mode 100644 index 00000000..f93d7df3 --- /dev/null +++ b/example-web/aiken/build/packages/sidan-lab-vodka/lib/mocktail.ak @@ -0,0 +1,476 @@ +use aiken/cbor +use aiken/dict +use aiken/hash.{Blake2b_224, Hash, blake2b_256} +use aiken/interval.{Finite, Interval} +use aiken/list +use aiken/time.{PosixTime} +use aiken/transaction.{ + InlineDatum, Input, Output, OutputReference, Redeemer, ScriptPurpose, + Transaction, TransactionId, placeholder, +} +use aiken/transaction/certificate.{Certificate} +use aiken/transaction/credential.{ + Address, Inline, ScriptCredential, StakeCredential, VerificationKey, +} +use aiken/transaction/value.{ + Value, from_asset, from_lovelace, from_minted_value, merge, to_minted_value, + zero, +} +use mocktail/virgin_address.{mock_script_address} +use mocktail/virgin_output_reference.{mock_utxo_ref} +use mocktail/virgin_outputs.{mock_pub_key_output, mock_script_output} +use mocktail/virgin_validity_range.{mock_interval} + +pub type MocktailTx { + tx: Transaction, + queue_input: Option, + queue_output: Option, + queue_ref_input: Option, +} + +pub fn mocktail_tx() -> MocktailTx { + MocktailTx { + tx: placeholder(), + queue_input: None, + queue_output: None, + queue_ref_input: None, + } +} + +pub fn tx_in( + mocktail_tx: MocktailTx, + condition: Bool, + tx_hash: ByteArray, + tx_index: Int, + amount: Value, + address: Address, +) -> MocktailTx { + if !condition { + mocktail_tx + } else { + let queue_input = + Some( + Input { + output_reference: OutputReference { + transaction_id: TransactionId(tx_hash), + output_index: tx_index, + }, + output: mock_pub_key_output(address, amount), + }, + ) + when mocktail_tx.queue_input is { + Some(input) -> + MocktailTx { + ..mocktail_tx, + queue_input: queue_input, + tx: mocktail_tx.tx |> add_input(True, input), + } + None -> MocktailTx { ..mocktail_tx, queue_input: queue_input } + } + } +} + +pub fn tx_in_inline_datum( + mocktail_tx: MocktailTx, + condition: Bool, + datum: Data, +) -> MocktailTx { + if !condition { + mocktail_tx + } else { + let queue_input = + when mocktail_tx.queue_input is { + Some(input) -> { + let Input { output_reference, output } = input + Some( + Input { + output_reference, + output: Output { ..output, datum: InlineDatum(datum) }, + }, + ) + } + None -> + Some( + Input { + output_reference: mock_utxo_ref(0, 0), + output: mock_script_output( + mock_script_address(0, None), + zero(), + InlineDatum(datum), + ), + }, + ) + } + MocktailTx { ..mocktail_tx, queue_input: queue_input } + } +} + +pub fn tx_out( + mocktail_tx: MocktailTx, + condition: Bool, + address: Address, + amount: Value, +) -> MocktailTx { + if !condition { + mocktail_tx + } else { + let queue_output = Some(mock_pub_key_output(address, amount)) + when mocktail_tx.queue_output is { + Some(output) -> + MocktailTx { + ..mocktail_tx, + queue_output: queue_output, + tx: mocktail_tx.tx |> add_output(True, output), + } + None -> MocktailTx { ..mocktail_tx, queue_output: queue_output } + } + } +} + +pub fn tx_out_inline_datum( + mocktail_tx: MocktailTx, + condition: Bool, + datum: Data, +) -> MocktailTx { + if !condition { + mocktail_tx + } else { + let queue_output = + when mocktail_tx.queue_output is { + Some(output) -> Some(Output { ..output, datum: InlineDatum(datum) }) + None -> + Some( + mock_script_output( + mock_script_address(0, None), + zero(), + InlineDatum(datum), + ), + ) + } + MocktailTx { ..mocktail_tx, queue_output: queue_output } + } +} + +pub fn mint( + mocktail_tx: MocktailTx, + condition: Bool, + quantity: Int, + policy_id: ByteArray, + token_name: ByteArray, +) -> MocktailTx { + if !condition { + mocktail_tx + } else { + MocktailTx { + ..mocktail_tx, + tx: mocktail_tx.tx + |> add_mint(True, from_asset(policy_id, token_name, quantity)), + } + } +} + +pub fn ref_tx_in( + mocktail_tx: MocktailTx, + condition: Bool, + tx_hash: ByteArray, + tx_index: Int, + amount: Value, + address: Address, +) -> MocktailTx { + if !condition { + mocktail_tx + } else { + let queue_ref_input = + Some( + Input { + output_reference: OutputReference { + transaction_id: TransactionId(tx_hash), + output_index: tx_index, + }, + output: mock_pub_key_output(address, amount), + }, + ) + when mocktail_tx.queue_ref_input is { + Some(input) -> + MocktailTx { + ..mocktail_tx, + queue_ref_input: queue_ref_input, + tx: mocktail_tx.tx |> add_reference_input(True, input), + } + None -> MocktailTx { ..mocktail_tx, queue_ref_input: queue_ref_input } + } + } +} + +pub fn ref_tx_in_inline_datum( + mocktail_tx: MocktailTx, + condition: Bool, + datum: Data, +) -> MocktailTx { + if !condition { + mocktail_tx + } else { + let queue_ref_input = + when mocktail_tx.queue_ref_input is { + Some(input) -> { + let Input { output_reference, output } = input + Some( + Input { + output_reference, + output: Output { ..output, datum: InlineDatum(datum) }, + }, + ) + } + None -> + Some( + Input { + output_reference: mock_utxo_ref(0, 0), + output: mock_script_output( + mock_script_address(0, None), + zero(), + InlineDatum(datum), + ), + }, + ) + } + MocktailTx { ..mocktail_tx, queue_ref_input: queue_ref_input } + } +} + +pub fn invalid_before( + mocktail_tx: MocktailTx, + condition: Bool, + time: PosixTime, +) -> MocktailTx { + if !condition { + mocktail_tx + } else { + let tx = mocktail_tx.tx + let upper_bound = + when tx.validity_range.upper_bound.bound_type is { + Finite(x) -> Some(x) + _ -> None + } + MocktailTx { + ..mocktail_tx, + tx: Transaction { + ..tx, + validity_range: mock_interval(Some(time), upper_bound), + }, + } + } +} + +pub fn invalid_hereafter( + mocktail_tx: MocktailTx, + condition: Bool, + time: PosixTime, +) -> MocktailTx { + if !condition { + mocktail_tx + } else { + let tx = mocktail_tx.tx + let lower_bound = + when tx.validity_range.lower_bound.bound_type is { + Finite(x) -> Some(x) + _ -> None + } + MocktailTx { + ..mocktail_tx, + tx: Transaction { + ..tx, + validity_range: mock_interval(lower_bound, Some(time)), + }, + } + } +} + +pub fn required_signer_hash( + mocktail_tx: MocktailTx, + condition: Bool, + key: ByteArray, +) -> MocktailTx { + if !condition { + mocktail_tx + } else { + MocktailTx { + ..mocktail_tx, + tx: mocktail_tx.tx |> add_extra_signatory(True, key), + } + } +} + +pub fn script_withdrawal( + mocktail_tx: MocktailTx, + condition: Bool, + script_hash: ByteArray, + withdrawal_amount: Int, +) -> MocktailTx { + if !condition { + mocktail_tx + } else { + MocktailTx { + ..mocktail_tx, + tx: mocktail_tx.tx + |> add_withdrawal( + True, + Pair(Inline(ScriptCredential(script_hash)), withdrawal_amount), + ), + } + } +} + +pub fn complete(mocktail_tx: MocktailTx) -> Transaction { + let tx = mocktail_tx.tx + let tx = + when mocktail_tx.queue_input is { + Some(input) -> tx |> add_input(True, input) + None -> tx + } + let tx = + when mocktail_tx.queue_output is { + Some(output) -> tx |> add_output(True, output) + None -> tx + } + let tx = + when mocktail_tx.queue_ref_input is { + Some(input) -> tx |> add_reference_input(True, input) + None -> tx + } + tx +} + +pub fn add_input(tx: Transaction, condition: Bool, input: Input) -> Transaction { + if !condition { + tx + } else { + Transaction { ..tx, inputs: tx.inputs |> list.push(input) } + } +} + +pub fn add_reference_input( + tx: Transaction, + condition: Bool, + input: Input, +) -> Transaction { + if !condition { + tx + } else { + Transaction { + ..tx, + reference_inputs: tx.reference_inputs |> list.push(input), + } + } +} + +pub fn add_output( + tx: Transaction, + condition: Bool, + output: Output, +) -> Transaction { + if !condition { + tx + } else { + Transaction { ..tx, outputs: tx.outputs |> list.push(output) } + } +} + +pub fn set_fee( + tx: Transaction, + condition: Bool, + lovelace_fee: Int, +) -> Transaction { + if !condition { + tx + } else { + Transaction { ..tx, fee: from_lovelace(lovelace_fee) } + } +} + +pub fn add_mint(tx: Transaction, condition: Bool, mint: Value) -> Transaction { + if !condition { + tx + } else { + Transaction { + ..tx, + mint: from_minted_value(tx.mint) + |> merge(mint) + |> to_minted_value(), + } + } +} + +pub fn add_certificate( + tx: Transaction, + condition: Bool, + certificate: Certificate, +) -> Transaction { + if !condition { + tx + } else { + Transaction { + ..tx, + certificates: tx.certificates |> list.push(certificate), + } + } +} + +pub fn add_withdrawal( + tx: Transaction, + condition: Bool, + withdrawal: Pair, +) -> Transaction { + if !condition { + tx + } else { + Transaction { ..tx, withdrawals: tx.withdrawals |> list.push(withdrawal) } + } +} + +pub fn add_extra_signatory( + tx: Transaction, + condition: Bool, + signatory: Hash, +) -> Transaction { + if !condition { + tx + } else { + Transaction { + ..tx, + extra_signatories: tx.extra_signatories |> list.push(signatory), + } + } +} + +pub fn add_redeemer( + tx: Transaction, + condition: Bool, + redeemer: Pair, +) -> Transaction { + if !condition { + tx + } else { + Transaction { ..tx, redeemers: tx.redeemers |> list.push(redeemer) } + } +} + +pub fn add_datum(tx: Transaction, condition: Bool, datum: Data) -> Transaction { + if !condition { + tx + } else { + let datum_hash = blake2b_256(cbor.serialise(datum)) + Transaction { ..tx, datums: tx.datums |> dict.insert(datum_hash, datum) } + } +} + +pub fn set_transaction_id( + tx: Transaction, + condition: Bool, + transaction_id: TransactionId, +) -> Transaction { + if !condition { + tx + } else { + Transaction { ..tx, id: transaction_id } + } +} diff --git a/example-web/aiken/build/packages/sidan-lab-vodka/lib/mocktail/virgin_address.ak b/example-web/aiken/build/packages/sidan-lab-vodka/lib/mocktail/virgin_address.ak new file mode 100644 index 00000000..2aadb76f --- /dev/null +++ b/example-web/aiken/build/packages/sidan-lab-vodka/lib/mocktail/virgin_address.ak @@ -0,0 +1,43 @@ +use aiken/transaction/credential.{ + Address, Credential, ScriptCredential, StakeCredential, + VerificationKeyCredential, +} +use mocktail/virgin_key_hex.{mock_pub_key_hash, mock_script_hash} + +/// Mock a pub key credential +/// `variation` same the same index as `mock_pub_key_hash` +pub fn mock_verfication_key_credential(variation: Int) -> Credential { + VerificationKeyCredential(mock_pub_key_hash(variation)) +} + +/// Mock a pub key address +/// `variation` same the same index as `mock_pub_key_hash` +/// `stake_credential` is optional +pub fn mock_pub_key_address( + variation: Int, + stake_credential: Option, +) -> Address { + Address { + payment_credential: mock_verfication_key_credential(variation), + stake_credential, + } +} + +/// Mock a script credential +/// `variation` same the same index as `mock_script_hash` +pub fn mock_script_credential(variation: Int) -> Credential { + ScriptCredential(mock_script_hash(variation)) +} + +/// Mock a script address +/// `variation` same the same index as `mock_script_hash` +/// `stake_credential` is optional +pub fn mock_script_address( + variation: Int, + stake_credential: Option, +) -> Address { + Address { + payment_credential: mock_script_credential(variation), + stake_credential, + } +} diff --git a/example-web/aiken/build/packages/sidan-lab-vodka/lib/mocktail/virgin_key_hex.ak b/example-web/aiken/build/packages/sidan-lab-vodka/lib/mocktail/virgin_key_hex.ak new file mode 100644 index 00000000..9910eef6 --- /dev/null +++ b/example-web/aiken/build/packages/sidan-lab-vodka/lib/mocktail/virgin_key_hex.ak @@ -0,0 +1,160 @@ +use aiken/hash.{Blake2b_224, Hash} +use aiken/transaction/credential.{Script, VerificationKey} +use aiken/transaction/value.{PolicyId} + +/// Mock a key in hexadecimal format +pub fn mock_key_hash(variation: Int) -> ByteArray { + when variation is { + 1 -> #"a2c20c77887ace1cd986193e4e75babd8993cfd56995cd5cfce609c2" + 2 -> #"e2cfca46ecdf7feb8a3a7f957ead86b88c156f3fdd9607bcb44eb8f5" + 3 -> #"70ce90786897b6f7b6430a57ae005ecaf7c8cab817dd7510598403a9" + 4 -> #"efa5288b2b13cff1db555eed5debc54f3b25a62b7321db37f8af9213" + 5 -> #"daa3d211db0e53d6088261552daeaa75f268b906f769d4eb04ead45d" + 11 -> #"e1491bc9d47bfe2117f792fa8c3389004d693b6b47e4c3facca1d146" + 12 -> #"2b77d1effa4dfb7f5913d176c88ae0e168b9e9c528bfc9b07784be1b" + 13 -> #"81f0f06d69c2422003d69113f497d78d3e0b42d7c1fb3fb5d6905ee2" + 14 -> #"81f0f06d69c2422003d69113f497d78d3e0b42d7c1fb3fb5d6905ee2" + 15 -> #"ecd2aeb53027de8b37ef622ba6a9b3a006c591301c7f6507ebb4e9b5" + 21 -> #"789cdcce60d344b947f28fa4c06ab6107c1a59d56bb35d7156951803" + 22 -> #"4d8a10609828b2b4403ba80e8559788f8c8433a8b007ebd70157c635" + 23 -> #"257c00a00c73e2ec2c113f877c742e79b0b6c6000b75e8f3ec92b0ab" + 24 -> #"672d5128420c19282d26e247aebc78669f896cff10173c1e23d2172e" + 25 -> #"bff30ac664233c97947b4e2f115320ac2c928bcc14cb85057679541e" + 26 -> #"80abf14bcde1d2237619057f40866b8de241b7264b5d6b4aa9f97ff9" + 27 -> #"8c4a9da730001048fb518a63696604c9b59e5433bb4f6332f8f2dd89" + 28 -> #"8ee022b68cfeb287c79966369301d3d01b1c4816e229509e7e1a6f61" + 29 -> #"6d628bcef102b61887dc389d962c401e724968fa2cdea5c4b4aeaa84" + 31 -> #"919cd6bf4bc94aa1fc008fe5776520a77dd9b0909872c0582792700a" + 32 -> #"ae3bdcce80699ab8a30f1583aed85c8bdf4c373f0d7441c2dcfd956b" + 33 -> #"c5dd55f31863ac8a72949de55810a55683c7ca3e1580740fd55dca0e" + 34 -> #"7d76f861a4d4f5fbf9e8760e95338d234b1e901e7c24586c67f6523a" + 35 -> #"586184843b60fd3941d01fd7b839c7712ebbaa045becefaeb9736e64" + 41 -> #"228fe3861b884a1148488811d56a35bfa834eb6c06c3797d7070aba7" + 42 -> #"df452e58fa35810e1a002f1e0e5beb07d7b7ef002feb3a124b32b00d" + 43 -> #"c031a6379982c1d48f4339993ceabc35a165ddaa00b6dc2e4b19b742" + 44 -> #"4bc61c9433f2c8b78022e57be0af60beb8242b53514edda19ddcd2a4" + 45 -> #"7736651b84aeba0f2f152b496767c4c6a7377702aa0998227883b1d2" + 100 -> #"5ea38808617f1d5a15cd3a4a5c8ed73a9b39e2649d803bb2e126b649" + 101 -> #"a0a867fc08bea53ac1a499c650d7b2f08810a8bdebf13790ad060b43" + 102 -> #"5c86ba6ce3ce3c8b7919164d9e97868839a5927d8c59db8f1868aa6f" + 103 -> #"1599d5256c64ed7d5a6d277460aff5c22975ebea544f08add08d116a" + 104 -> #"1b5635b617817de4a66da0f6cd1130805744f651d2f2baf934b74090" + 105 -> #"4f151b79f239c61b1db3d4a220f856675c0ec3397c4caa3a2fa469b6" + 106 -> #"66b81fa9d4bb50c5d99432f483c68c0e9e641d2803eb53fb7bf55291" + 107 -> #"269aa0c869e188d5483cd804fa32181ba66fc2dd54fe0b2b23ec7e3c" + 108 -> #"d91e83eed8e97db6794c13430c18730fe7cd2525bbf56d17a8d5cb8d" + 109 -> #"7a55a79490f5c67067d63b89ca92f3829bda7f72cf9ee360a8d1e1fc" + 110 -> #"9e43100d2ad567b971ecc7ce384fa899200683462c662c781f8be5ac" + 111 -> #"b257e731eae5a50f078ed1ec976d14b66a01bdb4d0ce6c6bc0d1ccc2" + 112 -> #"a98eec8962b83e5564542f641a9183b332ce341866f3b4edb6abc5a5" + 113 -> #"7192b1d72ce9c0bdfff218f566a840be5e8d442b69b70a6d98008bed" + 114 -> #"2077abc628e957f03b6f0468665558735cbb66474aff20cb7f9f89fc" + 115 -> #"e98788d22cf93ebc9ee43108c614006c07ad6d3c4960f5f9f8c64ae1" + 116 -> #"c1ab28790f71cf90d91a5e7ed241a5fc6b09a4802964be0303e81860" + 117 -> #"f647de2f68b54508f1e751b5809130db2ef04d1a1956e9afbcc4aaed" + 118 -> #"c9e4262b3d65fd28f1fa719beb87ad55c9541078c6f6badf29b3e2dc" + 119 -> #"11f4cb688954a273e9d3d3ce459290fe555c8b7f569de1e1f9c20cb9" + 120 -> #"e2ff8411220469fc3f5596736e0575c113b06ce9883bda9347cf6723" + 121 -> #"5e20695e3c109dc38b0742bd7fd1e8efcf22b7b372b0bc24c6575916" + 122 -> #"7f46d6e02bd7d432bbe5630d6bf11e5238c556408818960eafd3dc9c" + 123 -> #"e103e15926605b608282cbfb588abbe6e3e85aef6fe2202179237551" + 124 -> #"6bd19f36563611a4947e63585b5e36afa7db04daaf1e240b69bd5c0c" + 125 -> #"e814cc4ec4e677681370c992a46934c4dd0a5eaefd3dea56b2678956" + 126 -> #"213c5ad0b80af3a61a47ab982dc5dc3d570f0ac592a0957cf1928bef" + 127 -> #"dab8076d475085650ac92934f8b1e3601654cce60ebf589b2d247ac9" + 128 -> #"ab1c06794b2edbd2083bdb21cc9e43a797144c4e549aef9ff4f37ab9" + 129 -> #"5ea38808617f1d5a15cd3a4a5c8ed73a9b39e2649d803bb2e126b649" + _ -> #"49ab521ec4868f03d881452afe90e83a6c4e59681e7b97173ed9847e" + } +} + +/// Mock a PolicyID +/// The variation is used to distinguish between different PolicyIDs +/// Use this but not other `mock_key_hash` functions to avoid hash collision +pub fn mock_policy_id(variation: Int) -> PolicyId { + when variation is { + 1 -> mock_key_hash(1) + 2 -> mock_key_hash(2) + 3 -> mock_key_hash(3) + 4 -> mock_key_hash(4) + _ -> mock_key_hash(5) + } +} + +/// Mock a public key hash +/// The variation is used to distinguish between different public keys +/// Use this but not other `mock_key_hash` functions to avoid hash collision +pub fn mock_pub_key_hash(variation: Int) -> Hash { + when variation is { + 1 -> mock_key_hash(11) + 2 -> mock_key_hash(12) + 3 -> mock_key_hash(13) + 4 -> mock_key_hash(14) + _ -> mock_key_hash(15) + } +} + +/// Mock a script hash +/// The variation is used to distinguish between different scripts +/// Use this but not other `mock_key_hash` functions to avoid hash collision +pub fn mock_script_hash(variation: Int) -> Hash { + when variation is { + 1 -> mock_key_hash(101) + 2 -> mock_key_hash(102) + 3 -> mock_key_hash(103) + 4 -> mock_key_hash(104) + 5 -> mock_key_hash(105) + 6 -> mock_key_hash(106) + 7 -> mock_key_hash(107) + 8 -> mock_key_hash(108) + 9 -> mock_key_hash(109) + 10 -> mock_key_hash(109) + 11 -> mock_key_hash(111) + 12 -> mock_key_hash(112) + 13 -> mock_key_hash(113) + 14 -> mock_key_hash(114) + 15 -> mock_key_hash(115) + 16 -> mock_key_hash(116) + 17 -> mock_key_hash(117) + 18 -> mock_key_hash(118) + 19 -> mock_key_hash(119) + 20 -> mock_key_hash(120) + 21 -> mock_key_hash(121) + 22 -> mock_key_hash(122) + 23 -> mock_key_hash(123) + 24 -> mock_key_hash(124) + 25 -> mock_key_hash(125) + 26 -> mock_key_hash(126) + 27 -> mock_key_hash(127) + 28 -> mock_key_hash(128) + 29 -> mock_key_hash(129) + 30 -> mock_key_hash(130) + _ -> mock_key_hash(100) + } +} + +/// Mock a stake key hash +/// The variation is used to distinguish between different stake keys +/// Use this but not other `mock_key_hash` functions to avoid hash collision +pub fn mock_stake_key_hash(variation: Int) -> Hash { + when variation is { + 1 -> mock_key_hash(21) + 2 -> mock_key_hash(22) + 3 -> mock_key_hash(23) + 4 -> mock_key_hash(24) + _ -> mock_key_hash(25) + } +} + +/// Mock a script stake key hash +/// The variation is used to distinguish between different scripts +/// Use this but not other `mock_key_hash` functions to avoid hash collision +pub fn mock_script_stake_key_hash(variation: Int) -> Hash { + when variation is { + 1 -> mock_key_hash(31) + 2 -> mock_key_hash(32) + 3 -> mock_key_hash(33) + 4 -> mock_key_hash(34) + _ -> mock_key_hash(35) + } +} diff --git a/example-web/aiken/build/packages/sidan-lab-vodka/lib/mocktail/virgin_output_reference.ak b/example-web/aiken/build/packages/sidan-lab-vodka/lib/mocktail/virgin_output_reference.ak new file mode 100644 index 00000000..3162f4d3 --- /dev/null +++ b/example-web/aiken/build/packages/sidan-lab-vodka/lib/mocktail/virgin_output_reference.ak @@ -0,0 +1,20 @@ +use aiken/hash.{Blake2b_256, Hash} +use aiken/transaction.{OutputReference, Transaction, TransactionId} + +pub fn mock_tx_hash(variation: Int) -> Hash { + when variation is { + 1 -> #"5a077cbcdffb88b104f292aacb9687ce93e2191e103a30a0cc5505c18b719f98" + 2 -> #"e325a279c996cd20dd9d01373fef96b83a0ce9ab69d1dd44c14abd90f3b73dcc" + 3 -> #"5a077cbcdffb88b104f292aacb9687ce93e2191e103a30a0cc5505c18b719f98" + 4 -> #"1cd16cb025e37b6fcec77cba7b0572ce5fc035b8576caffd9c1f2e9d8143e151" + _ -> #"580a225baee136641ba78acdc9a9b0537468db40acad46554834687ff5c18dd0" + } +} + +/// Mock an output reference +pub fn mock_utxo_ref(variation: Int, output_index: Int) -> OutputReference { + OutputReference { + transaction_id: TransactionId(mock_tx_hash(variation)), + output_index, + } +} diff --git a/example-web/aiken/build/packages/sidan-lab-vodka/lib/mocktail/virgin_outputs.ak b/example-web/aiken/build/packages/sidan-lab-vodka/lib/mocktail/virgin_outputs.ak new file mode 100644 index 00000000..9f3655e4 --- /dev/null +++ b/example-web/aiken/build/packages/sidan-lab-vodka/lib/mocktail/virgin_outputs.ak @@ -0,0 +1,30 @@ +use aiken/hash.{Blake2b_224, Hash} +use aiken/transaction.{Datum, NoDatum, Output} +use aiken/transaction/credential.{Address, Script} +use aiken/transaction/value.{Value} + +/// Mock an output +pub fn mock_output( + address: Address, + value: Value, + datum: Datum, + reference_script: Option>, +) -> Output { + Output { address, value, datum, reference_script } +} + +/// Mock an output with a public key address +/// `datum` and `reference_script` is omitted as it is seldom used in practice +pub fn mock_pub_key_output(address: Address, value: Value) -> Output { + mock_output(address, value, NoDatum, reference_script: None) +} + +/// Mock an output with a script address +/// `reference_script` is omitted as it is seldom used in practice +pub fn mock_script_output( + address: Address, + value: Value, + datum: Datum, +) -> Output { + mock_output(address, value, datum, reference_script: None) +} diff --git a/example-web/aiken/build/packages/sidan-lab-vodka/lib/mocktail/virgin_validity_range.ak b/example-web/aiken/build/packages/sidan-lab-vodka/lib/mocktail/virgin_validity_range.ak new file mode 100644 index 00000000..807802f1 --- /dev/null +++ b/example-web/aiken/build/packages/sidan-lab-vodka/lib/mocktail/virgin_validity_range.ak @@ -0,0 +1,28 @@ +use aiken/interval.{ + Finite, Interval, IntervalBound, NegativeInfinity, PositiveInfinity, +} +use aiken/transaction.{ValidityRange} + +/// Mock a validity range with the given lower and upper bounds. +pub fn mock_interval(lower: Option, upper: Option) -> ValidityRange { + let lower_bound = + when lower is { + Some(lower_bound_number) -> + IntervalBound { + bound_type: Finite(lower_bound_number), + is_inclusive: True, + } + None -> IntervalBound { bound_type: NegativeInfinity, is_inclusive: True } + } + let upper_bound = + when upper is { + Some(upper_bound_number) -> + IntervalBound { + bound_type: Finite(upper_bound_number), + is_inclusive: True, + } + None -> IntervalBound { bound_type: PositiveInfinity, is_inclusive: True } + } + + Interval { lower_bound, upper_bound } +} diff --git a/example-web/aiken/build/packages/sidan-lab-vodka/lib/vodka_address.ak b/example-web/aiken/build/packages/sidan-lab-vodka/lib/vodka_address.ak new file mode 100644 index 00000000..3e021733 --- /dev/null +++ b/example-web/aiken/build/packages/sidan-lab-vodka/lib/vodka_address.ak @@ -0,0 +1,55 @@ +use aiken/bytearray +use aiken/hash.{Blake2b_224, Hash} +use aiken/transaction/credential.{ + Address, Script, ScriptCredential, VerificationKey, VerificationKeyCredential, +} + +/// Compare two script addresses +pub fn compare_script_address(x: Address, y: Address) -> Ordering { + expect ScriptCredential(x_hash) = x.payment_credential + expect ScriptCredential(y_hash) = y.payment_credential + bytearray.compare(x_hash, y_hash) +} + +/// Compare two addresses +pub fn compare_address(x: Address, y: Address) -> Ordering { + let x_hash = x.payment_credential + let y_hash = y.payment_credential + when (x_hash, y_hash) is { + (ScriptCredential(x_script_hash), ScriptCredential(y_script_hash)) -> + bytearray.compare(x_script_hash, y_script_hash) + ( + VerificationKeyCredential(x_key_hash), + VerificationKeyCredential(y_key_hash), + ) -> bytearray.compare(x_key_hash, y_key_hash) + _ -> Equal + } +} + +/// Obtain the payment key of an address, it can be either a script hash or a verification key +pub fn address_payment_key(address: Address) -> Hash { + when address.payment_credential is { + ScriptCredential(hash) -> hash + VerificationKeyCredential(key_hash) -> key_hash + } +} + +/// Obtain the verification key of an address, None if it is a script address +pub fn address_pub_key( + address: Address, +) -> Option> { + when address.payment_credential is { + VerificationKeyCredential(key_hash) -> Some(key_hash) + _ -> None + } +} + +/// Obtain the script hash of an address, None if it is a verification key address +pub fn address_script_hash( + address: Address, +) -> Option> { + when address.payment_credential is { + ScriptCredential(script_hash) -> Some(script_hash) + _ -> None + } +} diff --git a/example-web/aiken/build/packages/sidan-lab-vodka/lib/vodka_converter.ak b/example-web/aiken/build/packages/sidan-lab-vodka/lib/vodka_converter.ak new file mode 100644 index 00000000..8f55b0f2 --- /dev/null +++ b/example-web/aiken/build/packages/sidan-lab-vodka/lib/vodka_converter.ak @@ -0,0 +1,36 @@ +use aiken/bytearray + +/// Convert an integer to a "stringify" ByteArray value +pub fn convert_int_to_bytes(i: Int) -> ByteArray { + convert_int_to_bytes_go(i, get_number_digit(i)) +} + +fn convert_int_to_bytes_go(newInt: Int, digit: Int) -> ByteArray { + if digit == 1 { + bytearray.push("", newInt + 48) + } else { + bytearray.push( + convert_int_to_bytes_go(newInt % digit, digit / 10), + newInt / digit + 48, + ) + } +} + +/// Get the number of digits in an integer +pub fn get_number_digit(i: Int) -> Int { + go_get_number_digit(i, 1) +} + +fn go_get_number_digit(newInt: Int, digit: Int) -> Int { + if newInt < 10 { + digit + } else { + go_get_number_digit(newInt / 10, digit * 10) + } +} + +test byte_conversion() { + convert_int_to_bytes(1) == "1" && convert_int_to_bytes(123) == "123" && convert_int_to_bytes( + 672912, + ) == "672912" +} diff --git a/example-web/aiken/build/packages/sidan-lab-vodka/lib/vodka_extra_signatories.ak b/example-web/aiken/build/packages/sidan-lab-vodka/lib/vodka_extra_signatories.ak new file mode 100644 index 00000000..222406e3 --- /dev/null +++ b/example-web/aiken/build/packages/sidan-lab-vodka/lib/vodka_extra_signatories.ak @@ -0,0 +1,19 @@ +use aiken/list + +// Check if a key is signed by any of the extra_signatories +pub fn key_signed(extra_signatories: List, key: ByteArray) { + list.has(extra_signatories, key) +} + +/// Check if any of the keys are signed by the extra_signatories +pub fn one_of_keys_signed( + extra_signatories: List, + keys: List, +) { + list.any(keys, fn(key) { key_signed(extra_signatories, key) }) +} + +/// Check if all of the keys are signed by the extra_signatories +pub fn all_key_signed(extra_signatories: List, keys: List) { + list.all(keys, fn(key) { key_signed(extra_signatories, key) }) +} diff --git a/example-web/aiken/build/packages/sidan-lab-vodka/lib/vodka_inputs.ak b/example-web/aiken/build/packages/sidan-lab-vodka/lib/vodka_inputs.ak new file mode 100644 index 00000000..c202aba5 --- /dev/null +++ b/example-web/aiken/build/packages/sidan-lab-vodka/lib/vodka_inputs.ak @@ -0,0 +1,99 @@ +use aiken/list +use aiken/transaction.{InlineDatum, Input} +use aiken/transaction/credential.{Address} +use aiken/transaction/value.{AssetName, PolicyId, flatten, quantity_of} + +/// Extracts the inline datum from an input. +pub fn output_inline_datum(input: Input) { + expect InlineDatum(raw_datum) = input.output.datum + raw_datum +} + +/// Extract the inline datum by locating the first input in a list of inputs by Address and PolicyId. +pub fn only_input_datum_with( + inputs: List, + policy: PolicyId, + name: AssetName, +) { + expect Some(input) = + list.find( + inputs, + fn(input) { quantity_of(input.output.value, policy, name) == 1 }, + ) + output_inline_datum(input) +} + +/// Filters inputs by Address. +pub fn inputs_at(inputs: List, address: Address) -> List { + list.filter(inputs, fn(input) { input.output.address == address }) +} + +/// Filters inputs by PolicyId and AssetName. +pub fn inputs_with( + inputs: List, + policy: PolicyId, + name: AssetName, +) -> List { + list.filter( + inputs, + fn(input) { quantity_of(input.output.value, policy, name) == 1 }, + ) +} + +/// Filters inputs by token policy. +pub fn inputs_with_policy(inputs: List, policy: PolicyId) -> List { + list.filter( + inputs, + fn(input) { + list.any(flatten(input.output.value), fn(token) { token.1st == policy }) + }, + ) +} + +/// Filters inputs by Address, PolicyId, and AssetName. +pub fn inputs_at_with( + inputs: List, + address: Address, + policy: PolicyId, + name: AssetName, +) -> List { + list.filter( + inputs, + fn(input) { + input.output.address == address && quantity_of( + input.output.value, + policy, + name, + ) == 1 + }, + ) +} + +/// Filters inputs by Address and PolicyId. +pub fn inputs_at_with_policy( + inputs: List, + address: Address, + policy: PolicyId, +) -> List { + list.filter( + inputs, + fn(input) { + input.output.address == address && list.any( + flatten(input.output.value), + fn(token) { token.1st == policy }, + ) + }, + ) +} + +/// Calculate the total quantity of a token in a list of inputs. +pub fn inputs_token_quantity( + inputs: List, + token: (PolicyId, AssetName), +) -> Int { + list.map( + inputs, + fn(input) { quantity_of(input.output.value, token.1st, token.2nd) }, + ) + |> list.foldr(0, fn(n, total) { n + total }) +} diff --git a/example-web/aiken/build/packages/sidan-lab-vodka/lib/vodka_mints.ak b/example-web/aiken/build/packages/sidan-lab-vodka/lib/vodka_mints.ak new file mode 100644 index 00000000..7e3f769a --- /dev/null +++ b/example-web/aiken/build/packages/sidan-lab-vodka/lib/vodka_mints.ak @@ -0,0 +1,45 @@ +use aiken/list +use aiken/transaction/value.{ + AssetName, MintedValue, PolicyId, flatten, from_minted_value, +} + +/// Check if a certain PolicyId is burning only if exists in the minted value. +pub fn check_policy_only_burn(mint: MintedValue, policy: PolicyId) -> Bool { + let mint_value = flatten(from_minted_value(mint)) + list.all( + mint_value, + fn(x) { + if x.1st == policy { + x.3rd < 0 + } else { + True + } + }, + ) +} + +/// Check if the minted value contains only one distinct asset with particular PolicyId. +pub fn only_minted_token( + mint: MintedValue, + policy: PolicyId, + name: AssetName, + quantity: Int, +) { + when from_minted_value(mint) |> flatten() is { + [(minted_policy, minted_asset_name, minted_quantity)] -> + minted_policy == policy && minted_asset_name == name && minted_quantity == quantity + _ -> False + } +} + +/// Check if the minted value contains a token with particular PolicyId, AssetName, and quantity. +pub fn token_minted( + mint: MintedValue, + policy: PolicyId, + name: AssetName, + quantity: Int, +) { + from_minted_value(mint) + |> flatten() + |> list.any(fn(x) { x.1st == policy && x.2nd == name && x.3rd == quantity }) +} diff --git a/example-web/aiken/build/packages/sidan-lab-vodka/lib/vodka_outputs.ak b/example-web/aiken/build/packages/sidan-lab-vodka/lib/vodka_outputs.ak new file mode 100644 index 00000000..b6047949 --- /dev/null +++ b/example-web/aiken/build/packages/sidan-lab-vodka/lib/vodka_outputs.ak @@ -0,0 +1,65 @@ +use aiken/list +use aiken/transaction.{InlineDatum, Output} +use aiken/transaction/credential.{Address} +use aiken/transaction/value.{AssetName, PolicyId, flatten, quantity_of} + +/// Extracts the inline datum from an output. +pub fn output_inline_datum(output: Output) { + expect InlineDatum(raw_datum) = output.datum + raw_datum +} + +/// Filters outputs by Address. +pub fn outputs_at(outputs: List, address: Address) -> List { + list.filter(outputs, fn(output) { output.address == address }) +} + +/// Filters outputs by PolicyId and AssetName. +pub fn outputs_with(outputs: List, policy: PolicyId, name: AssetName) { + list.filter( + outputs, + fn(output) { quantity_of(output.value, policy, name) == 1 }, + ) +} + +/// Filters outputs by token policy. +pub fn outputs_with_policy(outputs: List, policy: PolicyId) { + list.filter( + outputs, + fn(output) { + list.any(flatten(output.value), fn(token) { token.1st == policy }) + }, + ) +} + +/// Filters outputs by Address, PolicyId, and AssetName. +pub fn outputs_at_with( + outputs: List, + address: Address, + policy: PolicyId, + name: AssetName, +) -> List { + list.filter( + outputs, + fn(output) { + output.address == address && quantity_of(output.value, policy, name) == 1 + }, + ) +} + +/// Filters outputs by Address and PolicyId. +pub fn outputs_at_with_policy( + outputs: List, + address: Address, + policy: PolicyId, +) -> List { + list.filter( + outputs, + fn(output) { + output.address == address && list.any( + flatten(output.value), + fn(token) { token.1st == policy }, + ) + }, + ) +} diff --git a/example-web/aiken/build/packages/sidan-lab-vodka/lib/vodka_redeemers.ak b/example-web/aiken/build/packages/sidan-lab-vodka/lib/vodka_redeemers.ak new file mode 100644 index 00000000..39f75fe9 --- /dev/null +++ b/example-web/aiken/build/packages/sidan-lab-vodka/lib/vodka_redeemers.ak @@ -0,0 +1,44 @@ +use aiken/bytearray +use aiken/pairs +use aiken/transaction.{ + Input, OutputReference, Redeemer, ScriptPurpose, Spend, WithdrawFrom, + find_input, +} +use aiken/transaction/credential.{Address, Inline, ScriptCredential} + +/// Obtain the redeemer for a given output reference and address +pub fn redeemer_from( + redeemers: Pairs, + inputs: List, + output_reference: OutputReference, + input_address: Address, +) -> Option { + expect Some(redeemer) = redeemers |> pairs.get_first(Spend(output_reference)) + expect Some(input) = find_input(inputs, output_reference) + if input.output.address == input_address { + Some(redeemer) + } else { + None + } +} + +/// Obtain the first redeemer for a given withdrawal script hash +pub fn withdrawal_redeemer( + redeemers: Pairs, + withdrawal_script_hash: ByteArray, +) -> Option { + redeemers + |> pairs.get_first( + WithdrawFrom(Inline(ScriptCredential(withdrawal_script_hash))), + ) +} + +/// Compare the output reference of two spend transactions +pub fn compare_output_reference(x, y) { + expect Spend(out_ref_x) = x + expect Spend(out_ref_y) = y + bytearray.compare( + out_ref_x.transaction_id.hash, + out_ref_y.transaction_id.hash, + ) +} diff --git a/example-web/aiken/build/packages/sidan-lab-vodka/lib/vodka_validity_range.ak b/example-web/aiken/build/packages/sidan-lab-vodka/lib/vodka_validity_range.ak new file mode 100644 index 00000000..a8cffabc --- /dev/null +++ b/example-web/aiken/build/packages/sidan-lab-vodka/lib/vodka_validity_range.ak @@ -0,0 +1,28 @@ +use aiken/interval.{Finite, IntervalBound} +use aiken/transaction.{ValidityRange} + +/// Check if the validity range is after the required timestamp. +pub fn valid_after( + validity_range: ValidityRange, + required_timestamp: Int, +) -> Bool { + let IntervalBound { bound_type, is_inclusive } = validity_range.lower_bound + when (bound_type, is_inclusive) is { + (Finite(lower_bound), True) -> lower_bound > required_timestamp + (Finite(lower_bound), False) -> lower_bound >= required_timestamp + _ -> False + } +} + +/// Check if the validity range is before the required timestamp. +pub fn valid_before( + validity_range: ValidityRange, + required_timestamp: Int, +) -> Bool { + let IntervalBound { bound_type, is_inclusive } = validity_range.upper_bound + when (bound_type, is_inclusive) is { + (Finite(upper_bound), True) -> upper_bound < required_timestamp + (Finite(upper_bound), False) -> upper_bound <= required_timestamp + _ -> False + } +} diff --git a/example-web/aiken/build/packages/sidan-lab-vodka/lib/vodka_value.ak b/example-web/aiken/build/packages/sidan-lab-vodka/lib/vodka_value.ak new file mode 100644 index 00000000..f8cce357 --- /dev/null +++ b/example-web/aiken/build/packages/sidan-lab-vodka/lib/vodka_value.ak @@ -0,0 +1,55 @@ +use aiken/list +use aiken/transaction.{Input, Output} +use aiken/transaction/credential.{Address} +use aiken/transaction/value.{Value, flatten, merge, quantity_of, zero} + +/// Calulate the length of a value +pub fn value_length(value: Value) -> Int { + list.length(flatten(value)) +} + +/// Get the value send to a particular address in a list of outputs +pub fn get_all_value_to(outputs: List, address: Address) -> Value { + list.foldr( + outputs, + zero(), + fn(output, acc_value) { + if output.address == address { + merge(acc_value, output.value) + } else { + acc_value + } + }, + ) +} + +/// Get the value coming from a particular address in a list of inputs +pub fn get_all_value_from(inputs: List, address: Address) -> Value { + list.foldr( + inputs, + zero(), + fn(input, acc_value) { + if input.output.address == address { + merge(acc_value, input.output.value) + } else { + acc_value + } + }, + ) +} + +/// Check if the first value provided is greater than or equal to the second value +pub fn value_geq(greater: Value, smaller: Value) -> Bool { + list.all( + flatten(smaller), + fn(token) { quantity_of(greater, token.1st, token.2nd) >= token.3rd }, + ) +} + +/// Obtain the information (i.e. flattened value) of a policy in a value +pub fn value_policy_info( + value: Value, + policy: ByteArray, +) -> Option<(ByteArray, ByteArray, Int)> { + list.find(flatten(value), fn(t) { t.1st == policy }) +} diff --git a/example-web/aiken/build/packages/sidan-lab-vodka/plutus.json b/example-web/aiken/build/packages/sidan-lab-vodka/plutus.json new file mode 100644 index 00000000..035b55e0 --- /dev/null +++ b/example-web/aiken/build/packages/sidan-lab-vodka/plutus.json @@ -0,0 +1,14 @@ +{ + "preamble": { + "title": "sidan-lab/vodka", + "description": "Aiken utils for project 'sidan-lab/vodka", + "version": "0.0.1-beta", + "plutusVersion": "v2", + "compiler": { + "name": "Aiken", + "version": "v1.0.29-alpha+unknown" + }, + "license": "Apache-2.0" + }, + "validators": [] +} \ No newline at end of file diff --git a/example-web/aiken/plutus.json b/example-web/aiken/plutus.json new file mode 100644 index 00000000..d875ba09 --- /dev/null +++ b/example-web/aiken/plutus.json @@ -0,0 +1,84 @@ +{ + "preamble": { + "title": "meshsdk/aiken-template", + "description": "Aiken contracts for project 'meshsdk/aiken-template'", + "version": "0.0.0", + "plutusVersion": "v2", + "compiler": { + "name": "Aiken", + "version": "v1.0.31-alpha+unknown" + }, + "license": "Apache-2.0" + }, + "validators": [ + { + "title": "hello_world.hello_world", + "datum": { + "title": "datum", + "schema": { + "$ref": "#/definitions/hello_world~1Datum" + } + }, + "redeemer": { + "title": "redeemer", + "schema": { + "$ref": "#/definitions/hello_world~1Redeemer" + } + }, + "compiledCode": "59012f010000323232323232322323223232253330083232533300a3371e6eb8c008c030dd5002a4410d48656c6c6f2c20576f726c642100100114a06644646600200200644a66602000229404c94ccc038cdc79bae301200200414a226600600600260240026eb0c034c038c038c038c038c038c038c038c038c02cdd5180098059baa002375c600260166ea801c8c0340045261365653330063370e900018039baa001132533300a00116132533300b300d002149858c94cccccc038004585858584dd7000980580098041baa00116533333300b00110011616161653330033370e900018021baa0011325333007001161325333008300a002149858c94cccccc02c004585858584dd7000980400098029baa0011653333330080011001161616165734aae7555cf2ab9f5742ae895d201", + "hash": "21d87ba6c97a980c884aeade86f3ba48a3891d871beb51101e0c9b72" + }, + { + "title": "minting.always_succeed_minting_policy", + "redeemer": { + "title": "_redeemer", + "schema": { + "$ref": "#/definitions/Data" + } + }, + "compiledCode": "5834010000323232323222533300353330033370e900018021baa3006300730053754002294458526136565734aae7555cf2ba157441", + "hash": "f060f0ef7fa4c3c6d3a4f831c639038db0f625c548a711f2b276a282" + } + ], + "definitions": { + "ByteArray": { + "dataType": "bytes" + }, + "Data": { + "title": "Data", + "description": "Any Plutus data." + }, + "hello_world/Datum": { + "title": "Datum", + "anyOf": [ + { + "title": "Datum", + "dataType": "constructor", + "index": 0, + "fields": [ + { + "title": "owner", + "$ref": "#/definitions/ByteArray" + } + ] + } + ] + }, + "hello_world/Redeemer": { + "title": "Redeemer", + "anyOf": [ + { + "title": "Redeemer", + "dataType": "constructor", + "index": 0, + "fields": [ + { + "title": "msg", + "$ref": "#/definitions/ByteArray" + } + ] + } + ] + } + } +} \ No newline at end of file diff --git a/example-web/aiken/validators/hello-world.ak b/example-web/aiken/validators/hello-world.ak new file mode 100644 index 00000000..7eec4f3e --- /dev/null +++ b/example-web/aiken/validators/hello-world.ak @@ -0,0 +1,61 @@ +use aiken/hash.{Blake2b_224, Hash} +use aiken/list +use aiken/transaction.{ScriptContext, Spend, Transaction} +use aiken/transaction/credential.{VerificationKey} +use mocktail.{complete, mocktail_tx, required_signer_hash} +use mocktail/virgin_key_hex.{mock_pub_key_hash} +use mocktail/virgin_output_reference.{mock_utxo_ref} + +type Datum { + owner: Hash, +} + +type Redeemer { + msg: ByteArray, +} + +validator { + fn hello_world( + datum: Datum, + redeemer: Redeemer, + context: ScriptContext, + ) -> Bool { + let must_say_hello = redeemer.msg == "Hello, World!" + let must_be_signed = + list.has(context.transaction.extra_signatories, datum.owner) + must_say_hello && must_be_signed + } +} + +fn mock_tx(is_owner_signed: Bool) -> Transaction { + mocktail_tx() + |> required_signer_hash(is_owner_signed, mock_pub_key_hash(0)) + |> complete() +} + +test test_hello_world() { + let datum = Datum { owner: mock_pub_key_hash(0) } + let redeemer = Redeemer { msg: "Hello, World!" } + let tx = mock_tx(True) + let context = + ScriptContext { transaction: tx, purpose: Spend(mock_utxo_ref(0, 0)) } + hello_world(datum, redeemer, context) +} + +test test_failed_hello_world_incorrect_redeemer() { + let datum = Datum { owner: mock_pub_key_hash(0) } + let redeemer = Redeemer { msg: "GM World!" } + let tx = mock_tx(True) + let context = + ScriptContext { transaction: tx, purpose: Spend(mock_utxo_ref(0, 0)) } + !hello_world(datum, redeemer, context) +} + +test test_failed_hello_world_without_signer() { + let datum = Datum { owner: mock_pub_key_hash(0) } + let redeemer = Redeemer { msg: "Hello, World!" } + let tx = mock_tx(False) + let context = + ScriptContext { transaction: tx, purpose: Spend(mock_utxo_ref(0, 0)) } + !hello_world(datum, redeemer, context) +} diff --git a/example-web/aiken/validators/minting.ak b/example-web/aiken/validators/minting.ak new file mode 100644 index 00000000..80315db8 --- /dev/null +++ b/example-web/aiken/validators/minting.ak @@ -0,0 +1,14 @@ +use aiken/transaction.{Mint, ScriptContext, placeholder} + +validator { + fn always_succeed_minting_policy(_redeemer: Data, context: ScriptContext) { + expect Mint(_) = context.purpose + True + } +} + +test test_always_succeed_minting_policy() { + let data = Void + let context = ScriptContext { purpose: Mint(#""), transaction: placeholder() } + always_succeed_minting_policy(data, context) +} diff --git a/example-web/src/pages/index.tsx b/example-web/src/pages/index.tsx index c090cc39..f212bdc0 100644 --- a/example-web/src/pages/index.tsx +++ b/example-web/src/pages/index.tsx @@ -1,10 +1,147 @@ import { Inter } from "next/font/google"; import Head from "next/head"; -import { CardanoWallet, MeshBadge } from "@meshsdk/react"; +import { CardanoWallet, MeshBadge, useWallet } from "@meshsdk/react"; +import axios from "axios"; +import { applyCborEncoding } from "@meshsdk/core-csl"; +import { + ByteString, + byteString, + ConStr0, + conStr0, + deserializeAddress, + MaestroProvider, + PubKeyHash, + pubKeyHash, + resolveScriptHash, + serializePlutusScript, + stringToHex, + UTxO, +} from "@meshsdk/core"; +import { parseDatumCbor } from "@meshsdk/core-csl"; const inter = Inter({ subsets: ["latin"] }); +const provider = new MaestroProvider({ + network: "Preprod", + apiKey: process.env.NEXT_PUBLIC_MAESTRO_API_KEY!, +}); + +const helloWorldScriptRawCompiledCode = + "59012f010000323232323232322323223232253330083232533300a3371e6eb8c008c030dd5002a4410d48656c6c6f2c20576f726c642100100114a06644646600200200644a66602000229404c94ccc038cdc79bae301200200414a226600600600260240026eb0c034c038c038c038c038c038c038c038c038c02cdd5180098059baa002375c600260166ea801c8c0340045261365653330063370e900018039baa001132533300a00116132533300b300d002149858c94cccccc038004585858584dd7000980580098041baa00116533333300b00110011616161653330033370e900018021baa0011325333007001161325333008300a002149858c94cccccc02c004585858584dd7000980400098029baa0011653333330080011001161616165734aae7555cf2ab9f5742ae895d201"; + +const helloWorldScriptCbor = applyCborEncoding(helloWorldScriptRawCompiledCode); +const helloWorldScriptAddress = serializePlutusScript({ + code: helloWorldScriptCbor, + version: "V2", +}).address; + +const alwaysSucceedScriptRawCompiledCode = + "5834010000323232323222533300353330033370e900018021baa3006300730053754002294458526136565734aae7555cf2ba157441"; + +const alwaysSucceedScriptCbor = applyCborEncoding( + alwaysSucceedScriptRawCompiledCode +); +const alwaysSucceedPolicyId = resolveScriptHash(alwaysSucceedScriptCbor, "V2"); + +const whisky = axios.create({ + baseURL: "http://127.0.0.1:8080", + headers: { + "Content-Type": "application/json", + }, +}); + export default function Home() { + const { wallet } = useWallet(); + const sendLovelace = async () => { + const inputs = await wallet.getUtxos(); + const address = await wallet.getChangeAddress(); + const response = await whisky.post("/send_lovelace", { + recipientAddress: + "addr_test1qqmrzjhtanauj20wg37uk58adyrqfm82a9qr52vdnv0e54r42v0mu8ngky0f5yxmh3wl3z0da2fryk59kavth0u8xhvsufgmc8", + myAddress: address, + inputs, + }); + const txHex = response.data.txHex; + const signedTx = await wallet.signTx(txHex); + const txHash = await wallet.submitTx(signedTx); + console.log("txHash", txHash); + }; + + const lockFund = async () => { + const inputs = await wallet.getUtxos(); + const address = await wallet.getChangeAddress(); + const ownPubKey = deserializeAddress(address).pubKeyHash; + const datum = conStr0([pubKeyHash(ownPubKey)]); + + const response = await whisky.post("/lock_fund", { + scriptAddress: helloWorldScriptAddress, + datum: JSON.stringify(datum), + myAddress: address, + inputs, + }); + const txHex = response.data.txHex; + const signedTx = await wallet.signTx(txHex); + const txHash = await wallet.submitTx(signedTx); + console.log("txHash", txHash); + }; + const unlockFund = async () => { + // "8fb75f27f60e8149a091c749f9712ad59c9d114c457aed1c1acc8d9225d5c662" + const inputs = await wallet.getUtxos(); + const address = await wallet.getChangeAddress(); + const collateral = (await wallet.getCollateral())[0]; + + const ownPubKey = deserializeAddress(address).pubKeyHash; + const scriptInput = ( + await provider.fetchAddressUTxOs(helloWorldScriptAddress) + ).find((input) => { + if (input.output.plutusData) { + const datum: ConStr0<[PubKeyHash]> = parseDatumCbor( + input.output.plutusData + ); + if (datum && datum.fields && datum.fields.length > 0) { + return datum.fields[0].bytes === ownPubKey; + } + } + return false; + }); + + const response = await whisky.post("/unlock_fund", { + scriptUtxo: scriptInput, + redeemer: JSON.stringify( + conStr0([byteString(stringToHex("Hello, World!"))]) + ), + script: { + scriptCbor: helloWorldScriptCbor, + languageVersion: "v2", + }, + myAddress: address, + inputs, + collateral, + }); + const txHex = response.data.txHex; + const signedTx = await wallet.signTx(txHex); + const txHash = await wallet.submitTx(signedTx); + console.log("txHash", txHash); + }; + const mintTokens = async () => { + const inputs = await wallet.getUtxos(); + const address = await wallet.getChangeAddress(); + const collateral = (await wallet.getCollateral())[0]; + + const response = await whisky.post("/mint_tokens", { + toMintAsset: { unit: alwaysSucceedPolicyId, quantity: "1" }, + redeemer: JSON.stringify(byteString("")), + script: { scriptCbor: alwaysSucceedScriptCbor, languageVersion: "v2" }, + myAddress: address, + inputs, + collateral, + }); + const txHex = response.data.txHex; + const signedTx = await wallet.signTx(txHex); + const txHash = await wallet.submitTx(signedTx); + console.log("txHash", txHash); + }; + return (
@@ -19,41 +156,49 @@ export default function Home() { {" "} Next.js +

+ + Whisky Example + {" "} +