diff options
Diffstat (limited to 'aoc2023/build/packages/tom')
156 files changed, 24775 insertions, 0 deletions
diff --git a/aoc2023/build/packages/tom/README.md b/aoc2023/build/packages/tom/README.md new file mode 100644 index 0000000..e6adcbe --- /dev/null +++ b/aoc2023/build/packages/tom/README.md @@ -0,0 +1,47 @@ +# tom + +A Gleam TOML parser! + +[](https://hex.pm/packages/tom) +[](https://hexdocs.pm/tom/) + + +```sh +gleam add tom +``` +```gleam +import tom + +const config = " + [person] + name = \"Lucy\" + is_cool = true +" + +pub fn main() { + // Parse a string of TOML + let assert Ok(parsed) = tom.parse(config) + + // Now you can work with the data directly, or you can use the `get_*` + // functions to retrieve values. + + tom.get_string(parsed, ["person", "name"]) + // -> Ok("Lucy") + + let is_cool = tom.get_bool(parsed, ["person", "is_cool"]) + // -> Ok(True) +} +``` + +Further documentation can be found at <https://hexdocs.pm/tom>. + +## Status + +The following string escape sequences are not supported yet: + +- `\b` +- `\f` +- `\e` +- `\xHH` +- `\uHHHH` +- `\UHHHHHHHH` diff --git a/aoc2023/build/packages/tom/build/dev/erlang/gleam.lock b/aoc2023/build/packages/tom/build/dev/erlang/gleam.lock new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/aoc2023/build/packages/tom/build/dev/erlang/gleam.lock diff --git a/aoc2023/build/packages/tom/build/dev/javascript/gleam.lock b/aoc2023/build/packages/tom/build/dev/javascript/gleam.lock new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/aoc2023/build/packages/tom/build/dev/javascript/gleam.lock diff --git a/aoc2023/build/packages/tom/build/lsp/erlang/gleam.lock b/aoc2023/build/packages/tom/build/lsp/erlang/gleam.lock new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/aoc2023/build/packages/tom/build/lsp/erlang/gleam.lock diff --git a/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@base.cache b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@base.cache Binary files differnew file mode 100644 index 0000000..0a49e16 --- /dev/null +++ b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@base.cache diff --git a/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@base.cache_meta b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@base.cache_meta Binary files differnew file mode 100644 index 0000000..d03bd4e --- /dev/null +++ b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@base.cache_meta diff --git a/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@bit_array.cache b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@bit_array.cache Binary files differnew file mode 100644 index 0000000..e75aa37 --- /dev/null +++ b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@bit_array.cache diff --git a/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@bit_array.cache_meta b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@bit_array.cache_meta Binary files differnew file mode 100644 index 0000000..831cdbd --- /dev/null +++ b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@bit_array.cache_meta diff --git a/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@bit_builder.cache b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@bit_builder.cache Binary files differnew file mode 100644 index 0000000..e64afd2 --- /dev/null +++ b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@bit_builder.cache diff --git a/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@bit_builder.cache_meta b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@bit_builder.cache_meta Binary files differnew file mode 100644 index 0000000..b59a80e --- /dev/null +++ b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@bit_builder.cache_meta diff --git a/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@bit_string.cache b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@bit_string.cache Binary files differnew file mode 100644 index 0000000..3dd81d8 --- /dev/null +++ b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@bit_string.cache diff --git a/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@bit_string.cache_meta b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@bit_string.cache_meta Binary files differnew file mode 100644 index 0000000..e7f013d --- /dev/null +++ b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@bit_string.cache_meta diff --git a/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@bool.cache b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@bool.cache Binary files differnew file mode 100644 index 0000000..de4ee67 --- /dev/null +++ b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@bool.cache diff --git a/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@bool.cache_meta b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@bool.cache_meta Binary files differnew file mode 100644 index 0000000..e616ec0 --- /dev/null +++ b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@bool.cache_meta diff --git a/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@bytes_builder.cache b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@bytes_builder.cache Binary files differnew file mode 100644 index 0000000..98608da --- /dev/null +++ b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@bytes_builder.cache diff --git a/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@bytes_builder.cache_meta b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@bytes_builder.cache_meta Binary files differnew file mode 100644 index 0000000..701db1f --- /dev/null +++ b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@bytes_builder.cache_meta diff --git a/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@dict.cache b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@dict.cache Binary files differnew file mode 100644 index 0000000..2f469d1 --- /dev/null +++ b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@dict.cache diff --git a/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@dict.cache_meta b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@dict.cache_meta Binary files differnew file mode 100644 index 0000000..277da6b --- /dev/null +++ b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@dict.cache_meta diff --git a/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@dynamic.cache b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@dynamic.cache Binary files differnew file mode 100644 index 0000000..60712c7 --- /dev/null +++ b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@dynamic.cache diff --git a/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@dynamic.cache_meta b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@dynamic.cache_meta Binary files differnew file mode 100644 index 0000000..91b39cc --- /dev/null +++ b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@dynamic.cache_meta diff --git a/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@float.cache b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@float.cache Binary files differnew file mode 100644 index 0000000..b832a82 --- /dev/null +++ b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@float.cache diff --git a/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@float.cache_meta b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@float.cache_meta Binary files differnew file mode 100644 index 0000000..702d055 --- /dev/null +++ b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@float.cache_meta diff --git a/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@function.cache b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@function.cache Binary files differnew file mode 100644 index 0000000..61d53d4 --- /dev/null +++ b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@function.cache diff --git a/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@function.cache_meta b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@function.cache_meta Binary files differnew file mode 100644 index 0000000..e7b9f9f --- /dev/null +++ b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@function.cache_meta diff --git a/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@int.cache b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@int.cache Binary files differnew file mode 100644 index 0000000..fdcf634 --- /dev/null +++ b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@int.cache diff --git a/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@int.cache_meta b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@int.cache_meta Binary files differnew file mode 100644 index 0000000..aad5629 --- /dev/null +++ b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@int.cache_meta diff --git a/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@io.cache b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@io.cache Binary files differnew file mode 100644 index 0000000..70774f5 --- /dev/null +++ b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@io.cache diff --git a/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@io.cache_meta b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@io.cache_meta Binary files differnew file mode 100644 index 0000000..48ca73e --- /dev/null +++ b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@io.cache_meta diff --git a/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@iterator.cache b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@iterator.cache Binary files differnew file mode 100644 index 0000000..2ebb0ba --- /dev/null +++ b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@iterator.cache diff --git a/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@iterator.cache_meta b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@iterator.cache_meta Binary files differnew file mode 100644 index 0000000..43c26de --- /dev/null +++ b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@iterator.cache_meta diff --git a/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@list.cache b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@list.cache Binary files differnew file mode 100644 index 0000000..f58495d --- /dev/null +++ b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@list.cache diff --git a/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@list.cache_meta b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@list.cache_meta Binary files differnew file mode 100644 index 0000000..ec9035e --- /dev/null +++ b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@list.cache_meta diff --git a/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@map.cache b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@map.cache Binary files differnew file mode 100644 index 0000000..f5f5c1e --- /dev/null +++ b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@map.cache diff --git a/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@map.cache_meta b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@map.cache_meta Binary files differnew file mode 100644 index 0000000..f1ed30f --- /dev/null +++ b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@map.cache_meta diff --git a/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@option.cache b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@option.cache Binary files differnew file mode 100644 index 0000000..f50856a --- /dev/null +++ b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@option.cache diff --git a/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@option.cache_meta b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@option.cache_meta Binary files differnew file mode 100644 index 0000000..8b36f47 --- /dev/null +++ b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@option.cache_meta diff --git a/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@order.cache b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@order.cache Binary files differnew file mode 100644 index 0000000..f10a597 --- /dev/null +++ b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@order.cache diff --git a/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@order.cache_meta b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@order.cache_meta Binary files differnew file mode 100644 index 0000000..2395015 --- /dev/null +++ b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@order.cache_meta diff --git a/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@pair.cache b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@pair.cache Binary files differnew file mode 100644 index 0000000..c31d461 --- /dev/null +++ b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@pair.cache diff --git a/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@pair.cache_meta b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@pair.cache_meta Binary files differnew file mode 100644 index 0000000..a2089a5 --- /dev/null +++ b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@pair.cache_meta diff --git a/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@queue.cache b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@queue.cache Binary files differnew file mode 100644 index 0000000..b1e9775 --- /dev/null +++ b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@queue.cache diff --git a/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@queue.cache_meta b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@queue.cache_meta Binary files differnew file mode 100644 index 0000000..6ec02d2 --- /dev/null +++ b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@queue.cache_meta diff --git a/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@regex.cache b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@regex.cache Binary files differnew file mode 100644 index 0000000..e9ae9d8 --- /dev/null +++ b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@regex.cache diff --git a/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@regex.cache_meta b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@regex.cache_meta Binary files differnew file mode 100644 index 0000000..d6129ae --- /dev/null +++ b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@regex.cache_meta diff --git a/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@result.cache b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@result.cache Binary files differnew file mode 100644 index 0000000..c29fc16 --- /dev/null +++ b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@result.cache diff --git a/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@result.cache_meta b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@result.cache_meta Binary files differnew file mode 100644 index 0000000..f4e8874 --- /dev/null +++ b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@result.cache_meta diff --git a/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@set.cache b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@set.cache Binary files differnew file mode 100644 index 0000000..47c7957 --- /dev/null +++ b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@set.cache diff --git a/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@set.cache_meta b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@set.cache_meta Binary files differnew file mode 100644 index 0000000..5d99ee5 --- /dev/null +++ b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@set.cache_meta diff --git a/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@string.cache b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@string.cache Binary files differnew file mode 100644 index 0000000..4d87904 --- /dev/null +++ b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@string.cache diff --git a/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@string.cache_meta b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@string.cache_meta Binary files differnew file mode 100644 index 0000000..d4739c1 --- /dev/null +++ b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@string.cache_meta diff --git a/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@string_builder.cache b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@string_builder.cache Binary files differnew file mode 100644 index 0000000..c0e91da --- /dev/null +++ b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@string_builder.cache diff --git a/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@string_builder.cache_meta b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@string_builder.cache_meta Binary files differnew file mode 100644 index 0000000..b367bfd --- /dev/null +++ b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@string_builder.cache_meta diff --git a/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@uri.cache b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@uri.cache Binary files differnew file mode 100644 index 0000000..ba5daf8 --- /dev/null +++ b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@uri.cache diff --git a/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@uri.cache_meta b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@uri.cache_meta Binary files differnew file mode 100644 index 0000000..2ae412b --- /dev/null +++ b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_stdlib/_gleam_artefacts/gleam@uri.cache_meta diff --git a/aoc2023/build/packages/tom/build/lsp/erlang/gleam_version b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_version new file mode 100644 index 0000000..7d07a19 --- /dev/null +++ b/aoc2023/build/packages/tom/build/lsp/erlang/gleam_version @@ -0,0 +1 @@ +0.33.0
\ No newline at end of file diff --git a/aoc2023/build/packages/tom/build/lsp/erlang/gleeunit/_gleam_artefacts/gleeunit.cache b/aoc2023/build/packages/tom/build/lsp/erlang/gleeunit/_gleam_artefacts/gleeunit.cache Binary files differnew file mode 100644 index 0000000..8f060b4 --- /dev/null +++ b/aoc2023/build/packages/tom/build/lsp/erlang/gleeunit/_gleam_artefacts/gleeunit.cache diff --git a/aoc2023/build/packages/tom/build/lsp/erlang/gleeunit/_gleam_artefacts/gleeunit.cache_meta b/aoc2023/build/packages/tom/build/lsp/erlang/gleeunit/_gleam_artefacts/gleeunit.cache_meta Binary files differnew file mode 100644 index 0000000..1c4228b --- /dev/null +++ b/aoc2023/build/packages/tom/build/lsp/erlang/gleeunit/_gleam_artefacts/gleeunit.cache_meta diff --git a/aoc2023/build/packages/tom/build/lsp/erlang/gleeunit/_gleam_artefacts/gleeunit@should.cache b/aoc2023/build/packages/tom/build/lsp/erlang/gleeunit/_gleam_artefacts/gleeunit@should.cache Binary files differnew file mode 100644 index 0000000..7c85b7e --- /dev/null +++ b/aoc2023/build/packages/tom/build/lsp/erlang/gleeunit/_gleam_artefacts/gleeunit@should.cache diff --git a/aoc2023/build/packages/tom/build/lsp/erlang/gleeunit/_gleam_artefacts/gleeunit@should.cache_meta b/aoc2023/build/packages/tom/build/lsp/erlang/gleeunit/_gleam_artefacts/gleeunit@should.cache_meta Binary files differnew file mode 100644 index 0000000..1b7dad9 --- /dev/null +++ b/aoc2023/build/packages/tom/build/lsp/erlang/gleeunit/_gleam_artefacts/gleeunit@should.cache_meta diff --git a/aoc2023/build/packages/tom/build/lsp/erlang/tom/_gleam_artefacts/tom.cache b/aoc2023/build/packages/tom/build/lsp/erlang/tom/_gleam_artefacts/tom.cache Binary files differnew file mode 100644 index 0000000..2bfceaf --- /dev/null +++ b/aoc2023/build/packages/tom/build/lsp/erlang/tom/_gleam_artefacts/tom.cache diff --git a/aoc2023/build/packages/tom/build/lsp/erlang/tom/_gleam_artefacts/tom.cache_meta b/aoc2023/build/packages/tom/build/lsp/erlang/tom/_gleam_artefacts/tom.cache_meta Binary files differnew file mode 100644 index 0000000..3371771 --- /dev/null +++ b/aoc2023/build/packages/tom/build/lsp/erlang/tom/_gleam_artefacts/tom.cache_meta diff --git a/aoc2023/build/packages/tom/build/lsp/javascript/gleam.lock b/aoc2023/build/packages/tom/build/lsp/javascript/gleam.lock new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/aoc2023/build/packages/tom/build/lsp/javascript/gleam.lock diff --git a/aoc2023/build/packages/tom/build/packages/gleam.lock b/aoc2023/build/packages/tom/build/packages/gleam.lock new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam.lock diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/LICENCE b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/LICENCE new file mode 100644 index 0000000..c1dabd0 --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/LICENCE @@ -0,0 +1,191 @@ + 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 + + Copyright 2018, Louis Pilfold <louis@lpil.uk>. + + 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/aoc2023/build/packages/tom/build/packages/gleam_stdlib/README.md b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/README.md new file mode 100644 index 0000000..05c68ca --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/README.md @@ -0,0 +1,39 @@ +# stdlib + +<a href="https://github.com/gleam-lang/stdlib/releases"><img src="https://img.shields.io/github/release/gleam-lang/stdlib" alt="GitHub release"></a> +<a href="https://discord.gg/Fm8Pwmy"><img src="https://img.shields.io/discord/768594524158427167?color=blue" alt="Discord chat"></a> + + +Gleam's standard library! +Documentation available on [HexDocs](https://hexdocs.pm/gleam_stdlib/). + +## Installation + +Add `gleam_stdlib` to your Gleam project. + +```sh +gleam add gleam_stdlib +``` + +## Usage + +Import the modules you want to use and write some code! + +```gleam +import gleam/string + +pub fn greet(name: String) -> String { + string.concat(["Hello ", name, "!"]) +} +``` + +## Targets + +Gleam's standard library supports both targets: Erlang and JavaScript. + +### Compatibility + +This library is compatible with all versions of Erlang/OTP, NodeJS, and +major browsers that are currently supported by their maintainers. If you +have a compatibility issue with any platform open an issue and we'll see +what we can do to help. diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/gleam.toml b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/gleam.toml new file mode 100644 index 0000000..28efd9f --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/gleam.toml @@ -0,0 +1,16 @@ +name = "gleam_stdlib" +version = "0.34.0" +gleam = ">= 0.32.0" +licences = ["Apache-2.0"] +description = "A standard library for the Gleam programming language" + +repository = { type = "github", user = "gleam-lang", repo = "stdlib" } +links = [ + { title = "Website", href = "https://gleam.run" }, + { title = "Sponsor", href = "https://github.com/sponsors/lpil" }, +] + +[javascript.deno] +allow_read = [ + "./", +] diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/include/gleam@dynamic_DecodeError.hrl b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/include/gleam@dynamic_DecodeError.hrl new file mode 100644 index 0000000..b1135f2 --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/include/gleam@dynamic_DecodeError.hrl @@ -0,0 +1,5 @@ +-record(decode_error, { + expected :: binary(), + found :: binary(), + path :: list(binary()) +}). diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/include/gleam@iterator_Iterator.hrl b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/include/gleam@iterator_Iterator.hrl new file mode 100644 index 0000000..b0d08dc --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/include/gleam@iterator_Iterator.hrl @@ -0,0 +1 @@ +-record(iterator, {continuation :: fun(() -> gleam@iterator:action(any()))}). diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/include/gleam@iterator_Next.hrl b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/include/gleam@iterator_Next.hrl new file mode 100644 index 0000000..1f61922 --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/include/gleam@iterator_Next.hrl @@ -0,0 +1 @@ +-record(next, {element :: any(), accumulator :: any()}). diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/include/gleam@queue_Queue.hrl b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/include/gleam@queue_Queue.hrl new file mode 100644 index 0000000..88ac25e --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/include/gleam@queue_Queue.hrl @@ -0,0 +1 @@ +-record(queue, {in :: list(any()), out :: list(any())}). diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/include/gleam@regex_CompileError.hrl b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/include/gleam@regex_CompileError.hrl new file mode 100644 index 0000000..ad5511e --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/include/gleam@regex_CompileError.hrl @@ -0,0 +1 @@ +-record(compile_error, {error :: binary(), byte_index :: integer()}). diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/include/gleam@regex_Match.hrl b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/include/gleam@regex_Match.hrl new file mode 100644 index 0000000..4216619 --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/include/gleam@regex_Match.hrl @@ -0,0 +1,4 @@ +-record(match, { + content :: binary(), + submatches :: list(gleam@option:option(binary())) +}). diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/include/gleam@regex_Options.hrl b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/include/gleam@regex_Options.hrl new file mode 100644 index 0000000..0074603 --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/include/gleam@regex_Options.hrl @@ -0,0 +1 @@ +-record(options, {case_insensitive :: boolean(), multi_line :: boolean()}). diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/include/gleam@set_Set.hrl b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/include/gleam@set_Set.hrl new file mode 100644 index 0000000..6e1e226 --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/include/gleam@set_Set.hrl @@ -0,0 +1 @@ +-record(set, {map :: gleam@dict:dict(any(), list(nil))}). diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/include/gleam@uri_Uri.hrl b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/include/gleam@uri_Uri.hrl new file mode 100644 index 0000000..50150f4 --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/include/gleam@uri_Uri.hrl @@ -0,0 +1,9 @@ +-record(uri, { + scheme :: gleam@option:option(binary()), + userinfo :: gleam@option:option(binary()), + host :: gleam@option:option(binary()), + port :: gleam@option:option(integer()), + path :: binary(), + 'query' :: gleam@option:option(binary()), + fragment :: gleam@option:option(binary()) +}). diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/dict.mjs b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/dict.mjs new file mode 100644 index 0000000..a8309e0 --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/dict.mjs @@ -0,0 +1,957 @@ +/** + * This file uses jsdoc to annotate types. + * These types can be checked using the typescript compiler with "checkjs" option. + */ + +import { isEqual } from "./gleam.mjs"; + +const referenceMap = new WeakMap(); +const tempDataView = new DataView(new ArrayBuffer(8)); +let referenceUID = 0; +/** + * hash the object by reference using a weak map and incrementing uid + * @param {any} o + * @returns {number} + */ +function hashByReference(o) { + const known = referenceMap.get(o); + if (known !== undefined) { + return known; + } + const hash = referenceUID++; + if (referenceUID === 0x7fffffff) { + referenceUID = 0; + } + referenceMap.set(o, hash); + return hash; +} +/** + * merge two hashes in an order sensitive way + * @param {number} a + * @param {number} b + * @returns {number} + */ +function hashMerge(a, b) { + return (a ^ (b + 0x9e3779b9 + (a << 6) + (a >> 2))) | 0; +} +/** + * standard string hash popularised by java + * @param {string} s + * @returns {number} + */ +function hashString(s) { + let hash = 0; + const len = s.length; + for (let i = 0; i < len; i++) { + hash = (Math.imul(31, hash) + s.charCodeAt(i)) | 0; + } + return hash; +} +/** + * hash a number by converting to two integers and do some jumbling + * @param {number} n + * @returns {number} + */ +function hashNumber(n) { + tempDataView.setFloat64(0, n); + const i = tempDataView.getInt32(0); + const j = tempDataView.getInt32(4); + return Math.imul(0x45d9f3b, (i >> 16) ^ i) ^ j; +} +/** + * hash a BigInt by converting it to a string and hashing that + * @param {BigInt} n + * @returns {number} + */ +function hashBigInt(n) { + return hashString(n.toString()); +} +/** + * hash any js object + * @param {any} o + * @returns {number} + */ +function hashObject(o) { + const proto = Object.getPrototypeOf(o); + if (proto !== null && typeof proto.hashCode === "function") { + try { + const code = o.hashCode(o); + if (typeof code === "number") { + return code; + } + } catch {} + } + if (o instanceof Promise || o instanceof WeakSet || o instanceof WeakMap) { + return hashByReference(o); + } + if (o instanceof Date) { + return hashNumber(o.getTime()); + } + let h = 0; + if (o instanceof ArrayBuffer) { + o = new Uint8Array(o); + } + if (Array.isArray(o) || o instanceof Uint8Array) { + for (let i = 0; i < o.length; i++) { + h = (Math.imul(31, h) + getHash(o[i])) | 0; + } + } else if (o instanceof Set) { + o.forEach((v) => { + h = (h + getHash(v)) | 0; + }); + } else if (o instanceof Map) { + o.forEach((v, k) => { + h = (h + hashMerge(getHash(v), getHash(k))) | 0; + }); + } else { + const keys = Object.keys(o); + for (let i = 0; i < keys.length; i++) { + const k = keys[i]; + const v = o[k]; + h = (h + hashMerge(getHash(v), hashString(k))) | 0; + } + } + return h; +} +/** + * hash any js value + * @param {any} u + * @returns {number} + */ +export function getHash(u) { + if (u === null) return 0x42108422; + if (u === undefined) return 0x42108423; + if (u === true) return 0x42108421; + if (u === false) return 0x42108420; + switch (typeof u) { + case "number": + return hashNumber(u); + case "string": + return hashString(u); + case "bigint": + return hashBigInt(u); + case "object": + return hashObject(u); + case "symbol": + return hashByReference(u); + case "function": + return hashByReference(u); + default: + return 0; // should be unreachable + } +} +/** + * @template K,V + * @typedef {ArrayNode<K,V> | IndexNode<K,V> | CollisionNode<K,V>} Node + */ +/** + * @template K,V + * @typedef {{ type: typeof ENTRY, k: K, v: V }} Entry + */ +/** + * @template K,V + * @typedef {{ type: typeof ARRAY_NODE, size: number, array: (undefined | Entry<K,V> | Node<K,V>)[] }} ArrayNode + */ +/** + * @template K,V + * @typedef {{ type: typeof INDEX_NODE, bitmap: number, array: (Entry<K,V> | Node<K,V>)[] }} IndexNode + */ +/** + * @template K,V + * @typedef {{ type: typeof COLLISION_NODE, hash: number, array: Entry<K, V>[] }} CollisionNode + */ +/** + * @typedef {{ val: boolean }} Flag + */ +const SHIFT = 5; // number of bits you need to shift by to get the next bucket +const BUCKET_SIZE = Math.pow(2, SHIFT); +const MASK = BUCKET_SIZE - 1; // used to zero out all bits not in the bucket +const MAX_INDEX_NODE = BUCKET_SIZE / 2; // when does index node grow into array node +const MIN_ARRAY_NODE = BUCKET_SIZE / 4; // when does array node shrink to index node +const ENTRY = 0; +const ARRAY_NODE = 1; +const INDEX_NODE = 2; +const COLLISION_NODE = 3; +/** @type {IndexNode<any,any>} */ +const EMPTY = { + type: INDEX_NODE, + bitmap: 0, + array: [], +}; +/** + * Mask the hash to get only the bucket corresponding to shift + * @param {number} hash + * @param {number} shift + * @returns {number} + */ +function mask(hash, shift) { + return (hash >>> shift) & MASK; +} +/** + * Set only the Nth bit where N is the masked hash + * @param {number} hash + * @param {number} shift + * @returns {number} + */ +function bitpos(hash, shift) { + return 1 << mask(hash, shift); +} +/** + * Count the number of 1 bits in a number + * @param {number} x + * @returns {number} + */ +function bitcount(x) { + x -= (x >> 1) & 0x55555555; + x = (x & 0x33333333) + ((x >> 2) & 0x33333333); + x = (x + (x >> 4)) & 0x0f0f0f0f; + x += x >> 8; + x += x >> 16; + return x & 0x7f; +} +/** + * Calculate the array index of an item in a bitmap index node + * @param {number} bitmap + * @param {number} bit + * @returns {number} + */ +function index(bitmap, bit) { + return bitcount(bitmap & (bit - 1)); +} +/** + * Efficiently copy an array and set one value at an index + * @template T + * @param {T[]} arr + * @param {number} at + * @param {T} val + * @returns {T[]} + */ +function cloneAndSet(arr, at, val) { + const len = arr.length; + const out = new Array(len); + for (let i = 0; i < len; ++i) { + out[i] = arr[i]; + } + out[at] = val; + return out; +} +/** + * Efficiently copy an array and insert one value at an index + * @template T + * @param {T[]} arr + * @param {number} at + * @param {T} val + * @returns {T[]} + */ +function spliceIn(arr, at, val) { + const len = arr.length; + const out = new Array(len + 1); + let i = 0; + let g = 0; + while (i < at) { + out[g++] = arr[i++]; + } + out[g++] = val; + while (i < len) { + out[g++] = arr[i++]; + } + return out; +} +/** + * Efficiently copy an array and remove one value at an index + * @template T + * @param {T[]} arr + * @param {number} at + * @returns {T[]} + */ +function spliceOut(arr, at) { + const len = arr.length; + const out = new Array(len - 1); + let i = 0; + let g = 0; + while (i < at) { + out[g++] = arr[i++]; + } + ++i; + while (i < len) { + out[g++] = arr[i++]; + } + return out; +} +/** + * Create a new node containing two entries + * @template K,V + * @param {number} shift + * @param {K} key1 + * @param {V} val1 + * @param {number} key2hash + * @param {K} key2 + * @param {V} val2 + * @returns {Node<K,V>} + */ +function createNode(shift, key1, val1, key2hash, key2, val2) { + const key1hash = getHash(key1); + if (key1hash === key2hash) { + return { + type: COLLISION_NODE, + hash: key1hash, + array: [ + { type: ENTRY, k: key1, v: val1 }, + { type: ENTRY, k: key2, v: val2 }, + ], + }; + } + const addedLeaf = { val: false }; + return assoc( + assocIndex(EMPTY, shift, key1hash, key1, val1, addedLeaf), + shift, + key2hash, + key2, + val2, + addedLeaf + ); +} +/** + * @template T,K,V + * @callback AssocFunction + * @param {T} root + * @param {number} shift + * @param {number} hash + * @param {K} key + * @param {V} val + * @param {Flag} addedLeaf + * @returns {Node<K,V>} + */ +/** + * Associate a node with a new entry, creating a new node + * @template T,K,V + * @type {AssocFunction<Node<K,V>,K,V>} + */ +function assoc(root, shift, hash, key, val, addedLeaf) { + switch (root.type) { + case ARRAY_NODE: + return assocArray(root, shift, hash, key, val, addedLeaf); + case INDEX_NODE: + return assocIndex(root, shift, hash, key, val, addedLeaf); + case COLLISION_NODE: + return assocCollision(root, shift, hash, key, val, addedLeaf); + } +} +/** + * @template T,K,V + * @type {AssocFunction<ArrayNode<K,V>,K,V>} + */ +function assocArray(root, shift, hash, key, val, addedLeaf) { + const idx = mask(hash, shift); + const node = root.array[idx]; + // if the corresponding index is empty set the index to a newly created node + if (node === undefined) { + addedLeaf.val = true; + return { + type: ARRAY_NODE, + size: root.size + 1, + array: cloneAndSet(root.array, idx, { type: ENTRY, k: key, v: val }), + }; + } + if (node.type === ENTRY) { + // if keys are equal replace the entry + if (isEqual(key, node.k)) { + if (val === node.v) { + return root; + } + return { + type: ARRAY_NODE, + size: root.size, + array: cloneAndSet(root.array, idx, { + type: ENTRY, + k: key, + v: val, + }), + }; + } + // otherwise upgrade the entry to a node and insert + addedLeaf.val = true; + return { + type: ARRAY_NODE, + size: root.size, + array: cloneAndSet( + root.array, + idx, + createNode(shift + SHIFT, node.k, node.v, hash, key, val) + ), + }; + } + // otherwise call assoc on the child node + const n = assoc(node, shift + SHIFT, hash, key, val, addedLeaf); + // if the child node hasn't changed just return the old root + if (n === node) { + return root; + } + // otherwise set the index to the new node + return { + type: ARRAY_NODE, + size: root.size, + array: cloneAndSet(root.array, idx, n), + }; +} +/** + * @template T,K,V + * @type {AssocFunction<IndexNode<K,V>,K,V>} + */ +function assocIndex(root, shift, hash, key, val, addedLeaf) { + const bit = bitpos(hash, shift); + const idx = index(root.bitmap, bit); + // if there is already a item at this hash index.. + if ((root.bitmap & bit) !== 0) { + // if there is a node at the index (not an entry), call assoc on the child node + const node = root.array[idx]; + if (node.type !== ENTRY) { + const n = assoc(node, shift + SHIFT, hash, key, val, addedLeaf); + if (n === node) { + return root; + } + return { + type: INDEX_NODE, + bitmap: root.bitmap, + array: cloneAndSet(root.array, idx, n), + }; + } + // otherwise there is an entry at the index + // if the keys are equal replace the entry with the updated value + const nodeKey = node.k; + if (isEqual(key, nodeKey)) { + if (val === node.v) { + return root; + } + return { + type: INDEX_NODE, + bitmap: root.bitmap, + array: cloneAndSet(root.array, idx, { + type: ENTRY, + k: key, + v: val, + }), + }; + } + // if the keys are not equal, replace the entry with a new child node + addedLeaf.val = true; + return { + type: INDEX_NODE, + bitmap: root.bitmap, + array: cloneAndSet( + root.array, + idx, + createNode(shift + SHIFT, nodeKey, node.v, hash, key, val) + ), + }; + } else { + // else there is currently no item at the hash index + const n = root.array.length; + // if the number of nodes is at the maximum, expand this node into an array node + if (n >= MAX_INDEX_NODE) { + // create a 32 length array for the new array node (one for each bit in the hash) + const nodes = new Array(32); + // create and insert a node for the new entry + const jdx = mask(hash, shift); + nodes[jdx] = assocIndex(EMPTY, shift + SHIFT, hash, key, val, addedLeaf); + let j = 0; + let bitmap = root.bitmap; + // place each item in the index node into the correct spot in the array node + // loop through all 32 bits / array positions + for (let i = 0; i < 32; i++) { + if ((bitmap & 1) !== 0) { + const node = root.array[j++]; + nodes[i] = node; + } + // shift the bitmap to process the next bit + bitmap = bitmap >>> 1; + } + return { + type: ARRAY_NODE, + size: n + 1, + array: nodes, + }; + } else { + // else there is still space in this index node + // simply insert a new entry at the hash index + const newArray = spliceIn(root.array, idx, { + type: ENTRY, + k: key, + v: val, + }); + addedLeaf.val = true; + return { + type: INDEX_NODE, + bitmap: root.bitmap | bit, + array: newArray, + }; + } + } +} +/** + * @template T,K,V + * @type {AssocFunction<CollisionNode<K,V>,K,V>} + */ +function assocCollision(root, shift, hash, key, val, addedLeaf) { + // if there is a hash collision + if (hash === root.hash) { + const idx = collisionIndexOf(root, key); + // if this key already exists replace the entry with the new value + if (idx !== -1) { + const entry = root.array[idx]; + if (entry.v === val) { + return root; + } + return { + type: COLLISION_NODE, + hash: hash, + array: cloneAndSet(root.array, idx, { type: ENTRY, k: key, v: val }), + }; + } + // otherwise insert the entry at the end of the array + const size = root.array.length; + addedLeaf.val = true; + return { + type: COLLISION_NODE, + hash: hash, + array: cloneAndSet(root.array, size, { type: ENTRY, k: key, v: val }), + }; + } + // if there is no hash collision, upgrade to an index node + return assoc( + { + type: INDEX_NODE, + bitmap: bitpos(root.hash, shift), + array: [root], + }, + shift, + hash, + key, + val, + addedLeaf + ); +} +/** + * Find the index of a key in the collision node's array + * @template K,V + * @param {CollisionNode<K,V>} root + * @param {K} key + * @returns {number} + */ +function collisionIndexOf(root, key) { + const size = root.array.length; + for (let i = 0; i < size; i++) { + if (isEqual(key, root.array[i].k)) { + return i; + } + } + return -1; +} +/** + * @template T,K,V + * @callback FindFunction + * @param {T} root + * @param {number} shift + * @param {number} hash + * @param {K} key + * @returns {undefined | Entry<K,V>} + */ +/** + * Return the found entry or undefined if not present in the root + * @template K,V + * @type {FindFunction<Node<K,V>,K,V>} + */ +function find(root, shift, hash, key) { + switch (root.type) { + case ARRAY_NODE: + return findArray(root, shift, hash, key); + case INDEX_NODE: + return findIndex(root, shift, hash, key); + case COLLISION_NODE: + return findCollision(root, key); + } +} +/** + * @template K,V + * @type {FindFunction<ArrayNode<K,V>,K,V>} + */ +function findArray(root, shift, hash, key) { + const idx = mask(hash, shift); + const node = root.array[idx]; + if (node === undefined) { + return undefined; + } + if (node.type !== ENTRY) { + return find(node, shift + SHIFT, hash, key); + } + if (isEqual(key, node.k)) { + return node; + } + return undefined; +} +/** + * @template K,V + * @type {FindFunction<IndexNode<K,V>,K,V>} + */ +function findIndex(root, shift, hash, key) { + const bit = bitpos(hash, shift); + if ((root.bitmap & bit) === 0) { + return undefined; + } + const idx = index(root.bitmap, bit); + const node = root.array[idx]; + if (node.type !== ENTRY) { + return find(node, shift + SHIFT, hash, key); + } + if (isEqual(key, node.k)) { + return node; + } + return undefined; +} +/** + * @template K,V + * @param {CollisionNode<K,V>} root + * @param {K} key + * @returns {undefined | Entry<K,V>} + */ +function findCollision(root, key) { + const idx = collisionIndexOf(root, key); + if (idx < 0) { + return undefined; + } + return root.array[idx]; +} +/** + * @template T,K,V + * @callback WithoutFunction + * @param {T} root + * @param {number} shift + * @param {number} hash + * @param {K} key + * @returns {undefined | Node<K,V>} + */ +/** + * Remove an entry from the root, returning the updated root. + * Returns undefined if the node should be removed from the parent. + * @template K,V + * @type {WithoutFunction<Node<K,V>,K,V>} + * */ +function without(root, shift, hash, key) { + switch (root.type) { + case ARRAY_NODE: + return withoutArray(root, shift, hash, key); + case INDEX_NODE: + return withoutIndex(root, shift, hash, key); + case COLLISION_NODE: + return withoutCollision(root, key); + } +} +/** + * @template K,V + * @type {WithoutFunction<ArrayNode<K,V>,K,V>} + */ +function withoutArray(root, shift, hash, key) { + const idx = mask(hash, shift); + const node = root.array[idx]; + if (node === undefined) { + return root; // already empty + } + let n = undefined; + // if node is an entry and the keys are not equal there is nothing to remove + // if node is not an entry do a recursive call + if (node.type === ENTRY) { + if (!isEqual(node.k, key)) { + return root; // no changes + } + } else { + n = without(node, shift + SHIFT, hash, key); + if (n === node) { + return root; // no changes + } + } + // if the recursive call returned undefined the node should be removed + if (n === undefined) { + // if the number of child nodes is at the minimum, pack into an index node + if (root.size <= MIN_ARRAY_NODE) { + const arr = root.array; + const out = new Array(root.size - 1); + let i = 0; + let j = 0; + let bitmap = 0; + while (i < idx) { + const nv = arr[i]; + if (nv !== undefined) { + out[j] = nv; + bitmap |= 1 << i; + ++j; + } + ++i; + } + ++i; // skip copying the removed node + while (i < arr.length) { + const nv = arr[i]; + if (nv !== undefined) { + out[j] = nv; + bitmap |= 1 << i; + ++j; + } + ++i; + } + return { + type: INDEX_NODE, + bitmap: bitmap, + array: out, + }; + } + return { + type: ARRAY_NODE, + size: root.size - 1, + array: cloneAndSet(root.array, idx, n), + }; + } + return { + type: ARRAY_NODE, + size: root.size, + array: cloneAndSet(root.array, idx, n), + }; +} +/** + * @template K,V + * @type {WithoutFunction<IndexNode<K,V>,K,V>} + */ +function withoutIndex(root, shift, hash, key) { + const bit = bitpos(hash, shift); + if ((root.bitmap & bit) === 0) { + return root; // already empty + } + const idx = index(root.bitmap, bit); + const node = root.array[idx]; + // if the item is not an entry + if (node.type !== ENTRY) { + const n = without(node, shift + SHIFT, hash, key); + if (n === node) { + return root; // no changes + } + // if not undefined, the child node still has items, so update it + if (n !== undefined) { + return { + type: INDEX_NODE, + bitmap: root.bitmap, + array: cloneAndSet(root.array, idx, n), + }; + } + // otherwise the child node should be removed + // if it was the only child node, remove this node from the parent + if (root.bitmap === bit) { + return undefined; + } + // otherwise just remove the child node + return { + type: INDEX_NODE, + bitmap: root.bitmap ^ bit, + array: spliceOut(root.array, idx), + }; + } + // otherwise the item is an entry, remove it if the key matches + if (isEqual(key, node.k)) { + if (root.bitmap === bit) { + return undefined; + } + return { + type: INDEX_NODE, + bitmap: root.bitmap ^ bit, + array: spliceOut(root.array, idx), + }; + } + return root; +} +/** + * @template K,V + * @param {CollisionNode<K,V>} root + * @param {K} key + * @returns {undefined | Node<K,V>} + */ +function withoutCollision(root, key) { + const idx = collisionIndexOf(root, key); + // if the key not found, no changes + if (idx < 0) { + return root; + } + // otherwise the entry was found, remove it + // if it was the only entry in this node, remove the whole node + if (root.array.length === 1) { + return undefined; + } + // otherwise just remove the entry + return { + type: COLLISION_NODE, + hash: root.hash, + array: spliceOut(root.array, idx), + }; +} +/** + * @template K,V + * @param {undefined | Node<K,V>} root + * @param {(value:V,key:K)=>void} fn + * @returns {void} + */ +function forEach(root, fn) { + if (root === undefined) { + return; + } + const items = root.array; + const size = items.length; + for (let i = 0; i < size; i++) { + const item = items[i]; + if (item === undefined) { + continue; + } + if (item.type === ENTRY) { + fn(item.v, item.k); + continue; + } + forEach(item, fn); + } +} +/** + * Extra wrapper to keep track of Dict size and clean up the API + * @template K,V + */ +export default class Dict { + /** + * @template V + * @param {Record<string,V>} o + * @returns {Dict<string,V>} + */ + static fromObject(o) { + const keys = Object.keys(o); + /** @type Dict<string,V> */ + let m = Dict.new(); + for (let i = 0; i < keys.length; i++) { + const k = keys[i]; + m = m.set(k, o[k]); + } + return m; + } + /** + * @template K,V + * @param {Map<K,V>} o + * @returns {Dict<K,V>} + */ + static fromMap(o) { + /** @type Dict<K,V> */ + let m = Dict.new(); + o.forEach((v, k) => { + m = m.set(k, v); + }); + return m; + } + static new() { + return new Dict(undefined, 0); + } + /** + * @param {undefined | Node<K,V>} root + * @param {number} size + */ + constructor(root, size) { + this.root = root; + this.size = size; + } + /** + * @template NotFound + * @param {K} key + * @param {NotFound} notFound + * @returns {NotFound | V} + */ + get(key, notFound) { + if (this.root === undefined) { + return notFound; + } + const found = find(this.root, 0, getHash(key), key); + if (found === undefined) { + return notFound; + } + return found.v; + } + /** + * @param {K} key + * @param {V} val + * @returns {Dict<K,V>} + */ + set(key, val) { + const addedLeaf = { val: false }; + const root = this.root === undefined ? EMPTY : this.root; + const newRoot = assoc(root, 0, getHash(key), key, val, addedLeaf); + if (newRoot === this.root) { + return this; + } + return new Dict(newRoot, addedLeaf.val ? this.size + 1 : this.size); + } + /** + * @param {K} key + * @returns {Dict<K,V>} + */ + delete(key) { + if (this.root === undefined) { + return this; + } + const newRoot = without(this.root, 0, getHash(key), key); + if (newRoot === this.root) { + return this; + } + if (newRoot === undefined) { + return Dict.new(); + } + return new Dict(newRoot, this.size - 1); + } + /** + * @param {K} key + * @returns {boolean} + */ + has(key) { + if (this.root === undefined) { + return false; + } + return find(this.root, 0, getHash(key), key) !== undefined; + } + /** + * @returns {[K,V][]} + */ + entries() { + if (this.root === undefined) { + return []; + } + /** @type [K,V][] */ + const result = []; + this.forEach((v, k) => result.push([k, v])); + return result; + } + /** + * + * @param {(val:V,key:K)=>void} fn + */ + forEach(fn) { + forEach(this.root, fn); + } + hashCode() { + let h = 0; + this.forEach((v, k) => { + h = (h + hashMerge(getHash(v), getHash(k))) | 0; + }); + return h; + } + /** + * @param {unknown} o + * @returns {boolean} + */ + equals(o) { + if (!(o instanceof Dict) || this.size !== o.size) { + return false; + } + let equal = true; + this.forEach((v, k) => { + equal = equal && isEqual(o.get(k, !v), v); + }); + return equal; + } +} diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/base.gleam b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/base.gleam new file mode 100644 index 0000000..eab2f0b --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/base.gleam @@ -0,0 +1,21 @@ +import gleam/bit_array + +@deprecated("Please use `base64_encode` in the `gleam/bit_array` module instead.") +pub fn encode64(input: BitArray, padding: Bool) -> String { + bit_array.base64_encode(input, padding) +} + +@deprecated("Please use `base64_decode` in the `gleam/bit_array` module instead.") +pub fn decode64(encoded: String) -> Result(BitArray, Nil) { + bit_array.base64_decode(encoded) +} + +@deprecated("Please use `base64_url_encode` in the `gleam/bit_array` module instead.") +pub fn url_encode64(input: BitArray, padding: Bool) -> String { + bit_array.base64_url_encode(input, padding) +} + +@deprecated("Please use `base64_url_decode` in the `gleam/bit_array` module instead.") +pub fn url_decode64(encoded: String) -> Result(BitArray, Nil) { + bit_array.base64_url_decode(encoded) +} diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/bit_array.gleam b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/bit_array.gleam new file mode 100644 index 0000000..79860e9 --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/bit_array.gleam @@ -0,0 +1,157 @@ +//// BitArrays are a sequence of binary data of any length. + +import gleam/string + +/// Converts a UTF-8 `String` type into a `BitArray`. +/// +@external(erlang, "gleam_stdlib", "identity") +@external(javascript, "../gleam_stdlib.mjs", "bit_array_from_string") +pub fn from_string(x: String) -> BitArray + +/// Returns an integer which is the number of bytes in the bit array. +/// +@external(erlang, "erlang", "byte_size") +@external(javascript, "../gleam_stdlib.mjs", "length") +pub fn byte_size(x: BitArray) -> Int + +/// Creates a new bit array by joining two bit arrays. +/// +/// ## Examples +/// +/// ```gleam +/// > append(to: from_string("butter"), suffix: from_string("fly")) +/// from_string("butterfly") +/// ``` +/// +pub fn append(to first: BitArray, suffix second: BitArray) -> BitArray { + concat([first, second]) +} + +/// Extracts a sub-section of a bit array. +/// +/// The slice will start at given position and continue up to specified +/// length. +/// A negative length can be used to extract bytes at the end of a bit array. +/// +/// This function runs in constant time. +/// +@external(erlang, "gleam_stdlib", "bit_array_slice") +@external(javascript, "../gleam_stdlib.mjs", "bit_array_slice") +pub fn slice( + from string: BitArray, + at position: Int, + take length: Int, +) -> Result(BitArray, Nil) + +/// Tests to see whether a bit array is valid UTF-8. +/// +pub fn is_utf8(bits: BitArray) -> Bool { + do_is_utf8(bits) +} + +@target(erlang) +fn do_is_utf8(bits: BitArray) -> Bool { + case bits { + <<>> -> True + <<_:utf8, rest:bytes>> -> do_is_utf8(rest) + _ -> False + } +} + +@target(javascript) +fn do_is_utf8(bits: BitArray) -> Bool { + case to_string(bits) { + Ok(_) -> True + _ -> False + } +} + +/// Converts a bit array to a string. +/// +/// Returns an error if the bit array is invalid UTF-8 data. +/// +pub fn to_string(bits: BitArray) -> Result(String, Nil) { + do_to_string(bits) +} + +@target(erlang) +@external(erlang, "gleam_stdlib", "identity") +fn unsafe_to_string(a: BitArray) -> String + +@target(erlang) +fn do_to_string(bits: BitArray) -> Result(String, Nil) { + case is_utf8(bits) { + True -> Ok(unsafe_to_string(bits)) + False -> Error(Nil) + } +} + +@target(javascript) +@external(javascript, "../gleam_stdlib.mjs", "bit_array_to_string") +fn do_to_string(a: BitArray) -> Result(String, Nil) + +/// Creates a new bit array by joining multiple binaries. +/// +/// ## Examples +/// +/// ```gleam +/// > concat([from_string("butter"), from_string("fly")]) +/// from_string("butterfly") +/// ``` +/// +@external(erlang, "gleam_stdlib", "bit_array_concat") +@external(javascript, "../gleam_stdlib.mjs", "bit_array_concat") +pub fn concat(bit_arrays: List(BitArray)) -> BitArray + +/// Encodes a BitArray into a base 64 encoded string. +/// +pub fn base64_encode(input: BitArray, padding: Bool) -> String { + let encoded = encode64(input) + case padding { + True -> encoded + False -> string.replace(encoded, "=", "") + } +} + +@external(erlang, "base64", "encode") +@external(javascript, "../gleam_stdlib.mjs", "encode64") +fn encode64(a: BitArray) -> String + +/// Decodes a base 64 encoded string into a `BitArray`. +/// +pub fn base64_decode(encoded: String) -> Result(BitArray, Nil) { + let padded = case byte_size(from_string(encoded)) % 4 { + 0 -> encoded + n -> string.append(encoded, string.repeat("=", 4 - n)) + } + decode64(padded) +} + +@external(erlang, "gleam_stdlib", "base_decode64") +@external(javascript, "../gleam_stdlib.mjs", "decode64") +fn decode64(a: String) -> Result(BitArray, Nil) + +/// Encodes a `BitArray` into a base 64 encoded string with URL and filename safe alphabet. +/// +pub fn base64_url_encode(input: BitArray, padding: Bool) -> String { + base64_encode(input, padding) + |> string.replace("+", "-") + |> string.replace("/", "_") +} + +/// Decodes a base 64 encoded string with URL and filename safe alphabet into a `BitArray`. +/// +pub fn base64_url_decode(encoded: String) -> Result(BitArray, Nil) { + encoded + |> string.replace("-", "+") + |> string.replace("_", "/") + |> base64_decode() +} + +@external(erlang, "binary", "encode_hex") +@external(javascript, "../gleam_stdlib.mjs", "base16_encode") +pub fn base16_encode(input: BitArray) -> String + +@external(erlang, "gleam_stdlib", "base16_decode") +@external(javascript, "../gleam_stdlib.mjs", "base16_decode") +pub fn base16_decode(input: String) -> Result(BitArray, Nil) diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/bit_builder.gleam b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/bit_builder.gleam new file mode 100644 index 0000000..ce6fe52 --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/bit_builder.gleam @@ -0,0 +1,80 @@ +//// This module has been deprecated in favour of `gleam/bytes_builder`. + +import gleam/bytes_builder +import gleam/string_builder.{type StringBuilder} + +pub type BitBuilder = + bytes_builder.BytesBuilder + +@deprecated("Please use the `gleam/bytes_builder` module instead.") +pub fn new() -> BitBuilder { + bytes_builder.new() +} + +@deprecated("Please use the `gleam/bytes_builder` module instead.") +pub fn prepend(to: BitBuilder, prefix: BitArray) -> BitBuilder { + bytes_builder.prepend(to, prefix) +} + +@deprecated("Please use the `gleam/bytes_builder` module instead.") +pub fn append(to: BitBuilder, suffix: BitArray) -> BitBuilder { + bytes_builder.append(to, suffix) +} + +@deprecated("Please use the `gleam/bytes_builder` module instead.") +pub fn prepend_builder(to: BitBuilder, prefix: BitBuilder) -> BitBuilder { + bytes_builder.prepend_builder(to, prefix) +} + +@deprecated("Please use the `gleam/bytes_builder` module instead.") +pub fn append_builder( + to first: BitBuilder, + suffix second: BitBuilder, +) -> BitBuilder { + bytes_builder.append_builder(first, second) +} + +@deprecated("Please use the `gleam/bytes_builder` module instead.") +pub fn prepend_string(to: BitBuilder, prefix: String) -> BitBuilder { + bytes_builder.prepend_string(to, prefix) +} + +@deprecated("Please use the `gleam/bytes_builder` module instead.") +pub fn append_string(to: BitBuilder, suffix: String) -> BitBuilder { + bytes_builder.append_string(to, suffix) +} + +@deprecated("Please use the `gleam/bytes_builder` module instead.") +pub fn concat(builders: List(BitBuilder)) -> BitBuilder { + bytes_builder.concat(builders) +} + +@deprecated("Please use the `gleam/bytes_builder` module instead.") +pub fn concat_bit_strings(bits: List(BitArray)) -> BitBuilder { + bytes_builder.concat_bit_arrays(bits) +} + +@deprecated("Please use the `gleam/bytes_builder` module instead.") +pub fn from_string(string: String) -> BitBuilder { + bytes_builder.from_string(string) +} + +@deprecated("Please use the `gleam/bytes_builder` module instead.") +pub fn from_string_builder(builder: StringBuilder) -> BitBuilder { + bytes_builder.from_string_builder(builder) +} + +@deprecated("Please use the `gleam/bytes_builder` module instead.") +pub fn from_bit_string(bits: BitArray) -> BitBuilder { + bytes_builder.from_bit_array(bits) +} + +@deprecated("Please use the `gleam/bytes_builder` module instead.") +pub fn to_bit_string(builder: BitBuilder) -> BitArray { + bytes_builder.to_bit_array(builder) +} + +@deprecated("Please use the `gleam/bytes_builder` module instead.") +pub fn byte_size(builder: BitBuilder) -> Int { + bytes_builder.byte_size(builder) +} diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/bit_string.gleam b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/bit_string.gleam new file mode 100644 index 0000000..b703da0 --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/bit_string.gleam @@ -0,0 +1,43 @@ +//// This module has been deprecated. Please use the `gleam/bit_array` module +//// instead. + +import gleam/bit_array + +@deprecated("Please use the `gleam/bit_array` module instead.") +pub fn from_string(x: String) -> BitArray { + bit_array.from_string(x) +} + +@deprecated("Please use the `gleam/bit_array` module instead.") +pub fn byte_size(x: BitArray) -> Int { + bit_array.byte_size(x) +} + +@deprecated("Please use the `gleam/bit_array` module instead.") +pub fn append(to first: BitArray, suffix second: BitArray) -> BitArray { + bit_array.append(first, second) +} + +@deprecated("Please use the `gleam/bit_array` module instead.") +pub fn slice( + from string: BitArray, + at position: Int, + take length: Int, +) -> Result(BitArray, Nil) { + bit_array.slice(string, position, length) +} + +@deprecated("Please use the `gleam/bit_array` module instead.") +pub fn is_utf8(bits: BitArray) -> Bool { + bit_array.is_utf8(bits) +} + +@deprecated("Please use the `gleam/bit_array` module instead.") +pub fn to_string(bits: BitArray) -> Result(String, Nil) { + bit_array.to_string(bits) +} + +@deprecated("Please use the `gleam/bit_array` module instead.") +pub fn concat(bit_strings: List(BitArray)) -> BitArray { + bit_array.concat(bit_strings) +} diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/bool.gleam b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/bool.gleam new file mode 100644 index 0000000..91bd6b7 --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/bool.gleam @@ -0,0 +1,428 @@ +//// A type with two possible values, `True` and `False`. Used to indicate whether +//// things are... true or false! +//// +//// Often is it clearer and offers more type safety to define a custom type +//// than to use `Bool`. For example, rather than having a `is_teacher: Bool` +//// field consider having a `role: SchoolRole` field where `SchoolRole` is a custom +//// type that can be either `Student` or `Teacher`. + +import gleam/order.{type Order} + +/// Returns the and of two bools, but it evaluates both arguments. +/// +/// It's the function equivalent of the `&&` operator. +/// This function is useful in higher order functions or pipes. +/// +/// ## Examples +/// +/// ```gleam +/// > and(True, True) +/// True +/// ``` +/// +/// ```gleam +/// > and(False, True) +/// False +/// ``` +/// +/// ```gleam +/// > False |> and(True) +/// False +/// ``` +/// +pub fn and(a: Bool, b: Bool) -> Bool { + a && b +} + +/// Returns the or of two bools, but it evaluates both arguments. +/// +/// It's the function equivalent of the `||` operator. +/// This function is useful in higher order functions or pipes. +/// +/// ## Examples +/// +/// ```gleam +/// > or(True, True) +/// True +/// ``` +/// +/// ```gleam +/// > or(False, True) +/// True +/// ``` +/// +/// ```gleam +/// > False |> or(True) +/// True +/// ``` +/// +pub fn or(a: Bool, b: Bool) -> Bool { + a || b +} + +/// Returns the opposite bool value. +/// +/// This is the same as the `!` or `not` operators in some other languages. +/// +/// ## Examples +/// +/// ```gleam +/// > negate(True) +/// False +/// ``` +/// +/// ```gleam +/// > negate(False) +/// True +/// ``` +/// +pub fn negate(bool: Bool) -> Bool { + case bool { + True -> False + False -> True + } +} + +/// Returns the nor of two bools. +/// +/// ## Examples +/// +/// ```gleam +/// > nor(False, False) +/// True +/// ``` +/// +/// ```gleam +/// > nor(False, True) +/// False +/// ``` +/// +/// ```gleam +/// > nor(True, False) +/// False +/// ``` +/// +/// ```gleam +/// > nor(True, True) +/// False +/// ``` +/// +pub fn nor(a: Bool, b: Bool) -> Bool { + case a, b { + False, False -> True + False, True -> False + True, False -> False + True, True -> False + } +} + +/// Returns the nand of two bools. +/// +/// ## Examples +/// +/// ```gleam +/// > nand(False, False) +/// True +/// ``` +/// +/// ```gleam +/// > nand(False, True) +/// True +/// ``` +/// +/// ```gleam +/// > nand(True, False) +/// True +/// ``` +/// +/// ```gleam +/// > nand(True, True) +/// False +/// ``` +/// +pub fn nand(a: Bool, b: Bool) -> Bool { + case a, b { + False, False -> True + False, True -> True + True, False -> True + True, True -> False + } +} + +/// Returns the exclusive or of two bools. +/// +/// ## Examples +/// +/// ```gleam +/// > exclusive_or(False, False) +/// False +/// ``` +/// +/// ```gleam +/// > exclusive_or(False, True) +/// True +/// ``` +/// +/// ```gleam +/// > exclusive_or(True, False) +/// True +/// ``` +/// +/// ```gleam +/// > exclusive_or(True, True) +/// False +/// ``` +/// +pub fn exclusive_or(a: Bool, b: Bool) -> Bool { + case a, b { + False, False -> False + False, True -> True + True, False -> True + True, True -> False + } +} + +/// Returns the exclusive nor of two bools. +/// +/// ## Examples +/// +/// ```gleam +/// > exclusive_nor(False, False) +/// True +/// ``` +/// +/// ```gleam +/// > exclusive_nor(False, True) +/// False +/// ``` +/// +/// ```gleam +/// > exclusive_nor(True, False) +/// False +/// ``` +/// +/// ```gleam +/// > exclusive_nor(True, True) +/// True +/// ``` +/// +pub fn exclusive_nor(a: Bool, b: Bool) -> Bool { + case a, b { + False, False -> True + False, True -> False + True, False -> False + True, True -> True + } +} + +/// Compares two bools and returns the first value's `Order` to the second. +/// +/// ## Examples +/// +/// ```gleam +/// > import gleam/order +/// > compare(True, False) +/// order.Gt +/// ``` +/// +pub fn compare(a: Bool, with b: Bool) -> Order { + case a, b { + True, True -> order.Eq + True, False -> order.Gt + False, False -> order.Eq + False, True -> order.Lt + } +} + +/// Returns `True` if either argument's value is `True`. +/// +/// ## Examples +/// +/// ```gleam +/// > max(True, False) +/// True +/// ``` +/// +/// ```gleam +/// > max(False, True) +/// True +/// ``` +/// +/// ```gleam +/// > max(False, False) +/// False +/// ``` +/// +pub fn max(a: Bool, b: Bool) -> Bool { + case a { + True -> True + False -> b + } +} + +/// Returns `False` if either bool value is `False`. +/// +/// ## Examples +/// +/// ```gleam +/// > min(True, False) +/// False +/// ``` +/// +/// ```gleam +/// > min(False, True) +/// False +/// +/// > min(False, False) +/// False +/// ``` +/// +pub fn min(a: Bool, b: Bool) -> Bool { + case a { + False -> False + True -> b + } +} + +/// Returns a numeric representation of the given bool. +/// +/// ## Examples +/// +/// ```gleam +/// > to_int(True) +/// 1 +/// +/// > to_int(False) +/// 0 +/// ``` +/// +pub fn to_int(bool: Bool) -> Int { + case bool { + False -> 0 + True -> 1 + } +} + +/// Returns a string representation of the given bool. +/// +/// ## Examples +/// +/// ```gleam +/// > to_string(True) +/// "True" +/// ``` +/// +/// ```gleam +/// > to_string(False) +/// "False" +/// ``` +/// +pub fn to_string(bool: Bool) -> String { + case bool { + False -> "False" + True -> "True" + } +} + +/// Run a callback function if the given bool is `False`, otherwise return a +/// default value. +/// +/// With a `use` expression this function can simulate the early-return pattern +/// found in some other programming languages. +/// +/// In a procedural language: +/// +/// ```js +/// if (predicate) return value; +/// // ... +/// ``` +/// +/// In Gleam with a `use` expression: +/// +/// ```gleam +/// use <- guard(when: predicate, return: value) +/// // ... +/// ``` +/// +/// Like everything in Gleam `use` is an expression, so it short circuits the +/// current block, not the entire function. As a result you can assign the value +/// to a variable: +/// +/// ```gleam +/// let x = { +/// use <- guard(when: predicate, return: value) +/// // ... +/// } +/// ``` +/// +/// Note that unlike in procedural languages the `return` value is evaluated +/// even when the predicate is `False`, so it is advisable not to perform +/// expensive computation there. +/// +/// +/// ## Examples +/// +/// ```gleam +/// > let name = "" +/// > use <- guard(when: name == "", return: "Welcome!") +/// > "Hello, " <> name +/// "Welcome!" +/// ``` +/// +/// ```gleam +/// > let name = "Kamaka" +/// > use <- guard(when: name == "", return: "Welcome!") +/// > "Hello, " <> name +/// "Hello, Kamaka" +/// ``` +/// +pub fn guard( + when requirement: Bool, + return consequence: t, + otherwise alternative: fn() -> t, +) -> t { + case requirement { + True -> consequence + False -> alternative() + } +} + +/// Runs a callback function if the given bool is `True`, otherwise runs an +/// alternative callback function. +/// +/// Useful when further computation should be delayed regardless of the given +/// bool's value. +/// +/// See [`guard`](#guard) for more info. +/// +/// ## Examples +/// +/// ```gleam +/// > let name = "Kamaka" +/// > let inquiry = fn() { "How may we address you?" } +/// > use <- lazy_guard(when: name == "", return: inquiry) +/// > "Hello, " <> name +/// "Hello, Kamaka" +/// ``` +/// +/// ```gleam +/// > import gleam/int +/// > let name = "" +/// > let greeting = fn() { "Hello, " <> name } +/// > use <- lazy_guard(when: name == "", otherwise: greeting) +/// > let number = int.random(1, 99) +/// > let name = "User " <> int.to_string(number) +/// > "Welcome, " <> name +/// "Welcome, User 54" +/// ``` +/// +pub fn lazy_guard( + when requirement: Bool, + return consequence: fn() -> a, + otherwise alternative: fn() -> a, +) -> a { + case requirement { + True -> consequence() + False -> alternative() + } +} diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/bytes_builder.gleam b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/bytes_builder.gleam new file mode 100644 index 0000000..20c145d --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/bytes_builder.gleam @@ -0,0 +1,197 @@ +//// BytesBuilder is a type used for efficiently concatenating bytes together +//// without copying. +//// +//// If we append one bit array to another the bit arrays must be copied to a +//// new location in memory so that they can sit together. This behaviour +//// enables efficient reading of the string but copying can be expensive, +//// especially if we want to join many bit arrays together. +//// +//// BytesBuilder is different in that it can be joined together in constant +//// time using minimal memory, and then can be efficiently converted to a +//// bit array using the `to_bit_array` function. +//// +//// Byte builders are always byte aligned, so that a number of bits that is not +//// divisible by 8 will be padded with 0s. +//// +//// On Erlang this type is compatible with Erlang's iolists. + +// TODO: pad bit arrays to byte boundaries when adding to a builder. +import gleam/string_builder.{type StringBuilder} +import gleam/list +import gleam/bit_array + +pub opaque type BytesBuilder { + Bytes(BitArray) + Text(StringBuilder) + Many(List(BytesBuilder)) +} + +/// Create an empty `BytesBuilder`. Useful as the start of a pipe chaining many +/// builders together. +/// +pub fn new() -> BytesBuilder { + concat([]) +} + +/// Prepends a bit array to the start of a builder. +/// +/// Runs in constant time. +/// +pub fn prepend(to second: BytesBuilder, prefix first: BitArray) -> BytesBuilder { + append_builder(from_bit_array(first), second) +} + +/// Appends a bit array to the end of a builder. +/// +/// Runs in constant time. +/// +pub fn append(to first: BytesBuilder, suffix second: BitArray) -> BytesBuilder { + append_builder(first, from_bit_array(second)) +} + +/// Prepends a builder onto the start of another. +/// +/// Runs in constant time. +/// +pub fn prepend_builder( + to second: BytesBuilder, + prefix first: BytesBuilder, +) -> BytesBuilder { + append_builder(first, second) +} + +/// Appends a builder onto the end of another. +/// +/// Runs in constant time. +/// +@external(erlang, "gleam_stdlib", "iodata_append") +pub fn append_builder( + to first: BytesBuilder, + suffix second: BytesBuilder, +) -> BytesBuilder { + case second { + Many(builders) -> Many([first, ..builders]) + _ -> Many([first, second]) + } +} + +/// Prepends a string onto the start of a builder. +/// +/// Runs in constant time when running on Erlang. +/// Runs in linear time with the length of the string otherwise. +/// +pub fn prepend_string( + to second: BytesBuilder, + prefix first: String, +) -> BytesBuilder { + append_builder(from_string(first), second) +} + +/// Appends a string onto the end of a builder. +/// +/// Runs in constant time when running on Erlang. +/// Runs in linear time with the length of the string otherwise. +/// +pub fn append_string( + to first: BytesBuilder, + suffix second: String, +) -> BytesBuilder { + append_builder(first, from_string(second)) +} + +/// Joins a list of builders into a single builder. +/// +/// Runs in constant time. +/// +@external(erlang, "gleam_stdlib", "identity") +pub fn concat(builders: List(BytesBuilder)) -> BytesBuilder { + Many(builders) +} + +/// Joins a list of bit arrays into a single builder. +/// +/// Runs in constant time. +/// +@external(erlang, "gleam_stdlib", "identity") +pub fn concat_bit_arrays(bits: List(BitArray)) -> BytesBuilder { + bits + |> list.map(fn(b) { from_bit_array(b) }) + |> concat() +} + +/// Creates a new builder from a string. +/// +/// Runs in constant time when running on Erlang. +/// Runs in linear time otherwise. +/// +@external(erlang, "gleam_stdlib", "wrap_list") +pub fn from_string(string: String) -> BytesBuilder { + Text(string_builder.from_string(string)) +} + +/// Creates a new builder from a string builder. +/// +/// Runs in constant time when running on Erlang. +/// Runs in linear time otherwise. +/// +@external(erlang, "gleam_stdlib", "wrap_list") +pub fn from_string_builder(builder: StringBuilder) -> BytesBuilder { + Text(builder) +} + +/// Creates a new builder from a bit array. +/// +/// Runs in constant time. +/// +@external(erlang, "gleam_stdlib", "wrap_list") +pub fn from_bit_array(bits: BitArray) -> BytesBuilder { + Bytes(bits) +} + +/// Turns an builder into a bit array. +/// +/// Runs in linear time. +/// +/// When running on Erlang this function is implemented natively by the +/// virtual machine and is highly optimised. +/// +@external(erlang, "erlang", "list_to_bitstring") +pub fn to_bit_array(builder: BytesBuilder) -> BitArray { + [[builder]] + |> to_list([]) + |> list.reverse + |> bit_array.concat +} + +fn to_list( + stack: List(List(BytesBuilder)), + acc: List(BitArray), +) -> List(BitArray) { + case stack { + [] -> acc + + [[], ..remaining_stack] -> to_list(remaining_stack, acc) + + [[Bytes(bits), ..rest], ..remaining_stack] -> + to_list([rest, ..remaining_stack], [bits, ..acc]) + + [[Text(builder), ..rest], ..remaining_stack] -> { + let bits = bit_array.from_string(string_builder.to_string(builder)) + to_list([rest, ..remaining_stack], [bits, ..acc]) + } + + [[Many(builders), ..rest], ..remaining_stack] -> + to_list([builders, rest, ..remaining_stack], acc) + } +} + +/// Returns the size of the builder's content in bytes. +/// +/// Runs in linear time. +/// +@external(erlang, "erlang", "iolist_size") +pub fn byte_size(builder: BytesBuilder) -> Int { + [[builder]] + |> to_list([]) + |> list.fold(0, fn(acc, builder) { bit_array.byte_size(builder) + acc }) +} diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/dict.gleam b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/dict.gleam new file mode 100644 index 0000000..cecb2ca --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/dict.gleam @@ -0,0 +1,493 @@ +import gleam/option.{type Option} + +/// A dictionary of keys and values. +/// +/// Any type can be used for the keys and values of a dict, but all the keys +/// must be of the same type and all the values must be of the same type. +/// +/// Each key can only be present in a dict once. +/// +/// Dicts are not ordered in any way, and any unintentional ordering is not to +/// be relied upon in your code as it may change in future versions of Erlang +/// or Gleam. +/// +/// See [the Erlang map module](https://erlang.org/doc/man/maps.html) for more +/// information. +/// +pub type Dict(key, value) + +/// Determines the number of key-value pairs in the dict. +/// This function runs in constant time and does not need to iterate the dict. +/// +/// ## Examples +/// +/// ```gleam +/// > new() |> size() +/// 0 +/// ``` +/// +/// ```gleam +/// > new() |> insert("key", "value") |> size() +/// 1 +/// ``` +/// +@external(erlang, "maps", "size") +@external(javascript, "../gleam_stdlib.mjs", "map_size") +pub fn size(dict: Dict(k, v)) -> Int + +/// Converts the dict to a list of 2-element tuples `#(key, value)`, one for +/// each key-value pair in the dict. +/// +/// The tuples in the list have no specific order. +/// +/// ## Examples +/// +/// ```gleam +/// > new() |> to_list() +/// [] +/// ``` +/// +/// ```gleam +/// > new() |> insert("key", 0) |> to_list() +/// [#("key", 0)] +/// ``` +/// +@external(erlang, "maps", "to_list") +@external(javascript, "../gleam_stdlib.mjs", "map_to_list") +pub fn to_list(dict: Dict(key, value)) -> List(#(key, value)) + +/// Converts a list of 2-element tuples `#(key, value)` to a dict. +/// +/// If two tuples have the same key the last one in the list will be the one +/// that is present in the dict. +/// +@external(erlang, "maps", "from_list") +pub fn from_list(list: List(#(k, v))) -> Dict(k, v) { + fold_list_of_pair(list, new()) +} + +fn fold_list_of_pair( + over list: List(#(k, v)), + from initial: Dict(k, v), +) -> Dict(k, v) { + case list { + [] -> initial + [x, ..rest] -> fold_list_of_pair(rest, insert(initial, x.0, x.1)) + } +} + +/// Determines whether or not a value present in the dict for a given key. +/// +/// ## Examples +/// +/// ```gleam +/// > new() |> insert("a", 0) |> has_key("a") +/// True +/// ``` +/// +/// ```gleam +/// > new() |> insert("a", 0) |> has_key("b") +/// False +/// ``` +/// +pub fn has_key(dict: Dict(k, v), key: k) -> Bool { + do_has_key(key, dict) +} + +@external(erlang, "maps", "is_key") +fn do_has_key(key: k, dict: Dict(k, v)) -> Bool { + get(dict, key) != Error(Nil) +} + +/// Creates a fresh dict that contains no values. +/// +pub fn new() -> Dict(key, value) { + do_new() +} + +@external(erlang, "maps", "new") +@external(javascript, "../gleam_stdlib.mjs", "new_map") +fn do_new() -> Dict(key, value) + +/// Fetches a value from a dict for a given key. +/// +/// The dict may not have a value for the key, so the value is wrapped in a +/// `Result`. +/// +/// ## Examples +/// +/// ```gleam +/// > new() |> insert("a", 0) |> get("a") +/// Ok(0) +/// ``` +/// +/// ```gleam +/// > new() |> insert("a", 0) |> get("b") +/// Error(Nil) +/// ``` +/// +pub fn get(from: Dict(key, value), get: key) -> Result(value, Nil) { + do_get(from, get) +} + +@external(erlang, "gleam_stdlib", "map_get") +@external(javascript, "../gleam_stdlib.mjs", "map_get") +fn do_get(a: Dict(key, value), b: key) -> Result(value, Nil) + +/// Inserts a value into the dict with the given key. +/// +/// If the dict already has a value for the given key then the value is +/// replaced with the new value. +/// +/// ## Examples +/// +/// ```gleam +/// > new() |> insert("a", 0) |> to_list +/// [#("a", 0)] +/// ``` +/// +/// ```gleam +/// > new() |> insert("a", 0) |> insert("a", 5) |> to_list +/// [#("a", 5)] +/// ``` +/// +pub fn insert(into dict: Dict(k, v), for key: k, insert value: v) -> Dict(k, v) { + do_insert(key, value, dict) +} + +@external(erlang, "maps", "put") +@external(javascript, "../gleam_stdlib.mjs", "map_insert") +fn do_insert(a: key, b: value, c: Dict(key, value)) -> Dict(key, value) + +/// Updates all values in a given dict by calling a given function on each key +/// and value. +/// +/// ## Examples +/// +/// ```gleam +/// > [#(3, 3), #(2, 4)] +/// > |> from_list +/// > |> map_values(fn(key, value) { key * value }) +/// [#(3, 9), #(2, 8)] +/// ``` +/// +pub fn map_values(in dict: Dict(k, v), with fun: fn(k, v) -> w) -> Dict(k, w) { + do_map_values(fun, dict) +} + +@external(erlang, "maps", "map") +fn do_map_values(f: fn(key, value) -> b, dict: Dict(key, value)) -> Dict(key, b) { + let f = fn(dict, k, v) { insert(dict, k, f(k, v)) } + dict + |> fold(from: new(), with: f) +} + +/// Gets a list of all keys in a given dict. +/// +/// Dicts are not ordered so the keys are not returned in any specific order. Do +/// not write code that relies on the order keys are returned by this function +/// as it may change in later versions of Gleam or Erlang. +/// +/// ## Examples +/// +/// ```gleam +/// > keys([#("a", 0), #("b", 1)]) +/// ["a", "b"] +/// ``` +/// +pub fn keys(dict: Dict(keys, v)) -> List(keys) { + do_keys(dict) +} + +@external(erlang, "maps", "keys") +fn do_keys(dict: Dict(k, v)) -> List(k) { + let list_of_pairs = to_list(dict) + do_keys_acc(list_of_pairs, []) +} + +fn reverse_and_concat(remaining, accumulator) { + case remaining { + [] -> accumulator + [item, ..rest] -> reverse_and_concat(rest, [item, ..accumulator]) + } +} + +fn do_keys_acc(list: List(#(k, v)), acc: List(k)) -> List(k) { + case list { + [] -> reverse_and_concat(acc, []) + [x, ..xs] -> do_keys_acc(xs, [x.0, ..acc]) + } +} + +/// Gets a list of all values in a given dict. +/// +/// Dicts are not ordered so the values are not returned in any specific order. Do +/// not write code that relies on the order values are returned by this function +/// as it may change in later versions of Gleam or Erlang. +/// +/// ## Examples +/// +/// ```gleam +/// > values(from_list([#("a", 0), #("b", 1)])) +/// [0, 1] +/// ``` +/// +pub fn values(dict: Dict(k, values)) -> List(values) { + do_values(dict) +} + +@external(erlang, "maps", "values") +fn do_values(dict: Dict(k, v)) -> List(v) { + let list_of_pairs = to_list(dict) + do_values_acc(list_of_pairs, []) +} + +fn do_values_acc(list: List(#(k, v)), acc: List(v)) -> List(v) { + case list { + [] -> reverse_and_concat(acc, []) + [x, ..xs] -> do_values_acc(xs, [x.1, ..acc]) + } +} + +/// Creates a new dict from a given dict, minus any entries that a given function +/// returns `False` for. +/// +/// ## Examples +/// +/// ```gleam +/// > from_list([#("a", 0), #("b", 1)]) +/// > |> filter(fn(key, value) { value != 0 }) +/// from_list([#("b", 1)]) +/// ``` +/// +/// ```gleam +/// > from_list([#("a", 0), #("b", 1)]) +/// > |> filter(fn(key, value) { True }) +/// from_list([#("a", 0), #("b", 1)]) +/// ``` +/// +pub fn filter( + in dict: Dict(k, v), + keeping predicate: fn(k, v) -> Bool, +) -> Dict(k, v) { + do_filter(predicate, dict) +} + +@external(erlang, "maps", "filter") +fn do_filter( + f: fn(key, value) -> Bool, + dict: Dict(key, value), +) -> Dict(key, value) { + let insert = fn(dict, k, v) { + case f(k, v) { + True -> insert(dict, k, v) + _ -> dict + } + } + dict + |> fold(from: new(), with: insert) +} + +/// Creates a new dict from a given dict, only including any entries for which the +/// keys are in a given list. +/// +/// ## Examples +/// +/// ```gleam +/// > from_list([#("a", 0), #("b", 1)]) +/// > |> take(["b"]) +/// from_list([#("b", 1)]) +/// ``` +/// +/// ```gleam +/// > from_list([#("a", 0), #("b", 1)]) +/// > |> take(["a", "b", "c"]) +/// from_list([#("a", 0), #("b", 1)]) +/// ``` +/// +pub fn take(from dict: Dict(k, v), keeping desired_keys: List(k)) -> Dict(k, v) { + do_take(desired_keys, dict) +} + +@external(erlang, "maps", "with") +fn do_take(desired_keys: List(k), dict: Dict(k, v)) -> Dict(k, v) { + insert_taken(dict, desired_keys, new()) +} + +fn insert_taken( + dict: Dict(k, v), + desired_keys: List(k), + acc: Dict(k, v), +) -> Dict(k, v) { + let insert = fn(taken, key) { + case get(dict, key) { + Ok(value) -> insert(taken, key, value) + _ -> taken + } + } + case desired_keys { + [] -> acc + [x, ..xs] -> insert_taken(dict, xs, insert(acc, x)) + } +} + +/// Creates a new dict from a pair of given dicts by combining their entries. +/// +/// If there are entries with the same keys in both dicts the entry from the +/// second dict takes precedence. +/// +/// ## Examples +/// +/// ```gleam +/// > let a = from_list([#("a", 0), #("b", 1)]) +/// > let b = from_list([#("b", 2), #("c", 3)]) +/// > merge(a, b) +/// from_list([#("a", 0), #("b", 2), #("c", 3)]) +/// ``` +/// +pub fn merge(into dict: Dict(k, v), from new_entries: Dict(k, v)) -> Dict(k, v) { + do_merge(dict, new_entries) +} + +@external(erlang, "maps", "merge") +fn do_merge(dict: Dict(k, v), new_entries: Dict(k, v)) -> Dict(k, v) { + new_entries + |> to_list + |> fold_inserts(dict) +} + +fn insert_pair(dict: Dict(k, v), pair: #(k, v)) -> Dict(k, v) { + insert(dict, pair.0, pair.1) +} + +fn fold_inserts(new_entries: List(#(k, v)), dict: Dict(k, v)) -> Dict(k, v) { + case new_entries { + [] -> dict + [x, ..xs] -> fold_inserts(xs, insert_pair(dict, x)) + } +} + +/// Creates a new dict from a given dict with all the same entries except for the +/// one with a given key, if it exists. +/// +/// ## Examples +/// +/// ```gleam +/// > delete([#("a", 0), #("b", 1)], "a") +/// from_list([#("b", 1)]) +/// ``` +/// +/// ```gleam +/// > delete([#("a", 0), #("b", 1)], "c") +/// from_list([#("a", 0), #("b", 1)]) +/// ``` +/// +pub fn delete(from dict: Dict(k, v), delete key: k) -> Dict(k, v) { + do_delete(key, dict) +} + +@external(erlang, "maps", "remove") +@external(javascript, "../gleam_stdlib.mjs", "map_remove") +fn do_delete(a: k, b: Dict(k, v)) -> Dict(k, v) + +/// Creates a new dict from a given dict with all the same entries except any with +/// keys found in a given list. +/// +/// ## Examples +/// +/// ```gleam +/// > drop([#("a", 0), #("b", 1)], ["a"]) +/// from_list([#("b", 2)]) +/// ``` +/// +/// ```gleam +/// > delete([#("a", 0), #("b", 1)], ["c"]) +/// from_list([#("a", 0), #("b", 1)]) +/// ``` +/// +/// ```gleam +/// > drop([#("a", 0), #("b", 1)], ["a", "b", "c"]) +/// from_list([]) +/// ``` +/// +pub fn drop(from dict: Dict(k, v), drop disallowed_keys: List(k)) -> Dict(k, v) { + case disallowed_keys { + [] -> dict + [x, ..xs] -> drop(delete(dict, x), xs) + } +} + +/// Creates a new dict with one entry updated using a given function. +/// +/// If there was not an entry in the dict for the given key then the function +/// gets `None` as its argument, otherwise it gets `Some(value)`. +/// +/// ## Example +/// +/// ```gleam +/// > let increment = fn(x) { +/// > case x { +/// > Some(i) -> i + 1 +/// > None -> 0 +/// > } +/// > } +/// > let dict = from_list([#("a", 0)]) +/// > +/// > update(dict, "a", increment) +/// from_list([#("a", 1)]) +/// ``` +/// +/// ```gleam +/// > update(dict, "b", increment) +/// from_list([#("a", 0), #("b", 0)]) +/// ``` +/// +pub fn update( + in dict: Dict(k, v), + update key: k, + with fun: fn(Option(v)) -> v, +) -> Dict(k, v) { + dict + |> get(key) + |> option.from_result + |> fun + |> insert(dict, key, _) +} + +fn do_fold(list: List(#(k, v)), initial: acc, fun: fn(acc, k, v) -> acc) -> acc { + case list { + [] -> initial + [#(k, v), ..rest] -> do_fold(rest, fun(initial, k, v), fun) + } +} + +/// Combines all entries into a single value by calling a given function on each +/// one. +/// +/// Dicts are not ordered so the values are not returned in any specific order. Do +/// not write code that relies on the order entries are used by this function +/// as it may change in later versions of Gleam or Erlang. +/// +/// # Examples +/// +/// ```gleam +/// > let dict = from_list([#("a", 1), #("b", 3), #("c", 9)]) +/// > fold(dict, 0, fn(accumulator, key, value) { accumulator + value }) +/// 13 +/// ``` +/// +/// ```gleam +/// > import gleam/string.{append} +/// > fold(dict, "", fn(accumulator, key, value) { append(accumulator, key) }) +/// "abc" +/// ``` +/// +pub fn fold( + over dict: Dict(k, v), + from initial: acc, + with fun: fn(acc, k, v) -> acc, +) -> acc { + dict + |> to_list + |> do_fold(initial, fun) +} diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/dynamic.gleam b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/dynamic.gleam new file mode 100644 index 0000000..1c4b431 --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/dynamic.gleam @@ -0,0 +1,1520 @@ +import gleam/int +import gleam/list +import gleam/dict.{type Dict} +import gleam/option.{type Option} +import gleam/result +import gleam/string_builder +@target(erlang) +import gleam/bit_array + +/// `Dynamic` data is data that we don't know the type of yet. +/// We likely get data like this from interop with Erlang, or from +/// IO with the outside world. +/// +pub type Dynamic + +/// Error returned when unexpected data is encountered +/// +pub type DecodeError { + DecodeError(expected: String, found: String, path: List(String)) +} + +pub type DecodeErrors = + List(DecodeError) + +pub type Decoder(t) = + fn(Dynamic) -> Result(t, DecodeErrors) + +/// Converts any Gleam data into `Dynamic` data. +/// +pub fn from(a) -> Dynamic { + do_from(a) +} + +@external(erlang, "gleam_stdlib", "identity") +@external(javascript, "../gleam_stdlib.mjs", "identity") +fn do_from(a: anything) -> Dynamic + +/// Unsafely casts a Dynamic value into any other type. +/// +/// This is an escape hatch for the type system that may be useful when wrapping +/// native Erlang APIs. It is to be used as a last measure only! +/// +/// If you can avoid using this function, do! +/// +pub fn unsafe_coerce(a: Dynamic) -> anything { + do_unsafe_coerce(a) +} + +@external(erlang, "gleam_stdlib", "identity") +@external(javascript, "../gleam_stdlib.mjs", "identity") +fn do_unsafe_coerce(a: Dynamic) -> a + +/// Decodes a `Dynamic` value from a `Dynamic` value. +/// +/// This function doesn't seem very useful at first, but it can be convenient +/// when you need to give a decoder function but you don't actually care what +/// the to-decode value is. +/// +pub fn dynamic(value: Dynamic) -> Result(Dynamic, List(DecodeError)) { + Ok(value) +} + +/// Checks to see whether a `Dynamic` value is a bit array, and returns that bit +/// array if it is. +/// +/// ## Examples +/// +/// ```gleam +/// > bit_array(from("Hello")) == bit_array.from_string("Hello") +/// True +/// ``` +/// +/// ```gleam +/// > bit_array(from(123)) +/// Error([DecodeError(expected: "BitArray", found: "Int", path: [])]) +/// ``` +/// +pub fn bit_array(from data: Dynamic) -> Result(BitArray, DecodeErrors) { + decode_bit_array(data) +} + +@deprecated("Please use `bit_array` instead") +pub fn bit_string(from data: Dynamic) -> Result(BitArray, DecodeErrors) { + bit_array(data) +} + +@external(erlang, "gleam_stdlib", "decode_bit_array") +@external(javascript, "../gleam_stdlib.mjs", "decode_bit_array") +fn decode_bit_array(a: Dynamic) -> Result(BitArray, DecodeErrors) + +/// Checks to see whether a `Dynamic` value is a string, and returns that string if +/// it is. +/// +/// ## Examples +/// +/// ```gleam +/// > string(from("Hello")) +/// Ok("Hello") +/// ``` +/// +/// ```gleam +/// > string(from(123)) +/// Error([DecodeError(expected: "String", found: "Int", path: [])]) +/// ``` +/// +pub fn string(from data: Dynamic) -> Result(String, DecodeErrors) { + decode_string(data) +} + +fn map_errors( + result: Result(t, DecodeErrors), + f: fn(DecodeError) -> DecodeError, +) -> Result(t, DecodeErrors) { + result.map_error(result, list.map(_, f)) +} + +@target(erlang) +fn decode_string(data: Dynamic) -> Result(String, DecodeErrors) { + bit_array(data) + |> map_errors(put_expected(_, "String")) + |> result.try(fn(raw) { + case bit_array.to_string(raw) { + Ok(string) -> Ok(string) + Error(Nil) -> + Error([DecodeError(expected: "String", found: "BitArray", path: [])]) + } + }) +} + +@target(erlang) +fn put_expected(error: DecodeError, expected: String) -> DecodeError { + DecodeError(..error, expected: expected) +} + +@target(javascript) +@external(javascript, "../gleam_stdlib.mjs", "decode_string") +fn decode_string(a: Dynamic) -> Result(String, DecodeErrors) + +/// Return a string indicating the type of the dynamic value. +/// +/// ```gleam +/// > classify(from("Hello")) +/// "String" +/// ``` +/// +pub fn classify(data: Dynamic) -> String { + do_classify(data) +} + +@external(erlang, "gleam_stdlib", "classify_dynamic") +@external(javascript, "../gleam_stdlib.mjs", "classify_dynamic") +fn do_classify(a: Dynamic) -> String + +/// Checks to see whether a `Dynamic` value is an int, and returns that int if it +/// is. +/// +/// ## Examples +/// +/// ```gleam +/// > int(from(123)) +/// Ok(123) +/// ``` +/// +/// ```gleam +/// > int(from("Hello")) +/// Error([DecodeError(expected: "Int", found: "String", path: [])]) +/// ``` +/// +pub fn int(from data: Dynamic) -> Result(Int, DecodeErrors) { + decode_int(data) +} + +@external(erlang, "gleam_stdlib", "decode_int") +@external(javascript, "../gleam_stdlib.mjs", "decode_int") +fn decode_int(a: Dynamic) -> Result(Int, DecodeErrors) + +/// Checks to see whether a `Dynamic` value is a float, and returns that float if +/// it is. +/// +/// ## Examples +/// +/// ```gleam +/// > float(from(2.0)) +/// Ok(2.0) +/// ``` +/// +/// ```gleam +/// > float(from(123)) +/// Error([DecodeError(expected: "Float", found: "Int", path: [])]) +/// ``` +/// +pub fn float(from data: Dynamic) -> Result(Float, DecodeErrors) { + decode_float(data) +} + +@external(erlang, "gleam_stdlib", "decode_float") +@external(javascript, "../gleam_stdlib.mjs", "decode_float") +fn decode_float(a: Dynamic) -> Result(Float, DecodeErrors) + +/// Checks to see whether a `Dynamic` value is a bool, and returns that bool if +/// it is. +/// +/// ## Examples +/// +/// ```gleam +/// > bool(from(True)) +/// Ok(True) +/// ``` +/// +/// ```gleam +/// > bool(from(123)) +/// Error([DecodeError(expected: "Bool", found: "Int", path: [])]) +/// ``` +/// +pub fn bool(from data: Dynamic) -> Result(Bool, DecodeErrors) { + decode_bool(data) +} + +@external(erlang, "gleam_stdlib", "decode_bool") +@external(javascript, "../gleam_stdlib.mjs", "decode_bool") +fn decode_bool(a: Dynamic) -> Result(Bool, DecodeErrors) + +/// Checks to see whether a `Dynamic` value is a list, and returns that list if it +/// is. The types of the elements are not checked. +/// +/// If you wish to decode all the elements in the list use the `list` function +/// instead. +/// +/// ## Examples +/// +/// ```gleam +/// > shallow_list(from(["a", "b", "c"])) +/// Ok([from("a"), from("b"), from("c")]) +/// ``` +/// +/// ```gleam +/// > shallow_list(1) +/// Error([DecodeError(expected: "List", found: "Int", path: [])]) +/// ``` +/// +pub fn shallow_list(from value: Dynamic) -> Result(List(Dynamic), DecodeErrors) { + decode_list(value) +} + +@external(erlang, "gleam_stdlib", "decode_list") +@external(javascript, "../gleam_stdlib.mjs", "decode_list") +fn decode_list(a: Dynamic) -> Result(List(Dynamic), DecodeErrors) + +@external(erlang, "gleam_stdlib", "decode_result") +@external(javascript, "../gleam_stdlib.mjs", "decode_result") +fn decode_result(a: Dynamic) -> Result(Result(a, e), DecodeErrors) + +/// Checks to see whether a `Dynamic` value is a result of a particular type, and +/// returns that result if it is. +/// +/// The `ok` and `error` arguments are decoders for decoding the `Ok` and +/// `Error` values of the result. +/// +/// ## Examples +/// +/// ```gleam +/// > from(Ok(1)) +/// > |> result(ok: int, error: string) +/// Ok(Ok(1)) +/// ``` +/// +/// ```gleam +/// > from(Error("boom")) +/// > |> result(ok: int, error: string) +/// Ok(Error("boom")) +/// ``` +/// +/// ```gleam +/// > from(123) +/// > |> result(ok: int, error: string) +/// Error([DecodeError(expected: "Result", found: "Int", path: [])]) +/// ``` +/// +pub fn result( + ok decode_ok: Decoder(a), + error decode_error: Decoder(e), +) -> Decoder(Result(a, e)) { + fn(value) { + use inner_result <- result.try(decode_result(value)) + + case inner_result { + Ok(raw) -> { + use value <- result.try( + decode_ok(raw) + |> map_errors(push_path(_, "ok")), + ) + Ok(Ok(value)) + } + Error(raw) -> { + use value <- result.try( + decode_error(raw) + |> map_errors(push_path(_, "error")), + ) + Ok(Error(value)) + } + } + } +} + +/// Checks to see whether a `Dynamic` value is a list of a particular type, and +/// returns that list if it is. +/// +/// The second argument is a decoder function used to decode the elements of +/// the list. The list is only decoded if all elements in the list can be +/// successfully decoded using this function. +/// +/// If you do not wish to decode all the elements in the list use the `shallow_list` +/// function instead. +/// +/// ## Examples +/// +/// ```gleam +/// > from(["a", "b", "c"]) +/// > |> list(of: string) +/// Ok(["a", "b", "c"]) +/// ``` +/// +/// ```gleam +/// > from([1, 2, 3]) +/// > |> list(of: string) +/// Error([DecodeError(expected: "String", found: "Int", path: ["*"])]) +/// ``` +/// +/// ```gleam +/// > from("ok") +/// > |> list(of: string) +/// Error([DecodeError(expected: "List", found: "String", path: [])]) +/// ``` +/// +pub fn list( + of decoder_type: fn(Dynamic) -> Result(inner, DecodeErrors), +) -> Decoder(List(inner)) { + fn(dynamic) { + use list <- result.try(shallow_list(dynamic)) + list + |> list.try_map(decoder_type) + |> map_errors(push_path(_, "*")) + } +} + +/// Checks to see if a `Dynamic` value is a nullable version of a particular +/// type, and returns a corresponding `Option` if it is. +/// +/// ## Examples +/// +/// ```gleam +/// > from("Hello") +/// > |> optional(string) +/// Ok(Some("Hello")) +/// ``` +/// +/// ```gleam +/// > from("Hello") +/// > |> optional(string) +/// Ok(Some("Hello")) +/// ``` +/// +/// ```gleam +/// > from(atom.from_string("null")) +/// > |> optional(string) +/// Ok(None) +/// ``` +/// +/// ```gleam +/// > from(atom.from_string("nil")) +/// > |> optional(string) +/// Ok(None) +/// ``` +/// +/// ```gleam +/// > from(atom.from_string("undefined")) +/// > |> optional(string) +/// Ok(None) +/// ``` +/// +/// ```gleam +/// > from(123) +/// > |> optional(string) +/// Error([DecodeError(expected: "String", found: "Int", path: [])]) +/// ``` +/// +pub fn optional(of decode: Decoder(inner)) -> Decoder(Option(inner)) { + fn(value) { decode_optional(value, decode) } +} + +@external(erlang, "gleam_stdlib", "decode_option") +@external(javascript, "../gleam_stdlib.mjs", "decode_option") +fn decode_optional(a: Dynamic, b: Decoder(a)) -> Result(Option(a), DecodeErrors) + +/// Checks to see if a `Dynamic` value is a map with a specific field, and returns +/// the value of that field if it is. +/// +/// This will not succeed on a record. +/// +/// ## Examples +/// +/// ```gleam +/// > import gleam/dict +/// > dict.new() +/// > |> dict.insert("Hello", "World") +/// > |> from +/// > |> field(named: "Hello", of: string) +/// Ok("World") +/// ``` +/// +/// ```gleam +/// > from(123) +/// > |> field("Hello", string) +/// Error([DecodeError(expected: "Map", found: "Int", path: [])]) +/// ``` +/// +pub fn field(named name: a, of inner_type: Decoder(t)) -> Decoder(t) { + fn(value) { + let missing_field_error = + DecodeError(expected: "field", found: "nothing", path: []) + + use maybe_inner <- result.try(decode_field(value, name)) + maybe_inner + |> option.to_result([missing_field_error]) + |> result.try(inner_type) + |> map_errors(push_path(_, name)) + } +} + +/// Checks to see if a `Dynamic` value is a map with a specific field. +/// If the map does not have the specified field, returns an `Ok(None)` instead of failing; otherwise, +/// returns the decoded field wrapped in `Some(_)`. +/// +/// ## Examples +/// +/// ```gleam +/// > import gleam/dict +/// > dict.new() +/// > |> dict.insert("Hello", "World") +/// > |> from +/// > |> field(named: "Hello", of: string) +/// Ok(Some("World")) +/// ``` +/// +/// ```gleam +/// > import gleam/dict +/// > dict.new() +/// > |> from +/// > |> field(named: "Hello", of: string) +/// Ok(None) +/// ``` +/// +/// ```gleam +/// > from(123) +/// > |> field("Hello", string) +/// Error([DecodeError(expected: "Map", found: "Int", path: [])]) +/// ``` +/// +pub fn optional_field( + named name: a, + of inner_type: Decoder(t), +) -> Decoder(Option(t)) { + fn(value) { + use maybe_inner <- result.try(decode_field(value, name)) + case maybe_inner { + option.None -> Ok(option.None) + option.Some(dynamic_inner) -> + dynamic_inner + |> decode_optional(inner_type) + |> map_errors(push_path(_, name)) + } + } +} + +@external(erlang, "gleam_stdlib", "decode_field") +@external(javascript, "../gleam_stdlib.mjs", "decode_field") +fn decode_field(a: Dynamic, b: name) -> Result(Option(Dynamic), DecodeErrors) + +/// Checks to see if a `Dynamic` value is a tuple large enough to have a certain +/// index, and returns the value of that index if it is. +/// +/// ## Examples +/// +/// ```gleam +/// > from(#(1, 2)) +/// > |> element(0, int) +/// Ok(from(1)) +/// ``` +/// +/// ```gleam +/// > from(#(1, 2)) +/// > |> element(2, int) +/// Error([ +/// DecodeError( +/// expected: "Tuple of at least 3 elements", +/// found: "Tuple of 2 elements", +/// path: [], +/// ), +/// ]) +/// ``` +/// +pub fn element(at index: Int, of inner_type: Decoder(t)) -> Decoder(t) { + fn(data: Dynamic) { + use tuple <- result.try(decode_tuple(data)) + let size = tuple_size(tuple) + use data <- result.try(case index >= 0 { + True -> + case index < size { + True -> tuple_get(tuple, index) + False -> at_least_decode_tuple_error(index + 1, data) + } + False -> + case int.absolute_value(index) <= size { + True -> tuple_get(tuple, size + index) + False -> at_least_decode_tuple_error(int.absolute_value(index), data) + } + }) + inner_type(data) + |> map_errors(push_path(_, index)) + } +} + +fn at_least_decode_tuple_error( + size: Int, + data: Dynamic, +) -> Result(a, DecodeErrors) { + let s = case size { + 1 -> "" + _ -> "s" + } + let error = + ["Tuple of at least ", int.to_string(size), " element", s] + |> string_builder.from_strings + |> string_builder.to_string + |> DecodeError(found: classify(data), path: []) + Error([error]) +} + +// A tuple of unknown size +type UnknownTuple + +@external(erlang, "gleam_stdlib", "decode_tuple") +@external(javascript, "../gleam_stdlib.mjs", "decode_tuple") +fn decode_tuple(a: Dynamic) -> Result(UnknownTuple, DecodeErrors) + +@external(erlang, "gleam_stdlib", "decode_tuple2") +@external(javascript, "../gleam_stdlib.mjs", "decode_tuple2") +fn decode_tuple2(a: Dynamic) -> Result(#(Dynamic, Dynamic), DecodeErrors) + +@external(erlang, "gleam_stdlib", "decode_tuple3") +@external(javascript, "../gleam_stdlib.mjs", "decode_tuple3") +fn decode_tuple3( + a: Dynamic, +) -> Result(#(Dynamic, Dynamic, Dynamic), DecodeErrors) + +@external(erlang, "gleam_stdlib", "decode_tuple4") +@external(javascript, "../gleam_stdlib.mjs", "decode_tuple4") +fn decode_tuple4( + a: Dynamic, +) -> Result(#(Dynamic, Dynamic, Dynamic, Dynamic), DecodeErrors) + +@external(erlang, "gleam_stdlib", "decode_tuple5") +@external(javascript, "../gleam_stdlib.mjs", "decode_tuple5") +fn decode_tuple5( + a: Dynamic, +) -> Result(#(Dynamic, Dynamic, Dynamic, Dynamic, Dynamic), DecodeErrors) + +@external(erlang, "gleam_stdlib", "decode_tuple6") +@external(javascript, "../gleam_stdlib.mjs", "decode_tuple6") +fn decode_tuple6( + a: Dynamic, +) -> Result( + #(Dynamic, Dynamic, Dynamic, Dynamic, Dynamic, Dynamic), + DecodeErrors, +) + +@external(erlang, "gleam_stdlib", "tuple_get") +@external(javascript, "../gleam_stdlib.mjs", "tuple_get") +fn tuple_get(a: UnknownTuple, b: Int) -> Result(Dynamic, DecodeErrors) + +@external(erlang, "gleam_stdlib", "size_of_tuple") +@external(javascript, "../gleam_stdlib.mjs", "length") +fn tuple_size(a: UnknownTuple) -> Int + +fn tuple_errors( + result: Result(a, List(DecodeError)), + name: String, +) -> List(DecodeError) { + case result { + Ok(_) -> [] + Error(errors) -> list.map(errors, push_path(_, name)) + } +} + +fn push_path(error: DecodeError, name: t) -> DecodeError { + let name = from(name) + let decoder = any([string, fn(x) { result.map(int(x), int.to_string) }]) + let name = case decoder(name) { + Ok(name) -> name + Error(_) -> + ["<", classify(name), ">"] + |> string_builder.from_strings + |> string_builder.to_string + } + DecodeError(..error, path: [name, ..error.path]) +} + +/// Checks to see if a `Dynamic` value is a 2-element tuple, list or array containing +/// specifically typed elements. +/// +/// ## Examples +/// +/// ```gleam +/// > from(#(1, 2)) +/// > |> tuple2(int, int) +/// Ok(#(1, 2)) +/// ``` +/// +/// ```gleam +/// > from(#(1, 2.0)) +/// > |> tuple2(int, float) +/// Ok(#(1, 2.0)) +/// ``` +/// +/// ```gleam +/// > from([1, 2]) +/// > |> tuple2(int, int) +/// Ok(#(1, 2)) +/// ``` +/// +/// ```gleam +/// > from([from(1), from(2.0)]) +/// > |> tuple2(int, float) +/// Ok(#(1, 2.0)) +/// ``` +/// +/// ```gleam +/// > from(#(1, 2, 3)) +/// > |> tuple2(int, float) +/// Error([ +/// DecodeError(expected: "Tuple of 2 elements", found: "Tuple of 3 elements", path: []), +/// ]) +/// ``` +/// +/// ```gleam +/// > from("") +/// > |> tuple2(int, float) +/// Error([DecodeError(expected: "Tuple of 2 elements", found: "String", path: [])]) +/// ``` +/// +pub fn tuple2( + first decode1: Decoder(a), + second decode2: Decoder(b), +) -> Decoder(#(a, b)) { + fn(value) { + use #(a, b) <- result.try(decode_tuple2(value)) + case decode1(a), decode2(b) { + Ok(a), Ok(b) -> Ok(#(a, b)) + a, b -> + tuple_errors(a, "0") + |> list.append(tuple_errors(b, "1")) + |> Error + } + } +} + +/// Checks to see if a `Dynamic` value is a 3-element tuple, list or array containing +/// specifically typed elements. +/// +/// ## Examples +/// +/// ```gleam +/// > from(#(1, 2, 3)) +/// > |> tuple3(int, int, int) +/// Ok(#(1, 2, 3)) +/// ``` +/// +/// ```gleam +/// > from(#(1, 2.0, "3")) +/// > |> tuple3(int, float, string) +/// Ok(#(1, 2.0, "3")) +/// ``` +/// +/// ```gleam +/// > from([1, 2, 3]) +/// > |> tuple3(int, int, int) +/// Ok(#(1, 2, 3)) +/// ``` +/// +/// ```gleam +/// > from([from(1), from(2.0), from("3")]) +/// > |> tuple3(int, float, string) +/// Ok(#(1, 2.0, "3")) +/// ``` +/// +/// ```gleam +/// > from(#(1, 2)) +/// > |> tuple3(int, float, string) +/// Error([ +/// DecodeError(expected: "Tuple of 3 elements", found: "Tuple of 2 elements", path: [])), +/// ]) +/// ``` +/// +/// ```gleam +/// > from("") +/// > |> tuple3(int, float, string) +/// Error([ +/// DecodeError(expected: "Tuple of 3 elements", found: "String", path: []), +/// ]) +/// ``` +/// +pub fn tuple3( + first decode1: Decoder(a), + second decode2: Decoder(b), + third decode3: Decoder(c), +) -> Decoder(#(a, b, c)) { + fn(value) { + use #(a, b, c) <- result.try(decode_tuple3(value)) + case decode1(a), decode2(b), decode3(c) { + Ok(a), Ok(b), Ok(c) -> Ok(#(a, b, c)) + a, b, c -> + tuple_errors(a, "0") + |> list.append(tuple_errors(b, "1")) + |> list.append(tuple_errors(c, "2")) + |> Error + } + } +} + +/// Checks to see if a `Dynamic` value is a 4-element tuple, list or array containing +/// specifically typed elements. +/// +/// ## Examples +/// +/// ```gleam +/// > from(#(1, 2, 3, 4)) +/// > |> tuple4(int, int, int, int) +/// Ok(#(1, 2, 3, 4)) +/// ``` +/// +/// ```gleam +/// > from(#(1, 2.0, "3", 4)) +/// > |> tuple4(int, float, string, int) +/// Ok(#(1, 2.0, "3", 4)) +/// ``` +/// +/// ```gleam +/// > from([1, 2, 3, 4]) +/// > |> tuple4(int, int, int, int) +/// Ok(#(1, 2, 3, 4)) +/// ``` +/// +/// ```gleam +/// > from([from(1), from(2.0), from("3"), from(4)]) +/// > |> tuple4(int, float, string, int) +/// Ok(#(1, 2.0, "3", 4)) +/// ``` +/// +/// ```gleam +/// > from(#(1, 2)) +/// > |> tuple4(int, float, string, int) +/// Error([ +/// DecodeError(expected: "Tuple of 4 elements", found: "Tuple of 2 elements", path: []), +/// ]) +/// ``` +/// +/// ```gleam +/// > from("") +/// > |> tuple4(int, float, string, int) +/// Error([ +/// DecodeError(expected: "Tuple of 4 elements", found: "String", path: []), +/// ]) +/// ``` +/// +pub fn tuple4( + first decode1: Decoder(a), + second decode2: Decoder(b), + third decode3: Decoder(c), + fourth decode4: Decoder(d), +) -> Decoder(#(a, b, c, d)) { + fn(value) { + use #(a, b, c, d) <- result.try(decode_tuple4(value)) + case decode1(a), decode2(b), decode3(c), decode4(d) { + Ok(a), Ok(b), Ok(c), Ok(d) -> Ok(#(a, b, c, d)) + a, b, c, d -> + tuple_errors(a, "0") + |> list.append(tuple_errors(b, "1")) + |> list.append(tuple_errors(c, "2")) + |> list.append(tuple_errors(d, "3")) + |> Error + } + } +} + +/// Checks to see if a `Dynamic` value is a 5-element tuple, list or array containing +/// specifically typed elements. +/// +/// ## Examples +/// +/// ```gleam +/// > from(#(1, 2, 3, 4, 5)) +/// > |> tuple5(int, int, int, int, int) +/// Ok(#(1, 2, 3, 4, 5)) +/// ``` +/// +/// ```gleam +/// > from(#(1, 2.0, "3", 4, 5)) +/// > |> tuple5(int, float, string, int, int) +/// Ok(#(1, 2.0, "3", 4, 5)) +/// ``` +/// +/// ```gleam +/// > from([1, 2, 3, 4, 5]) +/// > |> tuple5(int, int, int, int, int) +/// Ok(#(1, 2, 3, 4, 5)) +/// ``` +/// +/// ```gleam +/// > from([from(1), from(2.0), from("3"), from(4), from(True)]) +/// > |> tuple5(int, float, string, int, bool) +/// Ok(#(1, 2.0, "3", 4, True)) +/// ``` +/// +/// ```gleam +/// > from(#(1, 2)) +/// > |> tuple5(int, float, string, int, int) +/// Error([ +/// DecodeError(expected: "Tuple of 5 elements", found: "Tuple of 2 elements", path: [])), +/// ]) +/// ``` +/// +/// ```gleam +/// > from("") +/// > |> tuple5(int, float, string, int, int) +/// Error([DecodeError(expected: "Tuple of 5 elements", found: "String", path: [])]) +/// ``` +/// +pub fn tuple5( + first decode1: Decoder(a), + second decode2: Decoder(b), + third decode3: Decoder(c), + fourth decode4: Decoder(d), + fifth decode5: Decoder(e), +) -> Decoder(#(a, b, c, d, e)) { + fn(value) { + use #(a, b, c, d, e) <- result.try(decode_tuple5(value)) + case decode1(a), decode2(b), decode3(c), decode4(d), decode5(e) { + Ok(a), Ok(b), Ok(c), Ok(d), Ok(e) -> Ok(#(a, b, c, d, e)) + a, b, c, d, e -> + tuple_errors(a, "0") + |> list.append(tuple_errors(b, "1")) + |> list.append(tuple_errors(c, "2")) + |> list.append(tuple_errors(d, "3")) + |> list.append(tuple_errors(e, "4")) + |> Error + } + } +} + +/// Checks to see if a `Dynamic` value is a 6-element tuple, list or array containing +/// specifically typed elements. +/// +/// ## Examples +/// +/// ```gleam +/// > from(#(1, 2, 3, 4, 5, 6)) +/// > |> tuple6(int, int, int, int, int, int) +/// Ok(#(1, 2, 3, 4, 5, 6)) +/// ``` +/// +/// ```gleam +/// > from(#(1, 2.0, "3", 4, 5, 6)) +/// > |> tuple6(int, float, string, int, int, int) +/// Ok(#(1, 2.0, "3", 4, 5, 6)) +/// ``` +/// +/// ```gleam +/// > from([1, 2, 3, 4, 5, 6]) +/// > |> tuple6(int, int, int, int, int, int) +/// Ok(#(1, 2, 3, 4, 5, 6)) +/// ``` +/// +/// ```gleam +/// > from([from(1), from(2.0), from("3"), from(4), from(True), from(False)]) +/// > |> tuple6(int, float, string, int, bool, bool) +/// Ok(#(1, 2.0, "3", 4, True, False)) +/// ``` +/// +/// ```gleam +/// > from(#(1, 2)) +/// > |> tuple6(int, float, string, int, int, int) +/// Error([ +/// DecodeError(expected: "Tuple of 6 elements", found: "Tuple of 2 elements", path: []), +/// ]) +/// ``` +/// +/// ```gleam +/// > from("") +/// > |> tuple6(int, float, string, int, int, int) +/// Error([DecodeError(expected: "Tuple of 6 elements", found: "String", path: [])]) +/// ``` +/// +pub fn tuple6( + first decode1: Decoder(a), + second decode2: Decoder(b), + third decode3: Decoder(c), + fourth decode4: Decoder(d), + fifth decode5: Decoder(e), + sixth decode6: Decoder(f), +) -> Decoder(#(a, b, c, d, e, f)) { + fn(value) { + use #(a, b, c, d, e, f) <- result.try(decode_tuple6(value)) + case + decode1(a), + decode2(b), + decode3(c), + decode4(d), + decode5(e), + decode6(f) + { + Ok(a), Ok(b), Ok(c), Ok(d), Ok(e), Ok(f) -> Ok(#(a, b, c, d, e, f)) + a, b, c, d, e, f -> + tuple_errors(a, "0") + |> list.append(tuple_errors(b, "1")) + |> list.append(tuple_errors(c, "2")) + |> list.append(tuple_errors(d, "3")) + |> list.append(tuple_errors(e, "4")) + |> list.append(tuple_errors(f, "5")) + |> Error + } + } +} + +/// Checks to see if a `Dynamic` value is a dict. +/// +/// ## Examples +/// +/// ```gleam +/// > import gleam/dict +/// > dict.new() |> from |> map(string, int) +/// Ok(dict.new()) +/// ``` +/// +/// ```gleam +/// > from(1) |> map(string, int) +/// Error(DecodeError(expected: "Map", found: "Int", path: [])) +/// ``` +/// +/// ```gleam +/// > from("") |> map(string, int) +/// Error(DecodeError(expected: "Map", found: "String", path: [])) +/// ``` +/// +pub fn dict( + of key_type: Decoder(k), + to value_type: Decoder(v), +) -> Decoder(Dict(k, v)) { + fn(value) { + use map <- result.try(decode_map(value)) + use pairs <- result.try( + map + |> dict.to_list + |> list.try_map(fn(pair) { + let #(k, v) = pair + use k <- result.try( + key_type(k) + |> map_errors(push_path(_, "keys")), + ) + use v <- result.try( + value_type(v) + |> map_errors(push_path(_, "values")), + ) + Ok(#(k, v)) + }), + ) + Ok(dict.from_list(pairs)) + } +} + +@deprecated("Use `dict` instead") +pub fn map( + of key_type: Decoder(k), + to value_type: Decoder(v), +) -> Decoder(Dict(k, v)) { + dict(key_type, value_type) +} + +@external(erlang, "gleam_stdlib", "decode_map") +@external(javascript, "../gleam_stdlib.mjs", "decode_map") +fn decode_map(a: Dynamic) -> Result(Dict(Dynamic, Dynamic), DecodeErrors) + +/// Joins multiple decoders into one. When run they will each be tried in turn +/// until one succeeds, or they all fail. +/// +/// ## Examples +/// +/// ```gleam +/// > import gleam/result +/// > let bool_or_string = any(of: [ +/// > string, +/// > fn(x) { result.map(bool(x), fn(_) { "a bool" }) } +/// > ]) +/// > bool_or_string(from("ok")) +/// Ok("ok") +/// ``` +/// +/// ```gleam +/// > bool_or_string(from(True)) +/// Ok("a bool") +/// ``` +/// +/// ```gleam +/// > bool_or_string(from(1)) +/// Error(DecodeError(expected: "another type", found: "Int", path: [])) +/// ``` +/// +pub fn any(of decoders: List(Decoder(t))) -> Decoder(t) { + fn(data) { + case decoders { + [] -> + Error([ + DecodeError(found: classify(data), expected: "another type", path: []), + ]) + + [decoder, ..decoders] -> + case decoder(data) { + Ok(decoded) -> Ok(decoded) + Error(_) -> any(decoders)(data) + } + } + } +} + +/// Decode 1 values from a `Dynamic` value. +/// +/// ## Examples +/// +/// ```gleam +/// > from(#(1, 2.0, "3")) +/// > |> decode1(MyRecord, element(0, int)) +/// Ok(MyRecord(1)) +/// ``` +/// +/// ```gleam +/// > from(#("", "", "")) +/// > |> decode1(MyRecord, element(0, int)) +/// Error([ +/// DecodeError(expected: "Int", found: "String", path: ["0"]), +/// ]) +/// ``` +/// +pub fn decode1(constructor: fn(t1) -> t, t1: Decoder(t1)) -> Decoder(t) { + fn(value) { + case t1(value) { + Ok(a) -> Ok(constructor(a)) + a -> Error(all_errors(a)) + } + } +} + +/// Decode 2 values from a `Dynamic` value. +/// +/// ## Examples +/// +/// ```gleam +/// > from(#(1, 2.0, "3")) +/// > |> decode2(MyRecord, element(0, int), element(1, float)) +/// Ok(MyRecord(1, 2.0)) +/// ``` +/// +/// ```gleam +/// > from(#("", "", "")) +/// > |> decode2(MyRecord, element(0, int), element(1, float)) +/// Error([ +/// DecodeError(expected: "Int", found: "String", path: ["0"]), +/// DecodeError(expected: "Float", found: "String", path: ["1"]), +/// ]) +/// ``` +/// +pub fn decode2( + constructor: fn(t1, t2) -> t, + t1: Decoder(t1), + t2: Decoder(t2), +) -> Decoder(t) { + fn(value) { + case t1(value), t2(value) { + Ok(a), Ok(b) -> Ok(constructor(a, b)) + a, b -> Error(list.concat([all_errors(a), all_errors(b)])) + } + } +} + +/// Decode 3 values from a `Dynamic` value. +/// +/// ## Examples +/// +/// ```gleam +/// > from(#(1, 2.0, "3")) +/// > |> decode3(MyRecord, element(0, int), element(1, float), element(2, string)) +/// Ok(MyRecord(1, 2.0, "3")) +/// ``` +/// +/// ```gleam +/// > from(#("", "", "")) +/// > |> decode3(MyRecord, element(0, int), element(1, float), element(2, string)) +/// Error([ +/// DecodeError(expected: "Int", found: "String", path: ["0"]), +/// DecodeError(expected: "Float", found: "String", path: ["1"]), +/// ]) +/// ``` +/// +pub fn decode3( + constructor: fn(t1, t2, t3) -> t, + t1: Decoder(t1), + t2: Decoder(t2), + t3: Decoder(t3), +) -> Decoder(t) { + fn(value) { + case t1(value), t2(value), t3(value) { + Ok(a), Ok(b), Ok(c) -> Ok(constructor(a, b, c)) + a, b, c -> + Error(list.concat([all_errors(a), all_errors(b), all_errors(c)])) + } + } +} + +/// Decode 4 values from a `Dynamic` value. +/// +/// ## Examples +/// +/// ```gleam +/// > from(#(1, 2.1, "3", "4")) +/// > |> decode4( +/// > MyRecord, +/// > element(0, int), +/// > element(1, float), +/// > element(2, string), +/// > element(3, string), +/// > ) +/// Ok(MyRecord(1, 2.1, "3", "4")) +/// ``` +/// +/// ```gleam +/// > from(#("", "", "", "")) +/// > |> decode4( +/// > MyRecord, +/// > element(0, int), +/// > element(1, float), +/// > element(2, string), +/// > element(3, string), +/// > ) +/// Error([ +/// DecodeError(expected: "Int", found: "String", path: ["0"]), +/// DecodeError(expected: "Float", found: "String", path: ["1"]), +/// ]) +/// ``` +/// +pub fn decode4( + constructor: fn(t1, t2, t3, t4) -> t, + t1: Decoder(t1), + t2: Decoder(t2), + t3: Decoder(t3), + t4: Decoder(t4), +) -> Decoder(t) { + fn(x: Dynamic) { + case t1(x), t2(x), t3(x), t4(x) { + Ok(a), Ok(b), Ok(c), Ok(d) -> Ok(constructor(a, b, c, d)) + a, b, c, d -> + Error( + list.concat([ + all_errors(a), + all_errors(b), + all_errors(c), + all_errors(d), + ]), + ) + } + } +} + +/// Decode 5 values from a `Dynamic` value. +/// +/// ## Examples +/// +/// ```gleam +/// > from(#(1, 2.1, "3", "4", "5")) +/// > |> decode5( +/// > MyRecord, +/// > element(0, int), +/// > element(1, float), +/// > element(2, string), +/// > element(3, string), +/// > element(4, string), +/// > ) +/// Ok(MyRecord(1, 2.1, "3", "4", "5")) +/// ``` +/// +/// ```gleam +/// > from(#("", "", "", "", "")) +/// > |> decode5( +/// > MyRecord, +/// > element(0, int), +/// > element(1, float), +/// > element(2, string), +/// > element(3, string), +/// > element(4, string), +/// > ) +/// Error([ +/// DecodeError(expected: "Int", found: "String", path: ["0"]), +/// DecodeError(expected: "Float", found: "String", path: ["1"]), +/// ]) +/// ``` +/// +pub fn decode5( + constructor: fn(t1, t2, t3, t4, t5) -> t, + t1: Decoder(t1), + t2: Decoder(t2), + t3: Decoder(t3), + t4: Decoder(t4), + t5: Decoder(t5), +) -> Decoder(t) { + fn(x: Dynamic) { + case t1(x), t2(x), t3(x), t4(x), t5(x) { + Ok(a), Ok(b), Ok(c), Ok(d), Ok(e) -> Ok(constructor(a, b, c, d, e)) + a, b, c, d, e -> + Error( + list.concat([ + all_errors(a), + all_errors(b), + all_errors(c), + all_errors(d), + all_errors(e), + ]), + ) + } + } +} + +/// Decode 6 values from a `Dynamic` value. +/// +/// ## Examples +/// +/// ```gleam +/// > from(#(1, 2.1, "3", "4", "5", "6")) +/// > |> decode6( +/// > MyRecord, +/// > element(0, int), +/// > element(1, float), +/// > element(2, string), +/// > element(3, string), +/// > element(4, string), +/// > element(5, string), +/// > ) +/// Ok(MyRecord(1, 2.1, "3", "4", "5", "6")) +/// ``` +/// +/// ```gleam +/// > from(#("", "", "", "", "", "")) +/// > |> decode6( +/// > MyRecord, +/// > element(0, int), +/// > element(1, float), +/// > element(2, string), +/// > element(3, string), +/// > element(4, string), +/// > element(5, string), +/// > ) +/// Error([ +/// DecodeError(expected: "Int", found: "String", path: ["0"]), +/// DecodeError(expected: "Float", found: "String", path: ["1"]), +/// ]) +/// ``` +/// +pub fn decode6( + constructor: fn(t1, t2, t3, t4, t5, t6) -> t, + t1: Decoder(t1), + t2: Decoder(t2), + t3: Decoder(t3), + t4: Decoder(t4), + t5: Decoder(t5), + t6: Decoder(t6), +) -> Decoder(t) { + fn(x: Dynamic) { + case t1(x), t2(x), t3(x), t4(x), t5(x), t6(x) { + Ok(a), Ok(b), Ok(c), Ok(d), Ok(e), Ok(f) -> + Ok(constructor(a, b, c, d, e, f)) + a, b, c, d, e, f -> + Error( + list.concat([ + all_errors(a), + all_errors(b), + all_errors(c), + all_errors(d), + all_errors(e), + all_errors(f), + ]), + ) + } + } +} + +/// Decode 7 values from a `Dynamic` value. +/// +/// ## Examples +/// +/// ```gleam +/// > from(#(1, 2.1, "3", "4", "5", "6")) +/// > |> decode7( +/// > MyRecord, +/// > element(0, int), +/// > element(1, float), +/// > element(2, string), +/// > element(3, string), +/// > element(4, string), +/// > element(5, string), +/// > element(6, string), +/// > ) +/// Ok(MyRecord(1, 2.1, "3", "4", "5", "6", "7")) +/// ``` +/// +/// ```gleam +/// > from(#("", "", "", "", "", "", "")) +/// > |> decode7( +/// > MyRecord, +/// > element(0, int), +/// > element(1, float), +/// > element(2, string), +/// > element(3, string), +/// > element(4, string), +/// > element(5, string), +/// > element(6, string), +/// > ) +/// Error([ +/// DecodeError(expected: "Int", found: "String", path: ["0"]), +/// DecodeError(expected: "Float", found: "String", path: ["1"]), +/// ]) +/// ``` +/// +pub fn decode7( + constructor: fn(t1, t2, t3, t4, t5, t6, t7) -> t, + t1: Decoder(t1), + t2: Decoder(t2), + t3: Decoder(t3), + t4: Decoder(t4), + t5: Decoder(t5), + t6: Decoder(t6), + t7: Decoder(t7), +) -> Decoder(t) { + fn(x: Dynamic) { + case t1(x), t2(x), t3(x), t4(x), t5(x), t6(x), t7(x) { + Ok(a), Ok(b), Ok(c), Ok(d), Ok(e), Ok(f), Ok(g) -> + Ok(constructor(a, b, c, d, e, f, g)) + a, b, c, d, e, f, g -> + Error( + list.concat([ + all_errors(a), + all_errors(b), + all_errors(c), + all_errors(d), + all_errors(e), + all_errors(f), + all_errors(g), + ]), + ) + } + } +} + +/// Decode 8 values from a `Dynamic` value. +/// +/// ## Examples +/// +/// ```gleam +/// > from(#(1, 2.1, "3", "4", "5", "6", "7", "8")) +/// > |> decode8( +/// > MyRecord, +/// > element(0, int), +/// > element(1, float), +/// > element(2, string), +/// > element(3, string), +/// > element(4, string), +/// > element(5, string), +/// > element(6, string), +/// > element(7, string), +/// > ) +/// Ok(MyRecord(1, 2.1, "3", "4", "5", "6", "7", "8")) +/// ``` +/// +/// ```gleam +/// > from(#("", "", "", "", "", "", "", "")) +/// > |> decode8( +/// > MyRecord, +/// > element(0, int), +/// > element(1, float), +/// > element(2, string), +/// > element(3, string), +/// > element(4, string), +/// > element(5, string), +/// > element(6, string), +/// > element(7, string), +/// > ) +/// Error([ +/// DecodeError(expected: "Int", found: "String", path: ["0"]), +/// DecodeError(expected: "Float", found: "String", path: ["1"]), +/// ]) +/// ``` +/// +pub fn decode8( + constructor: fn(t1, t2, t3, t4, t5, t6, t7, t8) -> t, + t1: Decoder(t1), + t2: Decoder(t2), + t3: Decoder(t3), + t4: Decoder(t4), + t5: Decoder(t5), + t6: Decoder(t6), + t7: Decoder(t7), + t8: Decoder(t8), +) -> Decoder(t) { + fn(x: Dynamic) { + case t1(x), t2(x), t3(x), t4(x), t5(x), t6(x), t7(x), t8(x) { + Ok(a), Ok(b), Ok(c), Ok(d), Ok(e), Ok(f), Ok(g), Ok(h) -> + Ok(constructor(a, b, c, d, e, f, g, h)) + a, b, c, d, e, f, g, h -> + Error( + list.concat([ + all_errors(a), + all_errors(b), + all_errors(c), + all_errors(d), + all_errors(e), + all_errors(f), + all_errors(g), + all_errors(h), + ]), + ) + } + } +} + +/// Decode 9 values from a `Dynamic` value. +/// +/// ## Examples +/// +/// ```gleam +/// > from(#(1, 2.1, "3", "4", "5", "6", "7", "8", "9")) +/// > |> decode9( +/// > MyRecord, +/// > element(0, int), +/// > element(1, float), +/// > element(2, string), +/// > element(3, string), +/// > element(4, string), +/// > element(5, string), +/// > element(6, string), +/// > element(7, string), +/// > element(8, string), +/// > ) +/// Ok(MyRecord(1, 2.1, "3", "4", "5", "6", "7", "8", "9")) +/// ``` +/// +/// ```gleam +/// > from(#("", "", "", "", "", "", "", "", "")) +/// > |> decode9( +/// > MyRecord, +/// > element(0, int), +/// > element(1, float), +/// > element(2, string), +/// > element(3, string), +/// > element(4, string), +/// > element(5, string), +/// > element(6, string), +/// > element(7, string), +/// > element(8, string), +/// > ) +/// Error([ +/// DecodeError(expected: "Int", found: "String", path: ["0"]), +/// DecodeError(expected: "Float", found: "String", path: ["1"]), +/// ]) +/// ``` +/// +pub fn decode9( + constructor: fn(t1, t2, t3, t4, t5, t6, t7, t8, t9) -> t, + t1: Decoder(t1), + t2: Decoder(t2), + t3: Decoder(t3), + t4: Decoder(t4), + t5: Decoder(t5), + t6: Decoder(t6), + t7: Decoder(t7), + t8: Decoder(t8), + t9: Decoder(t9), +) -> Decoder(t) { + fn(x: Dynamic) { + case t1(x), t2(x), t3(x), t4(x), t5(x), t6(x), t7(x), t8(x), t9(x) { + Ok(a), Ok(b), Ok(c), Ok(d), Ok(e), Ok(f), Ok(g), Ok(h), Ok(i) -> + Ok(constructor(a, b, c, d, e, f, g, h, i)) + a, b, c, d, e, f, g, h, i -> + Error( + list.concat([ + all_errors(a), + all_errors(b), + all_errors(c), + all_errors(d), + all_errors(e), + all_errors(f), + all_errors(g), + all_errors(h), + all_errors(i), + ]), + ) + } + } +} + +fn all_errors(result: Result(a, List(DecodeError))) -> List(DecodeError) { + case result { + Ok(_) -> [] + Error(errors) -> errors + } +} diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/float.gleam b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/float.gleam new file mode 100644 index 0000000..e53074d --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/float.gleam @@ -0,0 +1,541 @@ +//// Functions for working with floats. +//// +//// ## Division by zero +//// +//// Gleam runs on the Erlang virtual machine, which does not follow the IEEE +//// 754 standard for floating point arithmetic and does not have an `Infinity` +//// value. In Erlang division by zero results in a crash, however Gleam does +//// not have partial functions and operators in core so instead division by zero +//// returns zero, a behaviour taken from Pony, Coq, and Lean. +//// +//// This may seem unexpected at first, but it is no less mathematically valid +//// than crashing or returning a special value. Division by zero is undefined +//// in mathematics. + +import gleam/order.{type Order} + +/// Attempts to parse a string as a `Float`, returning `Error(Nil)` if it was +/// not possible. +/// +/// ## Examples +/// +/// ```gleam +/// > parse("2.3") +/// Ok(2.3) +/// ``` +/// +/// ```gleam +/// > parse("ABC") +/// Error(Nil) +/// ``` +/// +pub fn parse(string: String) -> Result(Float, Nil) { + do_parse(string) +} + +@external(erlang, "gleam_stdlib", "parse_float") +@external(javascript, "../gleam_stdlib.mjs", "parse_float") +fn do_parse(a: String) -> Result(Float, Nil) + +/// Returns the string representation of the provided `Float`. +/// +/// ## Examples +/// +/// ```gleam +/// > to_string(2.3) +/// "2.3" +/// ``` +/// +pub fn to_string(x: Float) -> String { + do_to_string(x) +} + +@external(erlang, "gleam_stdlib", "float_to_string") +@external(javascript, "../gleam_stdlib.mjs", "float_to_string") +fn do_to_string(a: Float) -> String + +/// Restricts a `Float` between a lower and upper bound. +/// +/// ## Examples +/// +/// ```gleam +/// > clamp(1.2, min: 1.4, max: 1.6) +/// 1.4 +/// ``` +/// +pub fn clamp(x: Float, min min_bound: Float, max max_bound: Float) -> Float { + x + |> min(max_bound) + |> max(min_bound) +} + +/// Compares two `Float`s, returning an `Order`: +/// `Lt` for lower than, `Eq` for equals, or `Gt` for greater than. +/// +/// ## Examples +/// +/// ```gleam +/// > compare(2.0, 2.3) +/// Lt +/// ``` +/// +/// To handle +/// [Floating Point Imprecision](https://en.wikipedia.org/wiki/Floating-point_arithmetic#Accuracy_problems) +/// you may use [`loosely_compare`](#loosely_compare) instead. +/// +pub fn compare(a: Float, with b: Float) -> Order { + case a == b { + True -> order.Eq + False -> + case a <. b { + True -> order.Lt + False -> order.Gt + } + } +} + +/// Compares two `Float`s within a tolerance, returning an `Order`: +/// `Lt` for lower than, `Eq` for equals, or `Gt` for greater than. +/// +/// This function allows Float comparison while handling +/// [Floating Point Imprecision](https://en.wikipedia.org/wiki/Floating-point_arithmetic#Accuracy_problems). +/// +/// Notice: For `Float`s the tolerance won't be exact: +/// `5.3 - 5.0` is not exactly `0.3`. +/// +/// ## Examples +/// +/// ```gleam +/// > loosely_compare(5.0, with: 5.3, tolerating: 0.5) +/// Eq +/// ``` +/// +/// If you want to check only for equality you may use +/// [`loosely_equals`](#loosely_equals) instead. +/// +pub fn loosely_compare( + a: Float, + with b: Float, + tolerating tolerance: Float, +) -> Order { + let difference = absolute_value(a -. b) + case difference <=. tolerance { + True -> order.Eq + False -> compare(a, b) + } +} + +/// Checks for equality of two `Float`s within a tolerance, +/// returning an `Bool`. +/// +/// This function allows Float comparison while handling +/// [Floating Point Imprecision](https://en.wikipedia.org/wiki/Floating-point_arithmetic#Accuracy_problems). +/// +/// Notice: For `Float`s the tolerance won't be exact: +/// `5.3 - 5.0` is not exactly `0.3`. +/// +/// ## Examples +/// +/// ```gleam +/// > loosely_equals(5.0, with: 5.3, tolerating: 0.5) +/// True +/// ``` +/// +/// ```gleam +/// > loosely_equals(5.0, with: 5.1, tolerating: 0.1) +/// False +/// ``` +/// +pub fn loosely_equals( + a: Float, + with b: Float, + tolerating tolerance: Float, +) -> Bool { + let difference = absolute_value(a -. b) + difference <=. tolerance +} + +/// Compares two `Float`s, returning the smaller of the two. +/// +/// ## Examples +/// +/// ```gleam +/// > min(2.0, 2.3) +/// 2.0 +/// ``` +/// +pub fn min(a: Float, b: Float) -> Float { + case a <. b { + True -> a + False -> b + } +} + +/// Compares two `Float`s, returning the larger of the two. +/// +/// ## Examples +/// +/// ```gleam +/// > max(2.0, 2.3) +/// 2.3 +/// ``` +/// +pub fn max(a: Float, b: Float) -> Float { + case a >. b { + True -> a + False -> b + } +} + +/// Rounds the value to the next highest whole number as a `Float`. +/// +/// ## Examples +/// +/// ```gleam +/// > ceiling(2.3) +/// 3.0 +/// ``` +/// +pub fn ceiling(x: Float) -> Float { + do_ceiling(x) +} + +@external(erlang, "math", "ceil") +@external(javascript, "../gleam_stdlib.mjs", "ceiling") +fn do_ceiling(a: Float) -> Float + +/// Rounds the value to the next lowest whole number as a `Float`. +/// +/// ## Examples +/// +/// ```gleam +/// > floor(2.3) +/// 2.0 +/// ``` +/// +pub fn floor(x: Float) -> Float { + do_floor(x) +} + +@external(erlang, "math", "floor") +@external(javascript, "../gleam_stdlib.mjs", "floor") +fn do_floor(a: Float) -> Float + +/// Rounds the value to the nearest whole number as an `Int`. +/// +/// ## Examples +/// +/// ```gleam +/// > round(2.3) +/// 2 +/// ``` +/// +/// ```gleam +/// > round(2.5) +/// 3 +/// ``` +/// +pub fn round(x: Float) -> Int { + do_round(x) +} + +@target(erlang) +@external(erlang, "erlang", "round") +fn do_round(a: Float) -> Int + +@target(javascript) +fn do_round(x: Float) -> Int { + case x >=. 0.0 { + True -> js_round(x) + _ -> 0 - js_round(negate(x)) + } +} + +@target(javascript) +@external(javascript, "../gleam_stdlib.mjs", "round") +fn js_round(a: Float) -> Int + +/// Returns the value as an `Int`, truncating all decimal digits. +/// +/// ## Examples +/// +/// ```gleam +/// > truncate(2.4343434847383438) +/// 2 +/// ``` +/// +pub fn truncate(x: Float) -> Int { + do_truncate(x) +} + +@external(erlang, "erlang", "trunc") +@external(javascript, "../gleam_stdlib.mjs", "truncate") +fn do_truncate(a: Float) -> Int + +/// Returns the absolute value of the input as a `Float`. +/// +/// ## Examples +/// +/// ```gleam +/// > absolute_value(-12.5) +/// 12.5 +/// ``` +/// +/// ```gleam +/// > absolute_value(10.2) +/// 10.2 +/// ``` +/// +pub fn absolute_value(x: Float) -> Float { + case x >=. 0.0 { + True -> x + _ -> 0.0 -. x + } +} + +/// Returns the results of the base being raised to the power of the +/// exponent, as a `Float`. +/// +/// ## Examples +/// +/// ```gleam +/// > power(2.0, -1.0) +/// Ok(0.5) +/// ``` +/// +/// ```gleam +/// > power(2.0, 2.0) +/// Ok(4.0) +/// ``` +/// +/// ```gleam +/// > power(8.0, 1.5) +/// Ok(22.627416997969522) +/// ``` +/// +/// ```gleam +/// > 4.0 |> power(of: 2.0) +/// Ok(16.0) +/// ``` +/// +/// ```gleam +/// > power(-1.0, 0.5) +/// Error(Nil) +/// ``` +/// +pub fn power(base: Float, of exponent: Float) -> Result(Float, Nil) { + let fractional: Bool = ceiling(exponent) -. exponent >. 0.0 + // In the following check: + // 1. If the base is negative and the exponent is fractional then + // return an error as it will otherwise be an imaginary number + // 2. If the base is 0 and the exponent is negative then the expression + // is equivalent to the exponent divided by 0 and an error should be + // returned + case base <. 0.0 && fractional || base == 0.0 && exponent <. 0.0 { + True -> Error(Nil) + False -> Ok(do_power(base, exponent)) + } +} + +@external(erlang, "math", "pow") +@external(javascript, "../gleam_stdlib.mjs", "power") +fn do_power(a: Float, b: Float) -> Float + +/// Returns the square root of the input as a `Float`. +/// +/// ## Examples +/// +/// ```gleam +/// > square_root(4.0) +/// Ok(2.0) +/// ``` +/// +/// ```gleam +/// > square_root(-16.0) +/// Error(Nil) +/// ``` +/// +pub fn square_root(x: Float) -> Result(Float, Nil) { + power(x, 0.5) +} + +/// Returns the negative of the value provided. +/// +/// ## Examples +/// +/// ```gleam +/// > negate(1.0) +/// -1.0 +/// ``` +/// +pub fn negate(x: Float) -> Float { + -1.0 *. x +} + +/// Sums a list of `Float`s. +/// +/// ## Example +/// +/// ```gleam +/// > sum([1.0, 2.2, 3.3]) +/// 6.5 +/// ``` +/// +pub fn sum(numbers: List(Float)) -> Float { + numbers + |> do_sum(0.0) +} + +fn do_sum(numbers: List(Float), initial: Float) -> Float { + case numbers { + [] -> initial + [x, ..rest] -> do_sum(rest, x +. initial) + } +} + +/// Multiplies a list of `Float`s and returns the product. +/// +/// ## Example +/// +/// ```gleam +/// > product([2.5, 3.2, 4.2]) +/// 33.6 +/// ``` +/// +pub fn product(numbers: List(Float)) -> Float { + case numbers { + [] -> 1.0 + _ -> do_product(numbers, 1.0) + } +} + +fn do_product(numbers: List(Float), initial: Float) -> Float { + case numbers { + [] -> initial + [x, ..rest] -> do_product(rest, x *. initial) + } +} + +/// Generates a random float between the given zero (inclusive) and one +/// (exclusive). +/// +/// On Erlang this updates the random state in the process dictionary. +/// See: <https://www.erlang.org/doc/man/rand.html#uniform-0> +/// +/// ## Examples +/// +/// ```gleam +/// > random() +/// 0.646355926896028 +/// ``` +/// +@external(erlang, "rand", "uniform") +@external(javascript, "../gleam_stdlib.mjs", "random_uniform") +pub fn random() -> Float + +/// Returns division of the inputs as a `Result`. +/// +/// ## Examples +/// +/// ```gleam +/// > divide(0.0, 1.0) +/// Ok(1.0) +/// ``` +/// +/// ```gleam +/// > divide(1.0, 0.0) +/// Error(Nil) +/// ``` +/// +pub fn divide(a: Float, by b: Float) -> Result(Float, Nil) { + case b { + 0.0 -> Error(Nil) + b -> Ok(a /. b) + } +} + +/// Adds two floats together. +/// +/// It's the function equivalent of the `+.` operator. +/// This function is useful in higher order functions or pipes. +/// +/// ## Examples +/// +/// ```gleam +/// > add(1.0, 2.0) +/// 3.0 +/// ``` +/// +/// ```gleam +/// > import gleam/list +/// > list.fold([1.0, 2.0, 3.0], 0.0, add) +/// 6.0 +/// ``` +/// +/// ```gleam +/// > 3.0 |> add(2.0) +/// 5.0 +/// ``` +/// +pub fn add(a: Float, b: Float) -> Float { + a +. b +} + +/// Multiplies two floats together. +/// +/// It's the function equivalent of the `*.` operator. +/// This function is useful in higher order functions or pipes. +/// +/// ## Examples +/// +/// ```gleam +/// > multiply(2.0, 4.0) +/// 8.0 +/// ``` +/// +/// ```gleam +/// import gleam/list +/// > list.fold([2.0, 3.0, 4.0], 1.0, multiply) +/// 24.0 +/// ``` +/// +/// ```gleam +/// > 3.0 |> multiply(2.0) +/// 6.0 +/// ``` +/// +pub fn multiply(a: Float, b: Float) -> Float { + a *. b +} + +/// Subtracts one float from another. +/// +/// It's the function equivalent of the `-.` operator. +/// This function is useful in higher order functions or pipes. +/// +/// ## Examples +/// +/// ```gleam +/// > subtract(3.0, 1.0) +/// 2.0 +/// ``` +/// +/// ```gleam +/// > import gleam/list +/// > list.fold([1.0, 2.0, 3.0], 10.0, subtract) +/// 4.0 +/// ``` +/// +/// ```gleam +/// > 3.0 |> subtract(_, 2.0) +/// 1.0 +/// ``` +/// +/// ```gleam +/// > 3.0 |> subtract(2.0, _) +/// -1.0 +/// ``` +/// +pub fn subtract(a: Float, b: Float) -> Float { + a -. b +} diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/function.gleam b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/function.gleam new file mode 100644 index 0000000..daa997d --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/function.gleam @@ -0,0 +1,162 @@ +/// Takes two functions and chains them together to form one function that +/// takes the input from the first and returns the output of the second. +/// +pub fn compose(fun1: fn(a) -> b, fun2: fn(b) -> c) -> fn(a) -> c { + fn(a) { fun2(fun1(a)) } +} + +/// Takes a function with `2` arguments (an arity of `2`), and returns the +/// curried equivalent. +/// +/// `fn(a, b) -> c` becomes `fn(a) -> fn(b) -> c`. +/// +/// ## Examples +/// +/// *Currying* creates a new function that is identical to the given function +/// except that arguments must now be supplied one by one over several function +/// calls. It thus is the process of taking a function with `n` arguments +/// and producing a sequence of `n` single-argument functions. Given: +/// +/// ```gleam +/// > fn my_fun(i: Int, s: String) -> String { ... } +/// ``` +/// +/// …calling `curry2(my_fun)` would return the curried equivalent, like so: +/// +/// ```gleam +/// > curry2(my_fun) +/// fn(Int) -> fn(String) -> String +/// ``` +/// +/// Currying is useful when you want to partially apply a function with +/// some arguments and then pass it somewhere else, for example: +/// +/// ```gleam +/// > import gleam/list +/// > let multiply = curry2(fn(x, y) { x * y }) +/// > let doubles = list.map([1, 2, 3], multiply(2)) +/// [2, 4, 6] +/// ``` +/// +pub fn curry2(fun: fn(a, b) -> value) { + fn(a) { fn(b) { fun(a, b) } } +} + +/// Takes a function with `3` arguments (an arity of `3`), and returns the +/// curried equivalent. +/// +/// `fn(a, b, c) -> d` becomes `fn(a) -> fn(b) -> fn(c) -> d`. +/// +/// See [`curry2`](#curry2) for a detailed explanation. +/// +pub fn curry3(fun: fn(a, b, c) -> value) { + fn(a) { fn(b) { fn(c) { fun(a, b, c) } } } +} + +/// Takes a function with `4` arguments (an arity of `4`), and returns the +/// curried equivalent. +/// +/// `fn(a, b, c, d) -> e` becomes `fn(a) -> fn(b) -> fn(c) -> fn(d) -> e`. +/// +/// See [`curry2`](#curry2) for a detailed explanation. +/// +pub fn curry4(fun: fn(a, b, c, d) -> value) { + fn(a) { fn(b) { fn(c) { fn(d) { fun(a, b, c, d) } } } } +} + +/// Takes a function with `5` arguments (an arity of `5`), and returns the +/// curried equivalent. +/// +/// `fn(a, b, c, d, e) -> f` becomes +/// `fn(a) -> fn(b) -> fn(c) -> fn(d) -> fn(e) -> f`. +/// +/// See [`curry2`](#curry2) for a detailed explanation. +/// +pub fn curry5(fun: fn(a, b, c, d, e) -> value) { + fn(a) { fn(b) { fn(c) { fn(d) { fn(e) { fun(a, b, c, d, e) } } } } } +} + +/// Takes a function with `6` arguments (an arity of `6`), and returns the +/// curried equivalent. +/// +/// `fn(a, b, c, d, e, f) -> g` becomes +/// `fn(a) -> fn(b) -> fn(c) -> fn(d) -> fn(e) -> fn(f) -> g`. +/// +/// See [`curry2`](#curry2) for a detailed explanation. +/// +pub fn curry6(fun: fn(a, b, c, d, e, f) -> value) { + fn(a) { + fn(b) { fn(c) { fn(d) { fn(e) { fn(f) { fun(a, b, c, d, e, f) } } } } } + } +} + +/// Takes a function that takes two arguments and returns a new function that +/// takes the same two arguments, but in reverse order. +/// +pub fn flip(fun: fn(a, b) -> c) -> fn(b, a) -> c { + fn(b, a) { fun(a, b) } +} + +/// Takes a single argument and always returns its input value. +/// +pub fn identity(x: a) -> a { + x +} + +/// Takes a single argument and returns a new function that +/// ignores its argument and always returns the input value. +/// +pub fn constant(value: a) -> fn(b) -> a { + fn(_) { value } +} + +/// Takes an argument and a single function, +/// calls that function with that argument +/// and returns that argument instead of the function return value. +/// Useful for running synchronous side effects in a pipeline. +/// +pub fn tap(arg: a, effect: fn(a) -> b) -> a { + effect(arg) + arg +} + +/// Takes a function with arity one and an argument, +/// calls that function with the argument and returns the function return value. +/// +/// Useful for concisely calling functions returned as a part of a pipeline. +/// +/// ## Example +/// +/// ```gleam +/// > let doubler = fn() { +/// > fn(x: Int) { x * 2 } +/// > } +/// > +/// > doubler() +/// > |> apply1(2) +/// 4 +/// ``` +/// +pub fn apply1(fun: fn(a) -> value, arg1: a) -> value { + fun(arg1) +} + +/// Takes a function with arity two and two arguments, +/// calls that function with the arguments +/// and returns the function return value. +/// +/// See [`apply1`](#apply1) for more details. +/// +pub fn apply2(fun: fn(a, b) -> value, arg1: a, arg2: b) -> value { + fun(arg1, arg2) +} + +/// Takes a function with arity three and three arguments, +/// calls that function with the arguments +/// and returns the function return value. +/// +/// See [`apply1`](#apply1) for more details. +/// +pub fn apply3(fun: fn(a, b, c) -> value, arg1: a, arg2: b, arg3: c) -> value { + fun(arg1, arg2, arg3) +} diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/int.gleam b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/int.gleam new file mode 100644 index 0000000..86c77fa --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/int.gleam @@ -0,0 +1,886 @@ +//// Functions for working with integers. +//// +//// ## Division by zero +//// +//// In Erlang division by zero results in a crash, however Gleam does not have +//// partial functions and operators in core so instead division by zero returns +//// zero, a behaviour taken from Pony, Coq, and Lean. +//// +//// This may seem unexpected at first, but it is no less mathematically valid +//// than crashing or returning a special value. Division by zero is undefined +//// in mathematics. + +import gleam/float +import gleam/order.{type Order} + +/// Returns the absolute value of the input. +/// +/// ## Examples +/// +/// ```gleam +/// > absolute_value(-12) +/// 12 +/// ``` +/// +/// ```gleam +/// > absolute_value(10) +/// 10 +/// ``` +/// +pub fn absolute_value(x: Int) -> Int { + case x >= 0 { + True -> x + False -> x * -1 + } +} + +/// Returns the results of the base being raised to the power of the +/// exponent, as a `Float`. +/// +/// ## Examples +/// +/// ```gleam +/// > power(2, -1.0) +/// Ok(0.5) +/// ``` +/// +/// ```gleam +/// > power(2, 2.0) +/// Ok(4.0) +/// ``` +/// +/// ```gleam +/// > power(8, 1.5) +/// Ok(22.627416997969522) +/// ``` +/// +/// ```gleam +/// > 4 |> power(of: 2.0) +/// Ok(16.0) +/// ``` +/// +/// ```gleam +/// > power(-1, 0.5) +/// Error(Nil) +/// ``` +/// +pub fn power(base: Int, of exponent: Float) -> Result(Float, Nil) { + base + |> to_float() + |> float.power(exponent) +} + +/// Returns the square root of the input as a `Float`. +/// +/// ## Examples +/// +/// ```gleam +/// > square_root(4) +/// Ok(2.0) +/// ``` +/// +/// ```gleam +/// > square_root(-16) +/// Error(Nil) +/// ``` +/// +pub fn square_root(x: Int) -> Result(Float, Nil) { + x + |> to_float() + |> float.square_root() +} + +/// Parses a given string as an int if possible. +/// +/// ## Examples +/// +/// ```gleam +/// > parse("2") +/// Ok(2) +/// ``` +/// +/// ```gleam +/// > parse("ABC") +/// Error(Nil) +/// ``` +/// +pub fn parse(string: String) -> Result(Int, Nil) { + do_parse(string) +} + +@external(erlang, "gleam_stdlib", "parse_int") +@external(javascript, "../gleam_stdlib.mjs", "parse_int") +fn do_parse(a: String) -> Result(Int, Nil) + +/// Parses a given string as an int in a given base if possible. +/// Supports only bases 2 to 36, for values outside of which this function returns an `Error(Nil)`. +/// +/// ## Examples +/// +/// ```gleam +/// > base_parse("10", 2) +/// Ok(2) +/// +/// > base_parse("30", 16) +/// Ok(48) +/// +/// > base_parse("1C", 36) +/// Ok(48) +/// +/// > base_parse("48", 1) +/// Error(Nil) +/// +/// > base_parse("48", 37) +/// Error(Nil) +/// ``` +/// +pub fn base_parse(string: String, base: Int) -> Result(Int, Nil) { + case base >= 2 && base <= 36 { + True -> do_base_parse(string, base) + False -> Error(Nil) + } +} + +@external(erlang, "gleam_stdlib", "int_from_base_string") +@external(javascript, "../gleam_stdlib.mjs", "int_from_base_string") +fn do_base_parse(a: String, b: Int) -> Result(Int, Nil) + +/// Prints a given int to a string. +/// +/// ## Examples +/// +/// ```gleam +/// > to_string(2) +/// "2" +/// ``` +/// +pub fn to_string(x: Int) { + do_to_string(x) +} + +@external(erlang, "erlang", "integer_to_binary") +@external(javascript, "../gleam_stdlib.mjs", "to_string") +fn do_to_string(a: Int) -> String + +/// Error value when trying to operate with a base out of the allowed range. +/// +pub type InvalidBase { + InvalidBase +} + +/// Prints a given int to a string using the base number provided. +/// Supports only bases 2 to 36, for values outside of which this function returns an `Error(InvalidBase)`. +/// For common bases (2, 8, 16, 36), use the `to_baseN` functions. +/// +/// ## Examples +/// +/// ```gleam +/// > to_base_string(2, 2) +/// Ok("10") +/// ``` +/// +/// ```gleam +/// > to_base_string(48, 16) +/// Ok("30") +/// ``` +/// +/// ```gleam +/// > to_base_string(48, 36) +/// Ok("1C") +/// ``` +/// +/// ```gleam +/// > to_base_string(48, 1) +/// Error(InvalidBase) +/// ``` +/// +/// ```gleam +/// > to_base_string(48, 37) +/// Error(InvalidBase) +/// ``` +/// +pub fn to_base_string(x: Int, base: Int) -> Result(String, InvalidBase) { + case base >= 2 && base <= 36 { + True -> Ok(do_to_base_string(x, base)) + False -> Error(InvalidBase) + } +} + +@external(erlang, "erlang", "integer_to_binary") +@external(javascript, "../gleam_stdlib.mjs", "int_to_base_string") +fn do_to_base_string(a: Int, b: Int) -> String + +/// Prints a given int to a string using base-2. +/// +/// ## Examples +/// +/// ```gleam +/// > to_base2(2) +/// "10" +/// ``` +/// +pub fn to_base2(x: Int) -> String { + do_to_base_string(x, 2) +} + +/// Prints a given int to a string using base-8. +/// +/// ## Examples +/// +/// ```gleam +/// > to_base8(15) +/// "17" +/// ``` +/// +pub fn to_base8(x: Int) -> String { + do_to_base_string(x, 8) +} + +/// Prints a given int to a string using base-16. +/// +/// ## Examples +/// +/// ```gleam +/// > to_base16(48) +/// "30" +/// ``` +/// +pub fn to_base16(x: Int) -> String { + do_to_base_string(x, 16) +} + +/// Prints a given int to a string using base-36. +/// +/// ## Examples +/// +/// ```gleam +/// > to_base36(48) +/// "1C" +/// ``` +/// +pub fn to_base36(x: Int) -> String { + do_to_base_string(x, 36) +} + +/// Takes an int and returns its value as a float. +/// +/// ## Examples +/// +/// ```gleam +/// > to_float(5) +/// 5.0 +/// ``` +/// +/// ```gleam +/// > to_float(0) +/// 0.0 +/// ``` +/// +/// ```gleam +/// > to_float(-3) +/// -3.0 +/// ``` +/// +pub fn to_float(x: Int) -> Float { + do_to_float(x) +} + +@external(erlang, "erlang", "float") +@external(javascript, "../gleam_stdlib.mjs", "identity") +fn do_to_float(a: Int) -> Float + +/// Restricts an int between a lower and upper bound. +/// +/// ## Examples +/// +/// ```gleam +/// > clamp(40, min: 50, max: 60) +/// 50 +/// ``` +/// +pub fn clamp(x: Int, min min_bound: Int, max max_bound: Int) -> Int { + x + |> min(max_bound) + |> max(min_bound) +} + +/// Compares two ints, returning an order. +/// +/// ## Examples +/// +/// ```gleam +/// > compare(2, 3) +/// Lt +/// ``` +/// +/// ```gleam +/// > compare(4, 3) +/// Gt +/// ``` +/// +/// ```gleam +/// > compare(3, 3) +/// Eq +/// ``` +/// +pub fn compare(a: Int, with b: Int) -> Order { + case a == b { + True -> order.Eq + False -> + case a < b { + True -> order.Lt + False -> order.Gt + } + } +} + +/// Compares two ints, returning the smaller of the two. +/// +/// ## Examples +/// +/// ```gleam +/// > min(2, 3) +/// 2 +/// ``` +/// +pub fn min(a: Int, b: Int) -> Int { + case a < b { + True -> a + False -> b + } +} + +/// Compares two ints, returning the larger of the two. +/// +/// ## Examples +/// +/// ```gleam +/// > max(2, 3) +/// 3 +/// ``` +/// +pub fn max(a: Int, b: Int) -> Int { + case a > b { + True -> a + False -> b + } +} + +/// Returns whether the value provided is even. +/// +/// ## Examples +/// +/// ```gleam +/// > is_even(2) +/// True +/// ``` +/// +/// ```gleam +/// > is_even(3) +/// False +/// ``` +/// +pub fn is_even(x: Int) -> Bool { + x % 2 == 0 +} + +/// Returns whether the value provided is odd. +/// +/// ## Examples +/// +/// ```gleam +/// > is_odd(3) +/// True +/// ``` +/// +/// ```gleam +/// > is_odd(2) +/// False +/// ``` +/// +pub fn is_odd(x: Int) -> Bool { + x % 2 != 0 +} + +/// Returns the negative of the value provided. +/// +/// ## Examples +/// +/// ```gleam +/// > negate(1) +/// -1 +/// ``` +/// +pub fn negate(x: Int) -> Int { + -1 * x +} + +/// Sums a list of ints. +/// +/// ## Example +/// +/// ```gleam +/// > sum([1, 2, 3]) +/// 6 +/// ``` +/// +pub fn sum(numbers: List(Int)) -> Int { + numbers + |> do_sum(0) +} + +fn do_sum(numbers: List(Int), initial: Int) -> Int { + case numbers { + [] -> initial + [x, ..rest] -> do_sum(rest, x + initial) + } +} + +/// Multiplies a list of ints and returns the product. +/// +/// ## Example +/// +/// ```gleam +/// > product([2, 3, 4]) +/// 24 +/// ``` +/// +pub fn product(numbers: List(Int)) -> Int { + case numbers { + [] -> 1 + _ -> do_product(numbers, 1) + } +} + +fn do_product(numbers: List(Int), initial: Int) -> Int { + case numbers { + [] -> initial + [x, ..rest] -> do_product(rest, x * initial) + } +} + +/// Splits an integer into its digit representation in the specified base +/// +/// ## Examples +/// +/// ```gleam +/// > digits(234, 10) +/// Ok([2,3,4]) +/// ``` +/// +/// ```gleam +/// > digits(234, 1) +/// Error(InvalidBase) +/// ``` +/// +pub fn digits(x: Int, base: Int) -> Result(List(Int), InvalidBase) { + case base < 2 { + True -> Error(InvalidBase) + False -> Ok(do_digits(x, base, [])) + } +} + +fn do_digits(x: Int, base: Int, acc: List(Int)) -> List(Int) { + case absolute_value(x) < base { + True -> [x, ..acc] + False -> do_digits(x / base, base, [x % base, ..acc]) + } +} + +/// Joins a list of digits into a single value. +/// Returns an error if the base is less than 2 or if the list contains a digit greater than or equal to the specified base. +/// +/// ## Examples +/// +/// ```gleam +/// > undigits([2,3,4], 10) +/// Ok(234) +/// ``` +/// +/// ```gleam +/// > undigits([2,3,4], 1) +/// Error(InvalidBase) +/// ``` +/// +/// ```gleam +/// > undigits([2,3,4], 2) +/// Error(InvalidBase) +/// ``` +/// +pub fn undigits(numbers: List(Int), base: Int) -> Result(Int, InvalidBase) { + case base < 2 { + True -> Error(InvalidBase) + False -> do_undigits(numbers, base, 0) + } +} + +fn do_undigits( + numbers: List(Int), + base: Int, + acc: Int, +) -> Result(Int, InvalidBase) { + case numbers { + [] -> Ok(acc) + [digit, ..] if digit >= base -> Error(InvalidBase) + [digit, ..rest] -> do_undigits(rest, base, acc * base + digit) + } +} + +/// Generates a random int between zero and the given maximum. +/// +/// The lower number is inclusive, the upper number is exclusive. +/// +/// ## Examples +/// +/// ```gleam +/// > random(10) +/// 4 +/// ``` +/// +/// ```gleam +/// > random(1) +/// 0 +/// ``` +/// +/// ```gleam +/// > random(-1) +/// -1 +/// ``` +/// +pub fn random(max: Int) -> Int { + { float.random() *. to_float(max) } + |> float.floor() + |> float.round() +} + +/// Performs a truncated integer division. +/// +/// Returns division of the inputs as a `Result`: If the given divisor equals +/// `0`, this function returns an `Error`. +/// +/// ## Examples +/// +/// ```gleam +/// > divide(0, 1) +/// Ok(1) +/// ``` +/// +/// ```gleam +/// > divide(1, 0) +/// Error(Nil) +/// ``` +/// +/// ```gleam +/// > divide(5, 2) +/// Ok(2) +/// ``` +/// +/// ```gleam +/// > divide(-99, 2) +/// Ok(-49) +/// ``` +/// +pub fn divide(dividend: Int, by divisor: Int) -> Result(Int, Nil) { + case divisor { + 0 -> Error(Nil) + divisor -> Ok(dividend / divisor) + } +} + +/// Computes the remainder of an integer division of inputs as a `Result`. +/// +/// Returns division of the inputs as a `Result`: If the given divisor equals +/// `0`, this function returns an `Error`. +/// +/// Most the time you will want to use the `%` operator instead of this +/// function. +/// +/// ## Examples +/// +/// ```gleam +/// > remainder(3, 2) +/// Ok(1) +/// ``` +/// +/// ```gleam +/// > remainder(1, 0) +/// Error(Nil) +/// ``` +/// +/// ```gleam +/// > remainder(10, -1) +/// Ok(0) +/// ``` +/// +/// ```gleam +/// > remainder(13, by: 3) +/// Ok(1) +/// ``` +/// +/// ```gleam +/// > remainder(-13, by: 3) +/// Ok(-1) +/// ``` +/// +/// ```gleam +/// > remainder(13, by: -3) +/// Ok(1) +/// ``` +/// +/// ```gleam +/// > remainder(-13, by: -3) +/// Ok(-1) +/// ``` +/// +pub fn remainder(dividend: Int, by divisor: Int) -> Result(Int, Nil) { + case divisor { + 0 -> Error(Nil) + divisor -> Ok(dividend % divisor) + } +} + +/// Computes the modulo of an integer division of inputs as a `Result`. +/// +/// Returns division of the inputs as a `Result`: If the given divisor equals +/// `0`, this function returns an `Error`. +/// +/// Most the time you will want to use the `%` operator instead of this +/// function. +/// +/// ## Examples +/// +/// ```gleam +/// > modulo(3, 2) +/// Ok(1) +/// ``` +/// +/// ```gleam +/// > modulo(1, 0) +/// Error(Nil) +/// ``` +/// +/// ```gleam +/// > modulo(10, -1) +/// Ok(0) +/// ``` +/// +/// ```gleam +/// > modulo(13, by: 3) +/// Ok(1) +/// ``` +/// +/// ```gleam +/// > modulo(-13, by: 3) +/// Ok(2) +/// ``` +/// +/// ```gleam +/// > modulo(13, by: -3) +/// Ok(-2) +/// ``` +/// +/// ```gleam +/// > modulo(-13, by: -3) +/// Ok(-1) +/// ``` +/// +pub fn modulo(dividend: Int, by divisor: Int) -> Result(Int, Nil) { + case divisor { + 0 -> Error(Nil) + _ -> { + let remainder = dividend % divisor + case remainder * divisor < 0 { + True -> Ok(remainder + divisor) + False -> Ok(remainder) + } + } + } +} + +/// Performs a *floored* integer division, which means that the result will +/// always be rounded towards negative infinity. +/// +/// If you want to perform truncated integer division (rounding towards zero), +/// use `int.divide()` or the `/` operator instead. +/// +/// Returns division of the inputs as a `Result`: If the given divisor equals +/// `0`, this function returns an `Error`. +/// +/// ## Examples +/// +/// ```gleam +/// > floor_divide(1, 0) +/// Error(Nil) +/// ``` +/// +/// ```gleam +/// > floor_divide(5, 2) +/// Ok(2) +/// ``` +/// +/// ```gleam +/// > floor_divide(6, -4) +/// Ok(-2) +/// ``` +/// +/// ```gleam +/// > floor_divide(-99, 2) +/// Ok(-50) +/// ``` +/// +pub fn floor_divide(dividend: Int, by divisor: Int) -> Result(Int, Nil) { + case divisor { + 0 -> Error(Nil) + divisor -> + case dividend * divisor < 0 && dividend % divisor != 0 { + True -> Ok(dividend / divisor - 1) + False -> Ok(dividend / divisor) + } + } +} + +/// Adds two integers together. +/// +/// It's the function equivalent of the `+` operator. +/// This function is useful in higher order functions or pipes. +/// +/// ## Examples +/// +/// ```gleam +/// > add(1, 2) +/// 3 +/// ``` +/// +/// ```gleam +/// import gleam/list +/// > list.fold([1, 2, 3], 0, add) +/// 6 +/// ``` +/// +/// ```gleam +/// > 3 |> add(2) +/// 5 +/// ``` +/// +pub fn add(a: Int, b: Int) -> Int { + a + b +} + +/// Multiplies two integers together. +/// +/// It's the function equivalent of the `*` operator. +/// This function is useful in higher order functions or pipes. +/// +/// ## Examples +/// +/// ```gleam +/// > multiply(2, 4) +/// 8 +/// ``` +/// +/// ```gleam +/// import gleam/list +/// > list.fold([2, 3, 4], 1, multiply) +/// 24 +/// ``` +/// +/// ```gleam +/// > 3 |> multiply(2) +/// 6 +/// ``` +/// +pub fn multiply(a: Int, b: Int) -> Int { + a * b +} + +/// Subtracts one int from another. +/// +/// It's the function equivalent of the `-` operator. +/// This function is useful in higher order functions or pipes. +/// +/// ## Examples +/// +/// ```gleam +/// > subtract(3, 1) +/// 2.0 +/// ``` +/// +/// ```gleam +/// import gleam/list +/// > list.fold([1, 2, 3], 10, subtract) +/// 4 +/// ``` +/// +/// ```gleam +/// > 3 |> subtract(2) +/// 1 +/// ``` +/// +/// ```gleam +/// > 3 |> subtract(2, _) +/// -1 +/// ``` +/// +pub fn subtract(a: Int, b: Int) -> Int { + a - b +} + +/// Calculates the bitwise AND of its arguments. +/// +/// The exact behaviour of this function depends on the target platform. +/// On Erlang it is equivalent to bitwise operations on ints, on JavaScript it +/// is equivalent to bitwise operations on big-ints. +/// +@external(erlang, "erlang", "band") +@external(javascript, "../gleam_stdlib.mjs", "bitwise_and") +pub fn bitwise_and(x: Int, y: Int) -> Int + +/// Calculates the bitwise NOT of its argument. +/// +/// The exact behaviour of this function depends on the target platform. +/// On Erlang it is equivalent to bitwise operations on ints, on JavaScript it +/// is equivalent to bitwise operations on big-ints. +/// +@external(erlang, "erlang", "bnot") +@external(javascript, "../gleam_stdlib.mjs", "bitwise_not") +pub fn bitwise_not(x: Int) -> Int + +/// Calculates the bitwise OR of its arguments. +/// +/// The exact behaviour of this function depends on the target platform. +/// On Erlang it is equivalent to bitwise operations on ints, on JavaScript it +/// is equivalent to bitwise operations on big-ints. +/// +@external(erlang, "erlang", "bor") +@external(javascript, "../gleam_stdlib.mjs", "bitwise_or") +pub fn bitwise_or(x: Int, y: Int) -> Int + +/// Calculates the bitwise XOR of its arguments. +/// +/// The exact behaviour of this function depends on the target platform. +/// On Erlang it is equivalent to bitwise operations on ints, on JavaScript it +/// is equivalent to bitwise operations on big-ints. +/// +@external(erlang, "erlang", "bxor") +@external(javascript, "../gleam_stdlib.mjs", "bitwise_exclusive_or") +pub fn bitwise_exclusive_or(x: Int, y: Int) -> Int + +/// Calculates the result of an arithmetic left bitshift. +/// +/// The exact behaviour of this function depends on the target platform. +/// On Erlang it is equivalent to bitwise operations on ints, on JavaScript it +/// is equivalent to bitwise operations on big-ints. +/// +@external(erlang, "erlang", "bsl") +@external(javascript, "../gleam_stdlib.mjs", "bitwise_shift_left") +pub fn bitwise_shift_left(x: Int, y: Int) -> Int + +/// Calculates the result of an arithmetic right bitshift. +/// +/// The exact behaviour of this function depends on the target platform. +/// On Erlang it is equivalent to bitwise operations on ints, on JavaScript it +/// is equivalent to bitwise operations on big-ints. +/// +@external(erlang, "erlang", "bsr") +@external(javascript, "../gleam_stdlib.mjs", "bitwise_shift_right") +pub fn bitwise_shift_right(x: Int, y: Int) -> Int diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/io.gleam b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/io.gleam new file mode 100644 index 0000000..0c0a3ee --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/io.gleam @@ -0,0 +1,117 @@ +import gleam/string + +/// Writes a string to standard output. +/// +/// If you want your output to be printed on its own line see `println`. +/// +/// ## Example +/// +/// ```gleam +/// > io.print("Hi mum") +/// // -> Hi mum +/// Nil +/// ``` +/// +pub fn print(string: String) -> Nil { + do_print(string) +} + +@external(erlang, "gleam_stdlib", "print") +@external(javascript, "../gleam_stdlib.mjs", "print") +fn do_print(string string: String) -> Nil + +/// Writes a string to standard error. +/// +/// If you want your output to be printed on its own line see `println_error`. +/// +/// ## Example +/// +/// ``` +/// > io.print_error("Hi pop") +/// // -> Hi pop +/// Nil +/// ``` +/// +pub fn print_error(string: String) -> Nil { + do_print_error(string) +} + +@external(erlang, "gleam_stdlib", "print_error") +@external(javascript, "../gleam_stdlib.mjs", "print_error") +fn do_print_error(string string: String) -> Nil + +/// Writes a string to standard output, appending a newline to the end. +/// +/// ## Example +/// +/// ```gleam +/// > io.println("Hi mum") +/// // -> Hi mum +/// Nil +/// ``` +/// +pub fn println(string: String) -> Nil { + do_println(string) +} + +@external(erlang, "gleam_stdlib", "println") +@external(javascript, "../gleam_stdlib.mjs", "console_log") +fn do_println(string string: String) -> Nil + +/// Writes a string to standard error, appending a newline to the end. +/// +/// ## Example +/// +/// ```gleam +/// > io.println_error("Hi pop") +/// // -> Hi mum +/// Nil +/// ``` +/// +pub fn println_error(string: String) -> Nil { + do_println_error(string) +} + +@external(erlang, "gleam_stdlib", "println_error") +@external(javascript, "../gleam_stdlib.mjs", "console_error") +fn do_println_error(string string: String) -> Nil + +/// Prints a value to standard error (stderr) yielding Gleam syntax. +/// +/// The value is returned after being printed so it can be used in pipelines. +/// +/// ## Example +/// +/// ```gleam +/// > debug("Hi mum") +/// // -> <<"Hi mum">> +/// "Hi mum" +/// ``` +/// +/// ```gleam +/// > debug(Ok(1)) +/// // -> {ok, 1} +/// Ok(1) +/// ``` +/// +/// ```gleam +/// > import list +/// > [1, 2] +/// > |> list.map(fn(x) { x + 1 }) +/// > |> debug +/// > |> list.map(fn(x) { x * 2 }) +/// // -> [2, 3] +/// [4, 6] +/// ``` +/// +pub fn debug(term: anything) -> anything { + term + |> string.inspect + |> do_debug_println + + term +} + +@external(erlang, "gleam_stdlib", "println_error") +@external(javascript, "../gleam_stdlib.mjs", "print_debug") +fn do_debug_println(string string: String) -> Nil diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/iterator.gleam b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/iterator.gleam new file mode 100644 index 0000000..4cbb6f5 --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/iterator.gleam @@ -0,0 +1,1524 @@ +import gleam/result +import gleam/int +import gleam/list +import gleam/dict.{type Dict} +import gleam/option.{type Option, None, Some} +import gleam/order + +// Internal private representation of an Iterator +type Action(element) { + // Dedicated to Electric Six + // https://youtu.be/_30t2dzEgiw?t=162 + Stop + Continue(element, fn() -> Action(element)) +} + +/// An iterator is a lazily evaluated sequence of element. +/// +/// Iterators are useful when working with collections that are too large to +/// fit in memory (or those that are infinite in size) as they only require the +/// elements currently being processed to be in memory. +/// +/// As a lazy data structure no work is done when an iterator is filters, +/// mapped, etc, instead a new iterator is returned with these transformations +/// applied to the stream. Once the stream has all the required transformations +/// applied it can be evaluated using functions such as `fold` and `to_list`. +/// +pub opaque type Iterator(element) { + Iterator(continuation: fn() -> Action(element)) +} + +// Public API for iteration +pub type Step(element, accumulator) { + Next(element: element, accumulator: accumulator) + Done +} + +// Shortcut for an empty iterator. +fn stop() -> Action(element) { + Stop +} + +// Creating Iterators +fn do_unfold( + initial: acc, + f: fn(acc) -> Step(element, acc), +) -> fn() -> Action(element) { + fn() { + case f(initial) { + Next(x, acc) -> Continue(x, do_unfold(acc, f)) + Done -> Stop + } + } +} + +/// Creates an iterator from a given function and accumulator. +/// +/// The function is called on the accumulator and returns either `Done`, +/// indicating the iterator has no more elements, or `Next` which contains a +/// new element and accumulator. The element is yielded by the iterator and the +/// new accumulator is used with the function to compute the next element in +/// the sequence. +/// +/// ## Examples +/// +/// ```gleam +/// > unfold(from: 5, with: fn(n) { +/// > case n { +/// > 0 -> Done +/// > n -> Next(element: n, accumulator: n - 1) +/// > } +/// > }) +/// > |> to_list +/// [5, 4, 3, 2, 1] +/// ``` +/// +pub fn unfold( + from initial: acc, + with f: fn(acc) -> Step(element, acc), +) -> Iterator(element) { + initial + |> do_unfold(f) + |> Iterator +} + +// TODO: test +/// Creates an iterator that yields values created by calling a given function +/// repeatedly. +/// +pub fn repeatedly(f: fn() -> element) -> Iterator(element) { + unfold(Nil, fn(_) { Next(f(), Nil) }) +} + +/// Creates an iterator that returns the same value infinitely. +/// +/// ## Examples +/// +/// ```gleam +/// > repeat(10) +/// > |> take(4) +/// > |> to_list +/// [10, 10, 10, 10] +/// ``` +/// +pub fn repeat(x: element) -> Iterator(element) { + repeatedly(fn() { x }) +} + +/// Creates an iterator that yields each element from the given list. +/// +/// ## Examples +/// +/// ```gleam +/// > from_list([1, 2, 3, 4]) +/// > |> to_list +/// [1, 2, 3, 4] +/// ``` +/// +pub fn from_list(list: List(element)) -> Iterator(element) { + let yield = fn(acc) { + case acc { + [] -> Done + [head, ..tail] -> Next(head, tail) + } + } + unfold(list, yield) +} + +// Consuming Iterators +fn do_transform( + continuation: fn() -> Action(a), + state: acc, + f: fn(acc, a) -> Step(b, acc), +) -> fn() -> Action(b) { + fn() { + case continuation() { + Stop -> Stop + Continue(el, next) -> + case f(state, el) { + Done -> Stop + Next(yield, next_state) -> + Continue(yield, do_transform(next, next_state, f)) + } + } + } +} + +/// Creates an iterator from an existing iterator +/// and a stateful function that may short-circuit. +/// +/// `f` takes arguments `acc` for current state and `el` for current element from underlying iterator, +/// and returns either `Next` with yielded element and new state value, or `Done` to halt the iterator. +/// +/// ## Examples +/// +/// Approximate implementation of `index` in terms of `transform`: +/// +/// ```gleam +/// > from_list(["a", "b", "c"]) +/// > |> transform(0, fn(i, el) { Next(#(i, el), i + 1) }) +/// > |> to_list +/// [#(0, "a"), #(1, "b"), #(2, "c")] +/// ``` +pub fn transform( + over iterator: Iterator(a), + from initial: acc, + with f: fn(acc, a) -> Step(b, acc), +) -> Iterator(b) { + do_transform(iterator.continuation, initial, f) + |> Iterator +} + +fn do_fold( + continuation: fn() -> Action(e), + f: fn(acc, e) -> acc, + accumulator: acc, +) -> acc { + case continuation() { + Continue(elem, next) -> do_fold(next, f, f(accumulator, elem)) + Stop -> accumulator + } +} + +/// Reduces an iterator of elements into a single value by calling a given +/// function on each element in turn. +/// +/// If called on an iterator of infinite length then this function will never +/// return. +/// +/// If you do not care about the end value and only wish to evaluate the +/// iterator for side effects consider using the `run` function instead. +/// +/// ## Examples +/// +/// ```gleam +/// > [1, 2, 3, 4] +/// > |> from_list +/// > |> fold(from: 0, with: fn(acc, element) { element + acc }) +/// 10 +/// ``` +/// +pub fn fold( + over iterator: Iterator(e), + from initial: acc, + with f: fn(acc, e) -> acc, +) -> acc { + iterator.continuation + |> do_fold(f, initial) +} + +// TODO: test +/// Evaluates all elements emitted by the given iterator. This function is useful for when +/// you wish to trigger any side effects that would occur when evaluating +/// the iterator. +/// +pub fn run(iterator: Iterator(e)) -> Nil { + fold(iterator, Nil, fn(_, _) { Nil }) +} + +/// Evaluates an iterator and returns all the elements as a list. +/// +/// If called on an iterator of infinite length then this function will never +/// return. +/// +/// ## Examples +/// +/// ```gleam +/// > [1, 2, 3] +/// > |> from_list +/// > |> map(fn(x) { x * 2 }) +/// > |> to_list +/// [2, 4, 6] +/// ``` +/// +pub fn to_list(iterator: Iterator(element)) -> List(element) { + iterator + |> fold([], fn(acc, e) { [e, ..acc] }) + |> list.reverse +} + +/// Eagerly accesses the first value of an iterator, returning a `Next` +/// that contains the first value and the rest of the iterator. +/// +/// If called on an empty iterator, `Done` is returned. +/// +/// ## Examples +/// +/// ```gleam +/// > let assert Next(first, rest) = [1, 2, 3, 4] +/// > |> from_list +/// > |> step +/// > first +/// 1 +/// ``` +/// +/// ```gleam +/// > rest |> to_list +/// [2, 3, 4] +/// ``` +/// +/// ```gleam +/// > empty() |> step +/// Done +/// ``` +/// +pub fn step(iterator: Iterator(e)) -> Step(e, Iterator(e)) { + case iterator.continuation() { + Stop -> Done + Continue(e, a) -> Next(e, Iterator(a)) + } +} + +fn do_take(continuation: fn() -> Action(e), desired: Int) -> fn() -> Action(e) { + fn() { + case desired > 0 { + False -> Stop + True -> + case continuation() { + Stop -> Stop + Continue(e, next) -> Continue(e, do_take(next, desired - 1)) + } + } + } +} + +/// Creates an iterator that only yields the first `desired` elements. +/// +/// If the iterator does not have enough elements all of them are yielded. +/// +/// ## Examples +/// +/// ```gleam +/// > [1, 2, 3, 4, 5] +/// > |> from_list +/// > |> take(up_to: 3) +/// > |> to_list +/// [1, 2, 3] +/// ``` +/// +/// ```gleam +/// > [1, 2] +/// > |> from_list +/// > |> take(up_to: 3) +/// > |> to_list +/// [1, 2] +/// ``` +/// +pub fn take(from iterator: Iterator(e), up_to desired: Int) -> Iterator(e) { + iterator.continuation + |> do_take(desired) + |> Iterator +} + +fn do_drop(continuation: fn() -> Action(e), desired: Int) -> Action(e) { + case continuation() { + Stop -> Stop + Continue(e, next) -> + case desired > 0 { + True -> do_drop(next, desired - 1) + False -> Continue(e, next) + } + } +} + +/// Evaluates and discards the first N elements in an iterator, returning a new +/// iterator. +/// +/// If the iterator does not have enough elements an empty iterator is +/// returned. +/// +/// This function does not evaluate the elements of the iterator, the +/// computation is performed when the iterator is later run. +/// +/// ## Examples +/// +/// ```gleam +/// > [1, 2, 3, 4, 5] +/// > |> from_list +/// > |> drop(up_to: 3) +/// > |> to_list +/// [4, 5] +/// ``` +/// +/// ```gleam +/// > [1, 2] +/// > |> from_list +/// > |> drop(up_to: 3) +/// > |> to_list +/// [] +/// ``` +/// +pub fn drop(from iterator: Iterator(e), up_to desired: Int) -> Iterator(e) { + fn() { do_drop(iterator.continuation, desired) } + |> Iterator +} + +fn do_map(continuation: fn() -> Action(a), f: fn(a) -> b) -> fn() -> Action(b) { + fn() { + case continuation() { + Stop -> Stop + Continue(e, continuation) -> Continue(f(e), do_map(continuation, f)) + } + } +} + +/// Creates an iterator from an existing iterator and a transformation function. +/// +/// Each element in the new iterator will be the result of calling the given +/// function on the elements in the given iterator. +/// +/// This function does not evaluate the elements of the iterator, the +/// computation is performed when the iterator is later run. +/// +/// ## Examples +/// +/// ```gleam +/// > [1, 2, 3] +/// > |> from_list +/// > |> map(fn(x) { x * 2 }) +/// > |> to_list +/// [2, 4, 6] +/// ``` +/// +pub fn map(over iterator: Iterator(a), with f: fn(a) -> b) -> Iterator(b) { + iterator.continuation + |> do_map(f) + |> Iterator +} + +fn do_map2( + continuation1: fn() -> Action(a), + continuation2: fn() -> Action(b), + with fun: fn(a, b) -> c, +) -> fn() -> Action(c) { + fn() { + case continuation1() { + Stop -> Stop + Continue(a, next_a) -> + case continuation2() { + Stop -> Stop + Continue(b, next_b) -> + Continue(fun(a, b), do_map2(next_a, next_b, fun)) + } + } + } +} + +/// Combines two interators into a single one using the given function. +/// +/// If an iterator is longer than the other the extra elements are dropped. +/// +/// This function does not evaluate the elements of the two iterators, the +/// computation is performed when the resulting iterator is later run. +/// +/// ## Examples +/// +/// ```gleam +/// let first = from_list([1, 2, 3]) +/// let second = from_list([4, 5, 6]) +/// map2(first, second, fn(x, y) { x + y }) |> to_list +/// // -> [5, 7, 9] +/// ``` +/// +/// ```gleam +/// let first = from_list([1, 2]) +/// let second = from_list(["a", "b", "c"]) +/// map2(first, second, fn(i, x) { #(i, x) }) |> to_list +/// // -> [#(1, "a"), #(2, "b")] +/// ``` +/// +pub fn map2( + iterator1: Iterator(a), + iterator2: Iterator(b), + with fun: fn(a, b) -> c, +) -> Iterator(c) { + do_map2(iterator1.continuation, iterator2.continuation, fun) + |> Iterator +} + +fn do_append(first: fn() -> Action(a), second: fn() -> Action(a)) -> Action(a) { + case first() { + Continue(e, first) -> Continue(e, fn() { do_append(first, second) }) + Stop -> second() + } +} + +/// Appends two iterators, producing a new iterator. +/// +/// This function does not evaluate the elements of the iterators, the +/// computation is performed when the resulting iterator is later run. +/// +/// ## Examples +/// +/// ```gleam +/// > [1, 2] +/// > |> from_list +/// > |> append([3, 4] |> from_list) +/// > |> to_list +/// [1, 2, 3, 4] +/// ``` +/// +pub fn append(to first: Iterator(a), suffix second: Iterator(a)) -> Iterator(a) { + fn() { do_append(first.continuation, second.continuation) } + |> Iterator +} + +fn do_flatten(flattened: fn() -> Action(Iterator(a))) -> Action(a) { + case flattened() { + Stop -> Stop + Continue(it, next_iterator) -> + do_append(it.continuation, fn() { do_flatten(next_iterator) }) + } +} + +/// Flattens an iterator of iterators, creating a new iterator. +/// +/// This function does not evaluate the elements of the iterator, the +/// computation is performed when the iterator is later run. +/// +/// ## Examples +/// +/// ```gleam +/// > from_list([[1, 2], [3, 4]]) +/// > |> map(from_list) +/// > |> flatten +/// > |> to_list +/// [1, 2, 3, 4] +/// ``` +/// +pub fn flatten(iterator: Iterator(Iterator(a))) -> Iterator(a) { + fn() { do_flatten(iterator.continuation) } + |> Iterator +} + +/// Joins a list of iterators into a single iterator. +/// +/// This function does not evaluate the elements of the iterator, the +/// computation is performed when the iterator is later run. +/// +/// ## Examples +/// +/// ```gleam +/// > [[1, 2], [3, 4]] +/// > |> map(from_list) +/// > |> concat +/// > |> to_list +/// [1, 2, 3, 4] +/// ``` +/// +pub fn concat(iterators: List(Iterator(a))) -> Iterator(a) { + flatten(from_list(iterators)) +} + +/// Creates an iterator from an existing iterator and a transformation function. +/// +/// Each element in the new iterator will be the result of calling the given +/// function on the elements in the given iterator and then flattening the +/// results. +/// +/// This function does not evaluate the elements of the iterator, the +/// computation is performed when the iterator is later run. +/// +/// ## Examples +/// +/// ```gleam +/// > [1, 2] +/// > |> from_list +/// > |> flat_map(fn(x) { from_list([x, x + 1]) }) +/// > |> to_list +/// [1, 2, 2, 3] +/// ``` +/// +pub fn flat_map( + over iterator: Iterator(a), + with f: fn(a) -> Iterator(b), +) -> Iterator(b) { + iterator + |> map(f) + |> flatten +} + +fn do_filter( + continuation: fn() -> Action(e), + predicate: fn(e) -> Bool, +) -> Action(e) { + case continuation() { + Stop -> Stop + Continue(e, iterator) -> + case predicate(e) { + True -> Continue(e, fn() { do_filter(iterator, predicate) }) + False -> do_filter(iterator, predicate) + } + } +} + +/// Creates an iterator from an existing iterator and a predicate function. +/// +/// The new iterator will contain elements from the first iterator for which +/// the given function returns `True`. +/// +/// This function does not evaluate the elements of the iterator, the +/// computation is performed when the iterator is later run. +/// +/// ## Examples +/// +/// ```gleam +/// > import gleam/int +/// > [1, 2, 3, 4] +/// > |> from_list +/// > |> filter(int.is_even) +/// > |> to_list +/// [2, 4] +/// ``` +/// +pub fn filter( + iterator: Iterator(a), + keeping predicate: fn(a) -> Bool, +) -> Iterator(a) { + fn() { do_filter(iterator.continuation, predicate) } + |> Iterator +} + +/// Creates an iterator that repeats a given iterator infinitely. +/// +/// ## Examples +/// +/// ```gleam +/// > [1, 2] +/// > |> from_list +/// > |> cycle +/// > |> take(6) +/// > |> to_list +/// [1, 2, 1, 2, 1, 2] +/// ``` +/// +pub fn cycle(iterator: Iterator(a)) -> Iterator(a) { + repeat(iterator) + |> flatten +} + +/// Creates an iterator of ints, starting at a given start int and stepping by +/// one to a given end int. +/// +/// ## Examples +/// +/// ```gleam +/// > range(from: 1, to: 5) |> to_list +/// [1, 2, 3, 4, 5] +/// ``` +/// +/// ```gleam +/// > range(from: 1, to: -2) |> to_list +/// [1, 0, -1, -2] +/// ``` +/// +/// ```gleam +/// > range(from: 0, to: 0) |> to_list +/// [0] +/// ``` +/// +pub fn range(from start: Int, to stop: Int) -> Iterator(Int) { + case int.compare(start, stop) { + order.Eq -> once(fn() { start }) + order.Gt -> + unfold(from: start, with: fn(current) { + case current < stop { + False -> Next(current, current - 1) + True -> Done + } + }) + + order.Lt -> + unfold(from: start, with: fn(current) { + case current > stop { + False -> Next(current, current + 1) + True -> Done + } + }) + } +} + +fn do_find(continuation: fn() -> Action(a), f: fn(a) -> Bool) -> Result(a, Nil) { + case continuation() { + Stop -> Error(Nil) + Continue(e, next) -> + case f(e) { + True -> Ok(e) + False -> do_find(next, f) + } + } +} + +/// Finds the first element in a given iterator for which the given function returns +/// `True`. +/// +/// Returns `Error(Nil)` if the function does not return `True` for any of the +/// elements. +/// +/// ## Examples +/// +/// ```gleam +/// > find(from_list([1, 2, 3]), fn(x) { x > 2 }) +/// Ok(3) +/// ``` +/// +/// ```gleam +/// > find(from_list([1, 2, 3]), fn(x) { x > 4 }) +/// Error(Nil) +/// ``` +/// +/// ```gleam +/// > find(empty(), fn(_) { True }) +/// Error(Nil) +/// ``` +/// +pub fn find( + in haystack: Iterator(a), + one_that is_desired: fn(a) -> Bool, +) -> Result(a, Nil) { + haystack.continuation + |> do_find(is_desired) +} + +fn do_index( + continuation: fn() -> Action(element), + next: Int, +) -> fn() -> Action(#(element, Int)) { + fn() { + case continuation() { + Stop -> Stop + Continue(e, continuation) -> + Continue(#(e, next), do_index(continuation, next + 1)) + } + } +} + +/// Wraps values yielded from an iterator with indices, starting from 0. +/// +/// ## Examples +/// +/// ```gleam +/// > from_list(["a", "b", "c"]) |> index |> to_list +/// [#("a", 0), #("b", 1), #("c", 2)] +/// ``` +/// +pub fn index(over iterator: Iterator(element)) -> Iterator(#(element, Int)) { + iterator.continuation + |> do_index(0) + |> Iterator +} + +/// Creates an iterator that inifinitely applies a function to a value. +/// +/// ## Examples +/// +/// ```gleam +/// > iterate(1, fn(n) { n * 3 }) |> take(5) |> to_list +/// [1, 3, 9, 27, 81] +/// ``` +/// +pub fn iterate( + from initial: element, + with f: fn(element) -> element, +) -> Iterator(element) { + unfold(initial, fn(element) { Next(element, f(element)) }) +} + +fn do_take_while( + continuation: fn() -> Action(element), + predicate: fn(element) -> Bool, +) -> fn() -> Action(element) { + fn() { + case continuation() { + Stop -> Stop + Continue(e, next) -> + case predicate(e) { + False -> Stop + True -> Continue(e, do_take_while(next, predicate)) + } + } + } +} + +/// Creates an iterator that yields elements while the predicate returns `True`. +/// +/// ## Examples +/// +/// ```gleam +/// > from_list([1, 2, 3, 2, 4]) +/// > |> take_while(satisfying: fn(x) { x < 3 }) +/// > |> to_list +/// [1, 2] +/// ``` +/// +pub fn take_while( + in iterator: Iterator(element), + satisfying predicate: fn(element) -> Bool, +) -> Iterator(element) { + iterator.continuation + |> do_take_while(predicate) + |> Iterator +} + +fn do_drop_while( + continuation: fn() -> Action(element), + predicate: fn(element) -> Bool, +) -> Action(element) { + case continuation() { + Stop -> Stop + Continue(e, next) -> + case predicate(e) { + False -> Continue(e, next) + True -> do_drop_while(next, predicate) + } + } +} + +/// Creates an iterator that drops elements while the predicate returns `True`, +/// and then yields the remaining elements. +/// +/// ## Examples +/// +/// ```gleam +/// > from_list([1, 2, 3, 4, 2, 5]) +/// > |> drop_while(satisfying: fn(x) { x < 4 }) +/// > |> to_list +/// [4, 2, 5] +/// ``` +/// +pub fn drop_while( + in iterator: Iterator(element), + satisfying predicate: fn(element) -> Bool, +) -> Iterator(element) { + fn() { do_drop_while(iterator.continuation, predicate) } + |> Iterator +} + +fn do_scan( + continuation: fn() -> Action(element), + f: fn(acc, element) -> acc, + accumulator: acc, +) -> fn() -> Action(acc) { + fn() { + case continuation() { + Stop -> Stop + Continue(el, next) -> { + let accumulated = f(accumulator, el) + Continue(accumulated, do_scan(next, f, accumulated)) + } + } + } +} + +/// Creates an iterator from an existing iterator and a stateful function. +/// +/// Specifically, this behaves like `fold`, but yields intermediate results. +/// +/// ## Examples +/// +/// ```gleam +/// // Generate a sequence of partial sums +/// > from_list([1, 2, 3, 4, 5]) +/// > |> scan(from: 0, with: fn(acc, el) { acc + el }) +/// > |> to_list +/// [1, 3, 6, 10, 15] +/// ``` +/// +pub fn scan( + over iterator: Iterator(element), + from initial: acc, + with f: fn(acc, element) -> acc, +) -> Iterator(acc) { + iterator.continuation + |> do_scan(f, initial) + |> Iterator +} + +fn do_zip( + left: fn() -> Action(a), + right: fn() -> Action(b), +) -> fn() -> Action(#(a, b)) { + fn() { + case left() { + Stop -> Stop + Continue(el_left, next_left) -> + case right() { + Stop -> Stop + Continue(el_right, next_right) -> + Continue(#(el_left, el_right), do_zip(next_left, next_right)) + } + } + } +} + +/// Zips two iterators together, emitting values from both +/// until the shorter one runs out. +/// +/// ## Examples +/// +/// ```gleam +/// > from_list(["a", "b", "c"]) +/// > |> zip(range(20, 30)) +/// > |> to_list +/// [#("a", 20), #("b", 21), #("c", 22)] +/// ``` +/// +pub fn zip(left: Iterator(a), right: Iterator(b)) -> Iterator(#(a, b)) { + do_zip(left.continuation, right.continuation) + |> Iterator +} + +// Result of collecting a single chunk by key +type Chunk(element, key) { + AnotherBy(List(element), key, element, fn() -> Action(element)) + LastBy(List(element)) +} + +fn next_chunk( + continuation: fn() -> Action(element), + f: fn(element) -> key, + previous_key: key, + current_chunk: List(element), +) -> Chunk(element, key) { + case continuation() { + Stop -> LastBy(list.reverse(current_chunk)) + Continue(e, next) -> { + let key = f(e) + case key == previous_key { + True -> next_chunk(next, f, key, [e, ..current_chunk]) + False -> AnotherBy(list.reverse(current_chunk), key, e, next) + } + } + } +} + +fn do_chunk( + continuation: fn() -> Action(element), + f: fn(element) -> key, + previous_key: key, + previous_element: element, +) -> Action(List(element)) { + case next_chunk(continuation, f, previous_key, [previous_element]) { + LastBy(chunk) -> Continue(chunk, stop) + AnotherBy(chunk, key, el, next) -> + Continue(chunk, fn() { do_chunk(next, f, key, el) }) + } +} + +/// Creates an iterator that emits chunks of elements +/// for which `f` returns the same value. +/// +/// ## Examples +/// +/// ```gleam +/// > from_list([1, 2, 2, 3, 4, 4, 6, 7, 7]) +/// > |> chunk(by: fn(n) { n % 2 }) +/// > |> to_list +/// [[1], [2, 2], [3], [4, 4, 6], [7, 7]] +/// ``` +/// +pub fn chunk( + over iterator: Iterator(element), + by f: fn(element) -> key, +) -> Iterator(List(element)) { + fn() { + case iterator.continuation() { + Stop -> Stop + Continue(e, next) -> do_chunk(next, f, f(e), e) + } + } + |> Iterator +} + +// Result of collecting a single sized chunk +type SizedChunk(element) { + Another(List(element), fn() -> Action(element)) + Last(List(element)) + NoMore +} + +fn next_sized_chunk( + continuation: fn() -> Action(element), + left: Int, + current_chunk: List(element), +) -> SizedChunk(element) { + case continuation() { + Stop -> + case current_chunk { + [] -> NoMore + remaining -> Last(list.reverse(remaining)) + } + Continue(e, next) -> { + let chunk = [e, ..current_chunk] + case left > 1 { + False -> Another(list.reverse(chunk), next) + True -> next_sized_chunk(next, left - 1, chunk) + } + } + } +} + +fn do_sized_chunk( + continuation: fn() -> Action(element), + count: Int, +) -> fn() -> Action(List(element)) { + fn() { + case next_sized_chunk(continuation, count, []) { + NoMore -> Stop + Last(chunk) -> Continue(chunk, stop) + Another(chunk, next_element) -> + Continue(chunk, do_sized_chunk(next_element, count)) + } + } +} + +/// Creates an iterator that emits chunks of given size. +/// +/// If the last chunk does not have `count` elements, it is yielded +/// as a partial chunk, with less than `count` elements. +/// +/// For any `count` less than 1 this function behaves as if it was set to 1. +/// +/// ## Examples +/// +/// ```gleam +/// > from_list([1, 2, 3, 4, 5, 6]) +/// > |> sized_chunk(into: 2) +/// > |> to_list +/// [[1, 2], [3, 4], [5, 6]] +/// ``` +/// +/// ```gleam +/// > from_list([1, 2, 3, 4, 5, 6, 7, 8]) +/// > |> sized_chunk(into: 3) +/// > |> to_list +/// [[1, 2, 3], [4, 5, 6], [7, 8]] +/// ``` +/// +pub fn sized_chunk( + over iterator: Iterator(element), + into count: Int, +) -> Iterator(List(element)) { + iterator.continuation + |> do_sized_chunk(count) + |> Iterator +} + +fn do_intersperse( + continuation: fn() -> Action(element), + separator: element, +) -> Action(element) { + case continuation() { + Stop -> Stop + Continue(e, next) -> { + let next_interspersed = fn() { do_intersperse(next, separator) } + Continue(separator, fn() { Continue(e, next_interspersed) }) + } + } +} + +/// Creates an iterator that yields the given `elem` element +/// between elements emitted by the underlying iterator. +/// +/// ## Examples +/// +/// ```gleam +/// > empty() +/// > |> intersperse(with: 0) +/// > |> to_list +/// [] +/// +/// > from_list([1]) +/// > |> intersperse(with: 0) +/// > |> to_list +/// [1] +/// +/// > from_list([1, 2, 3, 4, 5]) +/// > |> intersperse(with: 0) +/// > |> to_list +/// [1, 0, 2, 0, 3, 0, 4, 0, 5] +/// ``` +/// +pub fn intersperse( + over iterator: Iterator(element), + with elem: element, +) -> Iterator(element) { + fn() { + case iterator.continuation() { + Stop -> Stop + Continue(e, next) -> Continue(e, fn() { do_intersperse(next, elem) }) + } + } + |> Iterator +} + +fn do_any( + continuation: fn() -> Action(element), + predicate: fn(element) -> Bool, +) -> Bool { + case continuation() { + Stop -> False + Continue(e, next) -> + case predicate(e) { + True -> True + False -> do_any(next, predicate) + } + } +} + +/// Returns `True` if any element emitted by the iterator satisfies the given predicate, +/// `False` otherwise. +/// +/// This function short-circuits once it finds a satisfying element. +/// +/// An empty iterator results in `False`. +/// +/// ## Examples +/// +/// ```gleam +/// > empty() |> any(fn(n) { n % 2 == 0 }) +/// False +/// ``` +/// +/// ```gleam +/// > from_list([1, 2, 5, 7, 9]) |> any(fn(n) { n % 2 == 0 }) +/// True +/// ``` +/// +/// ```gleam +/// > from_list([1, 3, 5, 7, 9]) |> any(fn(n) { n % 2 == 0 }) +/// False +/// ``` +/// +pub fn any( + in iterator: Iterator(element), + satisfying predicate: fn(element) -> Bool, +) -> Bool { + iterator.continuation + |> do_any(predicate) +} + +fn do_all( + continuation: fn() -> Action(element), + predicate: fn(element) -> Bool, +) -> Bool { + case continuation() { + Stop -> True + Continue(e, next) -> + case predicate(e) { + True -> do_all(next, predicate) + False -> False + } + } +} + +/// Returns `True` if all elements emitted by the iterator satisfy the given predicate, +/// `False` otherwise. +/// +/// This function short-circuits once it finds a non-satisfying element. +/// +/// An empty iterator results in `True`. +/// +/// ## Examples +/// +/// ```gleam +/// > empty() |> all(fn(n) { n % 2 == 0 }) +/// True +/// ``` +/// +/// ```gleam +/// > from_list([2, 4, 6, 8]) |> all(fn(n) { n % 2 == 0 }) +/// True +/// ``` +/// +/// ```gleam +/// > from_list([2, 4, 5, 8]) |> all(fn(n) { n % 2 == 0 }) +/// False +/// ``` +/// +pub fn all( + in iterator: Iterator(element), + satisfying predicate: fn(element) -> Bool, +) -> Bool { + iterator.continuation + |> do_all(predicate) +} + +fn update_group_with(el: element) -> fn(Option(List(element))) -> List(element) { + fn(maybe_group) { + case maybe_group { + Some(group) -> [el, ..group] + None -> [el] + } + } +} + +fn group_updater( + f: fn(element) -> key, +) -> fn(Dict(key, List(element)), element) -> Dict(key, List(element)) { + fn(groups, elem) { + groups + |> dict.update(f(elem), update_group_with(elem)) + } +} + +/// Returns a `Dict(k, List(element))` of elements from the given iterator +/// grouped with the given key function. +/// +/// The order within each group is preserved from the iterator. +/// +/// ## Examples +/// +/// ```gleam +/// > from_list([1, 2, 3, 4, 5, 6]) |> group(by: fn(n) { n % 3 }) +/// dict.from_list([#(0, [3, 6]), #(1, [1, 4]), #(2, [2, 5])]) +/// ``` +/// +pub fn group( + in iterator: Iterator(element), + by key: fn(element) -> key, +) -> Dict(key, List(element)) { + iterator + |> fold(dict.new(), group_updater(key)) + |> dict.map_values(fn(_, group) { list.reverse(group) }) +} + +/// This function acts similar to fold, but does not take an initial state. +/// Instead, it starts from the first yielded element +/// and combines it with each subsequent element in turn using the given function. +/// The function is called as `f(accumulator, current_element)`. +/// +/// Returns `Ok` to indicate a successful run, and `Error` if called on an empty iterator. +/// +/// ## Examples +/// +/// ```gleam +/// > from_list([]) |> reduce(fn(acc, x) { acc + x }) +/// Error(Nil) +/// ``` +/// +/// ```gleam +/// > from_list([1, 2, 3, 4, 5]) |> reduce(fn(acc, x) { acc + x }) +/// Ok(15) +/// ``` +/// +pub fn reduce( + over iterator: Iterator(e), + with f: fn(e, e) -> e, +) -> Result(e, Nil) { + case iterator.continuation() { + Stop -> Error(Nil) + Continue(e, next) -> + do_fold(next, f, e) + |> Ok + } +} + +/// Returns the last element in the given iterator. +/// +/// Returns `Error(Nil)` if the iterator is empty. +/// +/// This function runs in linear time. +/// +/// ## Examples +/// +/// ```gleam +/// > empty() |> last +/// Error(Nil) +/// ``` +/// +/// ```gleam +/// > range(1, 10) |> last +/// Ok(9) +/// ``` +/// +pub fn last(iterator: Iterator(element)) -> Result(element, Nil) { + iterator + |> reduce(fn(_, elem) { elem }) +} + +/// Creates an iterator that yields no elements. +/// +/// ## Examples +/// +/// ```gleam +/// > empty() |> to_list +/// [] +/// ``` +/// +pub fn empty() -> Iterator(element) { + Iterator(stop) +} + +/// Creates an iterator that yields exactly one element provided by calling the given function. +/// +/// ## Examples +/// +/// ```gleam +/// > once(fn() { 1 }) |> to_list +/// [1] +/// ``` +/// +pub fn once(f: fn() -> element) -> Iterator(element) { + fn() { Continue(f(), stop) } + |> Iterator +} + +/// Creates an iterator that yields the given element exactly once. +/// +/// ## Examples +/// +/// ```gleam +/// > single(1) |> to_list +/// [1] +/// ``` +/// +pub fn single(elem: element) -> Iterator(element) { + once(fn() { elem }) +} + +fn do_interleave( + current: fn() -> Action(element), + next: fn() -> Action(element), +) -> Action(element) { + case current() { + Stop -> next() + Continue(e, next_other) -> + Continue(e, fn() { do_interleave(next, next_other) }) + } +} + +/// Creates an iterator that alternates between the two given iterators +/// until both have run out. +/// +/// ## Examples +/// +/// ```gleam +/// > from_list([1, 2, 3, 4]) |> interleave(from_list([11, 12, 13, 14])) |> to_list +/// [1, 11, 2, 12, 3, 13, 4, 14] +/// ``` +/// +/// ```gleam +/// > from_list([1, 2, 3, 4]) |> interleave(from_list([100])) |> to_list +/// [1, 100, 2, 3, 4] +/// ``` +/// +pub fn interleave( + left: Iterator(element), + with right: Iterator(element), +) -> Iterator(element) { + fn() { do_interleave(left.continuation, right.continuation) } + |> Iterator +} + +fn do_fold_until( + continuation: fn() -> Action(e), + f: fn(acc, e) -> list.ContinueOrStop(acc), + accumulator: acc, +) -> acc { + case continuation() { + Stop -> accumulator + Continue(elem, next) -> + case f(accumulator, elem) { + list.Continue(accumulator) -> do_fold_until(next, f, accumulator) + list.Stop(accumulator) -> accumulator + } + } +} + +/// Like `fold`, `fold_until` reduces an iterator of elements into a single value by calling a given +/// function on each element in turn, but uses `list.ContinueOrStop` to determine +/// whether or not to keep iterating. +/// +/// If called on an iterator of infinite length then this function will only ever +/// return if the function returns `list.Stop`. +/// +/// ## Examples +/// +/// ```gleam +/// > import gleam/list +/// > let f = fn(acc, e) { +/// > case e { +/// > _ if e < 4 -> list.Continue(e + acc) +/// > _ -> list.Stop(acc) +/// > } +/// > } +/// > +/// > [1, 2, 3, 4] +/// > |> from_list +/// > |> fold_until(from: acc, with: f) +/// 6 +/// ``` +/// +pub fn fold_until( + over iterator: Iterator(e), + from initial: acc, + with f: fn(acc, e) -> list.ContinueOrStop(acc), +) -> acc { + iterator.continuation + |> do_fold_until(f, initial) +} + +fn do_try_fold( + over continuation: fn() -> Action(a), + with f: fn(acc, a) -> Result(acc, err), + from accumulator: acc, +) -> Result(acc, err) { + case continuation() { + Stop -> Ok(accumulator) + Continue(elem, next) -> { + use accumulator <- result.try(f(accumulator, elem)) + do_try_fold(next, f, accumulator) + } + } +} + +/// A variant of fold that might fail. +/// +/// The folding function should return `Result(accumulator, error)`. +/// If the returned value is `Ok(accumulator)` try_fold will try the next value in the iterator. +/// If the returned value is `Error(error)` try_fold will stop and return that error. +/// +/// ## Examples +/// +/// ```gleam +/// > [1, 2, 3, 4] +/// > |> iterator.from_list() +/// > |> try_fold(0, fn(acc, i) { +/// > case i < 3 { +/// > True -> Ok(acc + i) +/// > False -> Error(Nil) +/// > } +/// > }) +/// Error(Nil) +/// ``` +/// +pub fn try_fold( + over iterator: Iterator(e), + from initial: acc, + with f: fn(acc, e) -> Result(acc, err), +) -> Result(acc, err) { + iterator.continuation + |> do_try_fold(f, initial) +} + +/// Returns the first element yielded by the given iterator, if it exists, +/// or `Error(Nil)` otherwise. +/// +/// ## Examples +/// +/// ```gleam +/// > from_list([1, 2, 3]) |> first +/// Ok(1) +/// ``` +/// +/// ```gleam +/// > empty() |> first +/// Error(Nil) +/// ``` +pub fn first(from iterator: Iterator(e)) -> Result(e, Nil) { + case iterator.continuation() { + Stop -> Error(Nil) + Continue(e, _) -> Ok(e) + } +} + +/// Returns nth element yielded by the given iterator, where `0` means the first element. +/// +/// If there are not enough elements in the iterator, `Error(Nil)` is returned. +/// +/// For any `index` less than `0` this function behaves as if it was set to `0`. +/// +/// ## Examples +/// +/// ```gleam +/// > from_list([1, 2, 3, 4]) |> at(2) +/// Ok(3) +/// ``` +/// +/// ```gleam +/// > from_list([1, 2, 3, 4]) |> at(4) +/// Error(Nil) +/// ``` +/// +/// ```gleam +/// > empty() |> at(0) +/// Error(Nil) +/// ``` +/// +pub fn at(in iterator: Iterator(e), get index: Int) -> Result(e, Nil) { + iterator + |> drop(index) + |> first +} + +fn do_length(over continuation: fn() -> Action(e), with length: Int) -> Int { + case continuation() { + Stop -> length + Continue(_, next) -> do_length(next, length + 1) + } +} + +/// Counts the number of elements in the given iterator. +/// +/// This function has to traverse the entire iterator to count its elements, +/// so it runs in linear time. +/// +/// ## Examples +/// +/// ```gleam +/// > empty() |> length +/// 0 +/// ``` +/// +/// ```gleam +/// > from_list([1, 2, 3, 4]) |> length +/// 4 +/// ``` +/// +pub fn length(over iterator: Iterator(e)) -> Int { + iterator.continuation + |> do_length(0) +} + +/// Traverse an iterator, calling a function on each element. +/// +/// ## Examples +/// +/// ```gleam +/// > empty() |> each(io.println) +/// Nil +/// ``` +/// +/// ```gleam +/// > from_list(["Tom", "Malory", "Louis"]) |> each(io.println) +/// // -> Tom +/// // -> Malory +/// // -> Louis +/// Nil +/// ``` +/// +pub fn each(over iterator: Iterator(a), with f: fn(a) -> b) -> Nil { + iterator + |> map(f) + |> run +} + +/// Add a new element to the start of an iterator. +/// +/// This function is for use with `use` expressions, to replicate the behaviour +/// of the `yield` keyword found in other languages. +/// +/// ## Examples +/// +/// ```gleam +/// > use <- iterator.yield(1) +/// > use <- iterator.yield(2) +/// > use <- iterator.yield(3) +/// > iterator.empty() +/// iterator.from_list([1, 2, 3]) +/// ``` +/// +pub fn yield(element: a, next: fn() -> Iterator(a)) -> Iterator(a) { + Iterator(fn() { Continue(element, next().continuation) }) +} diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/list.gleam b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/list.gleam new file mode 100644 index 0000000..c58ce8c --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/list.gleam @@ -0,0 +1,2134 @@ +//// Lists are an ordered sequence of elements and are one of the most common +//// data types in Gleam. +//// +//// New elements can be added and removed from the front of a list in +//// constant time, while adding and removing from the end requires traversing +//// the copying the whole list, so keep this in mind when designing your +//// programs. +//// +//// There is a dedicated syntax for prefixing to a list: +//// +//// ```gleam +//// let new_list = [1, 2, ..existing_list] +//// ``` +//// +//// And a matching syntax for getting the first elements of a list: +//// +//// ```gleam +//// case list { +//// [first_element, ..rest] -> first_element +//// _ -> "this pattern matches when the list is empty" +//// } +//// ``` +//// + +import gleam/int +import gleam/float +import gleam/order.{type Order} +import gleam/pair +import gleam/dict.{type Dict} + +/// An error value returned by the `strict_zip` function. +/// +pub type LengthMismatch { + LengthMismatch +} + +/// Counts the number of elements in a given list. +/// +/// This function has to traverse the list to determine the number of elements, +/// so it runs in linear time. +/// +/// This function is natively implemented by the virtual machine and is highly +/// optimised. +/// +/// ## Examples +/// +/// ```gleam +/// > length([]) +/// 0 +/// ``` +/// +/// ```gleam +/// > length([1]) +/// 1 +/// ``` +/// +/// ```gleam +/// > length([1, 2]) +/// 2 +/// ``` +/// +pub fn length(of list: List(a)) -> Int { + do_length(list) +} + +@target(erlang) +@external(erlang, "erlang", "length") +fn do_length(a: List(a)) -> Int + +@target(javascript) +fn do_length(list: List(a)) -> Int { + do_length_acc(list, 0) +} + +@target(javascript) +fn do_length_acc(list: List(a), count: Int) -> Int { + case list { + [_, ..list] -> do_length_acc(list, count + 1) + _ -> count + } +} + +/// Creates a new list from a given list containing the same elements but in the +/// opposite order. +/// +/// This function has to traverse the list to create the new reversed list, so +/// it runs in linear time. +/// +/// This function is natively implemented by the virtual machine and is highly +/// optimised. +/// +/// ## Examples +/// +/// ```gleam +/// > reverse([]) +/// [] +/// ``` +/// +/// ```gleam +/// > reverse([1]) +/// [1] +/// ``` +/// +/// ```gleam +/// > reverse([1, 2]) +/// [2, 1] +/// ``` +/// +pub fn reverse(xs: List(a)) -> List(a) { + do_reverse(xs) +} + +@target(erlang) +@external(erlang, "lists", "reverse") +fn do_reverse(a: List(a)) -> List(a) + +@target(javascript) +fn do_reverse(list) { + do_reverse_acc(list, []) +} + +@target(javascript) +fn do_reverse_acc(remaining, accumulator) { + case remaining { + [] -> accumulator + [item, ..rest] -> do_reverse_acc(rest, [item, ..accumulator]) + } +} + +/// Determines whether or not the list is empty. +/// +/// This function runs in constant time. +/// +/// ## Examples +/// +/// ```gleam +/// > is_empty([]) +/// True +/// ``` +/// +/// ```gleam +/// > is_empty([1]) +/// False +/// ``` +/// +/// ```gleam +/// > is_empty([1, 1]) +/// False +/// ``` +/// +pub fn is_empty(list: List(a)) -> Bool { + list == [] +} + +/// Determines whether or not a given element exists within a given list. +/// +/// This function traverses the list to find the element, so it runs in linear +/// time. +/// +/// ## Examples +/// +/// ```gleam +/// > [] |> contains(any: 0) +/// False +/// ``` +/// +/// ```gleam +/// > [0] |> contains(any: 0) +/// True +/// ``` +/// +/// ```gleam +/// > [1] |> contains(any: 0) +/// False +/// ``` +/// +/// ```gleam +/// > [1, 1] |> contains(any: 0) +/// False +/// ``` +/// +/// ```gleam +/// > [1, 0] |> contains(any: 0) +/// True +/// ``` +/// +pub fn contains(list: List(a), any elem: a) -> Bool { + case list { + [] -> False + [first, ..] if first == elem -> True + [_, ..rest] -> contains(rest, elem) + } +} + +/// Gets the first element from the start of the list, if there is one. +/// +/// ## Examples +/// +/// ```gleam +/// > first([]) +/// Error(Nil) +/// ``` +/// +/// ```gleam +/// > first([0]) +/// Ok(0) +/// ``` +/// +/// ```gleam +/// > first([1, 2]) +/// Ok(1) +/// ``` +/// +pub fn first(list: List(a)) -> Result(a, Nil) { + case list { + [] -> Error(Nil) + [x, ..] -> Ok(x) + } +} + +/// Returns the list minus the first element. If the list is empty, `Error(Nil)` is +/// returned. +/// +/// This function runs in constant time and does not make a copy of the list. +/// +/// ## Examples +/// +/// ```gleam +/// > rest([]) +/// Error(Nil) +/// ``` +/// +/// ```gleam +/// > rest([0]) +/// Ok([]) +/// ``` +/// +/// ```gleam +/// > rest([1, 2]) +/// Ok([2]) +/// ``` +/// +pub fn rest(list: List(a)) -> Result(List(a), Nil) { + case list { + [] -> Error(Nil) + [_, ..xs] -> Ok(xs) + } +} + +fn update_group( + f: fn(element) -> key, +) -> fn(Dict(key, List(element)), element) -> Dict(key, List(element)) { + fn(groups, elem) { + case dict.get(groups, f(elem)) { + Ok(existing) -> dict.insert(groups, f(elem), [elem, ..existing]) + Error(_) -> dict.insert(groups, f(elem), [elem]) + } + } +} + +/// Takes a list and groups the values by a key +/// which is built from a key function. +/// +/// Does not preserve the initial value order. +/// +/// ## Examples +/// +/// ```gleam +/// > [Ok(3), Error("Wrong"), Ok(200), Ok(73)] +/// |> group(by: fn(i) { +/// case i { +/// Ok(_) -> "Successful" +/// Error(_) -> "Failed" +/// } +/// }) +/// |> dict.to_list +/// +/// [ +/// #("Failed", [Error("Wrong")]), +/// #("Successful", [Ok(73), Ok(200), Ok(3)]) +/// ] +/// +/// > group([1,2,3,4,5], by: fn(i) { i - i / 3 * 3 }) +/// |> dict.to_list +/// [#(0, [3]), #(1, [4, 1]), #(2, [5, 2])] +/// ``` +/// +pub fn group(list: List(v), by key: fn(v) -> k) -> Dict(k, List(v)) { + fold(list, dict.new(), update_group(key)) +} + +fn do_filter(list: List(a), fun: fn(a) -> Bool, acc: List(a)) -> List(a) { + case list { + [] -> reverse(acc) + [x, ..xs] -> { + let new_acc = case fun(x) { + True -> [x, ..acc] + False -> acc + } + do_filter(xs, fun, new_acc) + } + } +} + +/// Returns a new list containing only the elements from the first list for +/// which the given functions returns `True`. +/// +/// ## Examples +/// +/// ```gleam +/// > filter([2, 4, 6, 1], fn(x) { x > 2 }) +/// [4, 6] +/// ``` +/// +/// ```gleam +/// > filter([2, 4, 6, 1], fn(x) { x > 6 }) +/// [] +/// ``` +/// +pub fn filter(list: List(a), keeping predicate: fn(a) -> Bool) -> List(a) { + do_filter(list, predicate, []) +} + +fn do_filter_map( + list: List(a), + fun: fn(a) -> Result(b, e), + acc: List(b), +) -> List(b) { + case list { + [] -> reverse(acc) + [x, ..xs] -> { + let new_acc = case fun(x) { + Ok(x) -> [x, ..acc] + Error(_) -> acc + } + do_filter_map(xs, fun, new_acc) + } + } +} + +/// Returns a new list containing only the elements from the first list for +/// which the given functions returns `Ok(_)`. +/// +/// ## Examples +/// +/// ```gleam +/// > filter_map([2, 4, 6, 1], Error) +/// [] +/// ``` +/// +/// ```gleam +/// > filter_map([2, 4, 6, 1], fn(x) { Ok(x + 1) }) +/// [3, 5, 7, 2] +/// ``` +/// +pub fn filter_map(list: List(a), with fun: fn(a) -> Result(b, e)) -> List(b) { + do_filter_map(list, fun, []) +} + +fn do_map(list: List(a), fun: fn(a) -> b, acc: List(b)) -> List(b) { + case list { + [] -> reverse(acc) + [x, ..xs] -> do_map(xs, fun, [fun(x), ..acc]) + } +} + +/// Returns a new list containing only the elements of the first list after the +/// function has been applied to each one. +/// +/// ## Examples +/// +/// ```gleam +/// > map([2, 4, 6], fn(x) { x * 2 }) +/// [4, 8, 12] +/// ``` +/// +pub fn map(list: List(a), with fun: fn(a) -> b) -> List(b) { + do_map(list, fun, []) +} + +/// Combines two lists into a single list using the given function. +/// +/// If a list is longer than the other the extra elements are dropped. +/// +/// ## Examples +/// +/// ```gleam +/// > map2([1, 2, 3], [4, 5, 6], fn(x, y) { x + y }) +/// [5, 7, 9] +/// ``` +/// +/// ```gleam +/// > map2([1, 2], ["a", "b", "c"], fn(i, x) { #(i, x) }) +/// [#(1, "a"), #(2, "b")] +/// ``` +/// +pub fn map2(list1: List(a), list2: List(b), with fun: fn(a, b) -> c) -> List(c) { + do_map2(list1, list2, fun, []) +} + +fn do_map2( + list1: List(a), + list2: List(b), + fun: fn(a, b) -> c, + acc: List(c), +) -> List(c) { + case list1, list2 { + [], _ | _, [] -> reverse(acc) + [a, ..as_], [b, ..bs] -> do_map2(as_, bs, fun, [fun(a, b), ..acc]) + } +} + +/// Similar to `map` but also lets you pass around an accumulated value. +/// +/// ## Examples +/// +/// ```gleam +/// > map_fold( +/// over: [1, 2, 3], +/// from: 100, +/// with: fn(memo, i) { #(memo + i, i * 2) } +/// ) +/// #(106, [2, 4, 6]) +/// ``` +/// +pub fn map_fold( + over list: List(a), + from acc: acc, + with fun: fn(acc, a) -> #(acc, b), +) -> #(acc, List(b)) { + fold(over: list, from: #(acc, []), with: fn(acc, item) { + let #(current_acc, items) = acc + let #(next_acc, next_item) = fun(current_acc, item) + #(next_acc, [next_item, ..items]) + }) + |> pair.map_second(reverse) +} + +fn do_index_map( + list: List(a), + fun: fn(a, Int) -> b, + index: Int, + acc: List(b), +) -> List(b) { + case list { + [] -> reverse(acc) + [x, ..xs] -> { + let acc = [fun(x, index), ..acc] + do_index_map(xs, fun, index + 1, acc) + } + } +} + +/// Returns a new list containing only the elements of the first list after the +/// function has been applied to each one and their index. +/// +/// The index starts at 0, so the first element is 0, the second is 1, and so +/// on. +/// +/// ## Examples +/// +/// ```gleam +/// > index_map(["a", "b"], fn(x, i) { #(i, x) }) +/// [#(0, "a"), #(1, "b")] +/// ``` +/// +pub fn index_map(list: List(a), with fun: fn(a, Int) -> b) -> List(b) { + do_index_map(list, fun, 0, []) +} + +fn do_try_map( + list: List(a), + fun: fn(a) -> Result(b, e), + acc: List(b), +) -> Result(List(b), e) { + case list { + [] -> Ok(reverse(acc)) + [x, ..xs] -> + case fun(x) { + Ok(y) -> do_try_map(xs, fun, [y, ..acc]) + Error(error) -> Error(error) + } + } +} + +/// Takes a function that returns a `Result` and applies it to each element in a +/// given list in turn. +/// +/// If the function returns `Ok(new_value)` for all elements in the list then a +/// list of the new values is returned. +/// +/// If the function returns `Error(reason)` for any of the elements then it is +/// returned immediately. None of the elements in the list are processed after +/// one returns an `Error`. +/// +/// ## Examples +/// +/// ```gleam +/// > try_map([1, 2, 3], fn(x) { Ok(x + 2) }) +/// Ok([3, 4, 5]) +/// ``` +/// +/// ```gleam +/// > try_map([1, 2, 3], fn(_) { Error(0) }) +/// Error(0) +/// ``` +/// +/// ```gleam +/// > try_map([[1], [2, 3]], first) +/// Ok([1, 2]) +/// ``` +/// +/// ```gleam +/// > try_map([[1], [], [2]], first) +/// Error(Nil) +/// ``` +/// +pub fn try_map( + over list: List(a), + with fun: fn(a) -> Result(b, e), +) -> Result(List(b), e) { + do_try_map(list, fun, []) +} + +/// Returns a list that is the given list with up to the given number of +/// elements removed from the front of the list. +/// +/// If the element has less than the number of elements an empty list is +/// returned. +/// +/// This function runs in linear time but does not copy the list. +/// +/// ## Examples +/// +/// ```gleam +/// > drop([1, 2, 3, 4], 2) +/// [3, 4] +/// ``` +/// +/// ```gleam +/// > drop([1, 2, 3, 4], 9) +/// [] +/// ``` +/// +pub fn drop(from list: List(a), up_to n: Int) -> List(a) { + case n <= 0 { + True -> list + False -> + case list { + [] -> [] + [_, ..xs] -> drop(xs, n - 1) + } + } +} + +fn do_take(list: List(a), n: Int, acc: List(a)) -> List(a) { + case n <= 0 { + True -> reverse(acc) + False -> + case list { + [] -> reverse(acc) + [x, ..xs] -> do_take(xs, n - 1, [x, ..acc]) + } + } +} + +/// Returns a list containing the first given number of elements from the given +/// list. +/// +/// If the element has less than the number of elements then the full list is +/// returned. +/// +/// This function runs in linear time but does not copy the list. +/// +/// ## Examples +/// +/// ```gleam +/// > take([1, 2, 3, 4], 2) +/// [1, 2] +/// ``` +/// +/// ```gleam +/// > take([1, 2, 3, 4], 9) +/// [1, 2, 3, 4] +/// ``` +/// +pub fn take(from list: List(a), up_to n: Int) -> List(a) { + do_take(list, n, []) +} + +/// Returns a new empty list. +/// +/// ## Examples +/// +/// ```gleam +/// > new() +/// [] +/// ``` +/// +pub fn new() -> List(a) { + [] +} + +/// Joins one list onto the end of another. +/// +/// This function runs in linear time, and it traverses and copies the first +/// list. +/// +/// ## Examples +/// +/// ```gleam +/// > append([1, 2], [3]) +/// [1, 2, 3] +/// ``` +/// +pub fn append(first: List(a), second: List(a)) -> List(a) { + do_append(first, second) +} + +@target(erlang) +@external(erlang, "lists", "append") +fn do_append(a: List(a), b: List(a)) -> List(a) + +@target(javascript) +fn do_append(first: List(a), second: List(a)) -> List(a) { + do_append_acc(reverse(first), second) +} + +@target(javascript) +fn do_append_acc(first: List(a), second: List(a)) -> List(a) { + case first { + [] -> second + [item, ..rest] -> do_append_acc(rest, [item, ..second]) + } +} + +/// Prefixes an item to a list. This can also be done using the dedicated +/// syntax instead +/// +/// ```gleam +/// let new_list = [1, ..existing_list] +/// ``` +/// +pub fn prepend(to list: List(a), this item: a) -> List(a) { + [item, ..list] +} + +// Reverses a list and prepends it to another list +fn reverse_and_prepend(list prefix: List(a), to suffix: List(a)) -> List(a) { + case prefix { + [] -> suffix + [first, ..rest] -> reverse_and_prepend(list: rest, to: [first, ..suffix]) + } +} + +fn do_concat(lists: List(List(a)), acc: List(a)) -> List(a) { + case lists { + [] -> reverse(acc) + [list, ..further_lists] -> + do_concat(further_lists, reverse_and_prepend(list: list, to: acc)) + } +} + +/// Joins a list of lists into a single list. +/// +/// This function traverses all elements twice. +/// +/// ## Examples +/// +/// ```gleam +/// > concat([[1], [2, 3], []]) +/// [1, 2, 3] +/// ``` +/// +pub fn concat(lists: List(List(a))) -> List(a) { + do_concat(lists, []) +} + +/// This is the same as `concat`: it joins a list of lists into a single +/// list. +/// +/// This function traverses all elements twice. +/// +/// ## Examples +/// +/// ```gleam +/// > flatten([[1], [2, 3], []]) +/// [1, 2, 3] +/// ``` +/// +pub fn flatten(lists: List(List(a))) -> List(a) { + do_concat(lists, []) +} + +/// Maps the list with the given function into a list of lists, and then flattens it. +/// +/// ## Examples +/// +/// ```gleam +/// > flat_map([2, 4, 6], fn(x) { [x, x + 1] }) +/// [2, 3, 4, 5, 6, 7] +/// ``` +/// +pub fn flat_map(over list: List(a), with fun: fn(a) -> List(b)) -> List(b) { + map(list, fun) + |> concat +} + +/// Reduces a list of elements into a single value by calling a given function +/// on each element, going from left to right. +/// +/// `fold([1, 2, 3], 0, add)` is the equivalent of +/// `add(add(add(0, 1), 2), 3)`. +/// +/// This function runs in linear time. +/// +pub fn fold( + over list: List(a), + from initial: acc, + with fun: fn(acc, a) -> acc, +) -> acc { + case list { + [] -> initial + [x, ..rest] -> fold(rest, fun(initial, x), fun) + } +} + +/// Reduces a list of elements into a single value by calling a given function +/// on each element, going from right to left. +/// +/// `fold_right([1, 2, 3], 0, add)` is the equivalent of +/// `add(add(add(0, 3), 2), 1)`. +/// +/// This function runs in linear time. +/// +/// Unlike `fold` this function is not tail recursive. Where possible use +/// `fold` instead as it will use less memory. +/// +pub fn fold_right( + over list: List(a), + from initial: acc, + with fun: fn(acc, a) -> acc, +) -> acc { + case list { + [] -> initial + [x, ..rest] -> fun(fold_right(rest, initial, fun), x) + } +} + +fn do_index_fold( + over: List(a), + acc: acc, + with: fn(acc, a, Int) -> acc, + index: Int, +) -> acc { + case over { + [] -> acc + [first, ..rest] -> + do_index_fold(rest, with(acc, first, index), with, index + 1) + } +} + +/// Like fold but the folding function also receives the index of the current element. +/// +/// ## Examples +/// +/// ```gleam +/// ["a", "b", "c"] +/// |> index_fold([], fn(acc, item, index) { ... }) +/// ``` +/// +pub fn index_fold( + over over: List(a), + from initial: acc, + with fun: fn(acc, a, Int) -> acc, +) -> acc { + do_index_fold(over, initial, fun, 0) +} + +/// A variant of fold that might fail. +/// +/// The folding function should return `Result(accumulator, error)`. +/// If the returned value is `Ok(accumulator)` try_fold will try the next value in the list. +/// If the returned value is `Error(error)` try_fold will stop and return that error. +/// +/// ## Examples +/// +/// ```gleam +/// [1, 2, 3, 4] +/// |> try_fold(0, fn(acc, i) { +/// case i < 3 { +/// True -> Ok(acc + i) +/// False -> Error(Nil) +/// } +/// }) +/// ``` +/// +pub fn try_fold( + over collection: List(a), + from accumulator: acc, + with fun: fn(acc, a) -> Result(acc, e), +) -> Result(acc, e) { + case collection { + [] -> Ok(accumulator) + [first, ..rest] -> + case fun(accumulator, first) { + Ok(result) -> try_fold(rest, result, fun) + Error(_) as error -> error + } + } +} + +pub type ContinueOrStop(a) { + Continue(a) + Stop(a) +} + +/// A variant of fold that allows to stop folding earlier. +/// +/// The folding function should return `ContinueOrStop(accumulator)`. +/// If the returned value is `Continue(accumulator)` fold_until will try the next value in the list. +/// If the returned value is `Stop(accumulator)` fold_until will stop and return that accumulator. +/// +/// ## Examples +/// +/// ```gleam +/// [1, 2, 3, 4] +/// |> fold_until(0, fn(acc, i) { +/// case i < 3 { +/// True -> Continue(acc + i) +/// False -> Stop(acc) +/// } +/// }) +/// ``` +/// +pub fn fold_until( + over collection: List(a), + from accumulator: acc, + with fun: fn(acc, a) -> ContinueOrStop(acc), +) -> acc { + case collection { + [] -> accumulator + [first, ..rest] -> + case fun(accumulator, first) { + Continue(next_accumulator) -> fold_until(rest, next_accumulator, fun) + Stop(b) -> b + } + } +} + +/// Finds the first element in a given list for which the given function returns +/// `True`. +/// +/// Returns `Error(Nil)` if no such element is found. +/// +/// ## Examples +/// +/// ```gleam +/// > find([1, 2, 3], fn(x) { x > 2 }) +/// Ok(3) +/// ``` +/// +/// ```gleam +/// > find([1, 2, 3], fn(x) { x > 4 }) +/// Error(Nil) +/// ``` +/// +/// ```gleam +/// > find([], fn(_) { True }) +/// Error(Nil) +/// ``` +/// +pub fn find( + in haystack: List(a), + one_that is_desired: fn(a) -> Bool, +) -> Result(a, Nil) { + case haystack { + [] -> Error(Nil) + [x, ..rest] -> + case is_desired(x) { + True -> Ok(x) + _ -> find(in: rest, one_that: is_desired) + } + } +} + +/// Finds the first element in a given list for which the given function returns +/// `Ok(new_value)`, then returns the wrapped `new_value`. +/// +/// Returns `Error(Nil)` if no such element is found. +/// +/// ## Examples +/// +/// ```gleam +/// > find_map([[], [2], [3]], first) +/// Ok(2) +/// ``` +/// +/// ```gleam +/// > find_map([[], []], first) +/// Error(Nil) +/// ``` +/// +/// ```gleam +/// > find_map([], first) +/// Error(Nil) +/// ``` +/// +pub fn find_map( + in haystack: List(a), + with fun: fn(a) -> Result(b, c), +) -> Result(b, Nil) { + case haystack { + [] -> Error(Nil) + [x, ..rest] -> + case fun(x) { + Ok(x) -> Ok(x) + _ -> find_map(in: rest, with: fun) + } + } +} + +/// Returns `True` if the given function returns `True` for all the elements in +/// the given list. If the function returns `False` for any of the elements it +/// immediately returns `False` without checking the rest of the list. +/// +/// ## Examples +/// +/// ```gleam +/// > all([], fn(x) { x > 3 }) +/// True +/// ``` +/// +/// ```gleam +/// > all([4, 5], fn(x) { x > 3 }) +/// True +/// ``` +/// +/// ```gleam +/// > all([4, 3], fn(x) { x > 3 }) +/// False +/// ``` +/// +pub fn all(in list: List(a), satisfying predicate: fn(a) -> Bool) -> Bool { + case list { + [] -> True + [first, ..rest] -> + case predicate(first) { + True -> all(rest, predicate) + False -> False + } + } +} + +/// Returns `True` if the given function returns `True` for any the elements in +/// the given list. If the function returns `True` for any of the elements it +/// immediately returns `True` without checking the rest of the list. +/// +/// ## Examples +/// +/// ```gleam +/// > any([], fn(x) { x > 3 }) +/// False +/// ``` +/// +/// ```gleam +/// > any([4, 5], fn(x) { x > 3 }) +/// True +/// ``` +/// +/// ```gleam +/// > any([4, 3], fn(x) { x > 4 }) +/// False +/// ``` +/// +/// ```gleam +/// > any([3, 4], fn(x) { x > 3 }) +/// True +/// ``` +/// +pub fn any(in list: List(a), satisfying predicate: fn(a) -> Bool) -> Bool { + case list { + [] -> False + [first, ..rest] -> + case predicate(first) { + True -> True + False -> any(rest, predicate) + } + } +} + +fn do_zip(xs: List(a), ys: List(b), acc: List(#(a, b))) -> List(#(a, b)) { + case xs, ys { + [x, ..xs], [y, ..ys] -> do_zip(xs, ys, [#(x, y), ..acc]) + _, _ -> reverse(acc) + } +} + +/// Takes two lists and returns a single list of 2-element tuples. +/// +/// If one of the lists is longer than the other, the remaining elements from +/// the longer list are not used. +/// +/// ## Examples +/// +/// ```gleam +/// > zip([], []) +/// [] +/// ``` +/// +/// ```gleam +/// > zip([1, 2], [3]) +/// [#(1, 3)] +/// ``` +/// +/// ```gleam +/// > zip([1], [3, 4]) +/// [#(1, 3)] +/// ``` +/// +/// ```gleam +/// > zip([1, 2], [3, 4]) +/// [#(1, 3), #(2, 4)] +/// ``` +/// +pub fn zip(list: List(a), with other: List(b)) -> List(#(a, b)) { + do_zip(list, other, []) +} + +/// Takes two lists and returns a single list of 2-element tuples. +/// +/// If one of the lists is longer than the other, an `Error` is returned. +/// +/// ## Examples +/// +/// ```gleam +/// > strict_zip([], []) +/// Ok([]) +/// ``` +/// +/// ```gleam +/// > strict_zip([1, 2], [3]) +/// Error(LengthMismatch) +/// ``` +/// +/// ```gleam +/// > strict_zip([1], [3, 4]) +/// Error(LengthMismatch) +/// ``` +/// +/// ```gleam +/// > strict_zip([1, 2], [3, 4]) +/// Ok([#(1, 3), #(2, 4)]) +/// ``` +/// +pub fn strict_zip( + list: List(a), + with other: List(b), +) -> Result(List(#(a, b)), LengthMismatch) { + case length(of: list) == length(of: other) { + True -> Ok(zip(list, other)) + False -> Error(LengthMismatch) + } +} + +fn do_unzip(input, xs, ys) { + case input { + [] -> #(reverse(xs), reverse(ys)) + [#(x, y), ..rest] -> do_unzip(rest, [x, ..xs], [y, ..ys]) + } +} + +/// Takes a single list of 2-element tuples and returns two lists. +/// +/// ## Examples +/// +/// ```gleam +/// > unzip([#(1, 2), #(3, 4)]) +/// #([1, 3], [2, 4]) +/// ``` +/// +/// ```gleam +/// > unzip([]) +/// #([], []) +/// ``` +/// +pub fn unzip(input: List(#(a, b))) -> #(List(a), List(b)) { + do_unzip(input, [], []) +} + +fn do_intersperse(list: List(a), separator: a, acc: List(a)) -> List(a) { + case list { + [] -> reverse(acc) + [x, ..rest] -> do_intersperse(rest, separator, [x, separator, ..acc]) + } +} + +/// Inserts a given value between each existing element in a given list. +/// +/// This function runs in linear time and copies the list. +/// +/// ## Examples +/// +/// ```gleam +/// > intersperse([1, 1, 1], 2) +/// [1, 2, 1, 2, 1] +/// ``` +/// +/// ```gleam +/// > intersperse([], 2) +/// [] +/// ``` +/// +pub fn intersperse(list: List(a), with elem: a) -> List(a) { + case list { + [] | [_] -> list + [x, ..rest] -> do_intersperse(rest, elem, [x]) + } +} + +/// Returns the element in the Nth position in the list, with 0 being the first +/// position. +/// +/// `Error(Nil)` is returned if the list is not long enough for the given index +/// or if the index is less than 0. +/// +/// ## Examples +/// +/// ```gleam +/// > at([1, 2, 3], 1) +/// Ok(2) +/// ``` +/// +/// ```gleam +/// > at([1, 2, 3], 5) +/// Error(Nil) +/// ``` +/// +pub fn at(in list: List(a), get index: Int) -> Result(a, Nil) { + case index >= 0 { + True -> + list + |> drop(index) + |> first + False -> Error(Nil) + } +} + +/// Removes any duplicate elements from a given list. +/// +/// This function returns in loglinear time. +/// +/// ## Examples +/// +/// ```gleam +/// > unique([1, 1, 1, 4, 7, 3, 3, 4]) +/// [1, 4, 7, 3] +/// ``` +/// +pub fn unique(list: List(a)) -> List(a) { + case list { + [] -> [] + [x, ..rest] -> [x, ..unique(filter(rest, fn(y) { y != x }))] + } +} + +/// Merge lists `a` and `b` in ascending order +/// but only up to `na` and `nb` number of items respectively. +/// +fn merge_up( + na: Int, + nb: Int, + a: List(a), + b: List(a), + acc: List(a), + compare: fn(a, a) -> Order, +) { + case na, nb, a, b { + 0, 0, _, _ -> acc + _, 0, [ax, ..ar], _ -> merge_up(na - 1, nb, ar, b, [ax, ..acc], compare) + 0, _, _, [bx, ..br] -> merge_up(na, nb - 1, a, br, [bx, ..acc], compare) + _, _, [ax, ..ar], [bx, ..br] -> + case compare(ax, bx) { + order.Gt -> merge_up(na, nb - 1, a, br, [bx, ..acc], compare) + _ -> merge_up(na - 1, nb, ar, b, [ax, ..acc], compare) + } + _, _, _, _ -> acc + } +} + +/// Merge lists `a` and `b` in descending order +/// but only up to `na` and `nb` number of items respectively. +/// +fn merge_down( + na: Int, + nb: Int, + a: List(a), + b: List(a), + acc: List(a), + compare: fn(a, a) -> Order, +) { + case na, nb, a, b { + 0, 0, _, _ -> acc + _, 0, [ax, ..ar], _ -> merge_down(na - 1, nb, ar, b, [ax, ..acc], compare) + 0, _, _, [bx, ..br] -> merge_down(na, nb - 1, a, br, [bx, ..acc], compare) + _, _, [ax, ..ar], [bx, ..br] -> + case compare(bx, ax) { + order.Lt -> merge_down(na - 1, nb, ar, b, [ax, ..acc], compare) + _ -> merge_down(na, nb - 1, a, br, [bx, ..acc], compare) + } + _, _, _, _ -> acc + } +} + +/// Merge sort that alternates merging in ascending and descending order +/// because the merge process also reverses the list. +/// +/// Some copying is avoided by merging only a subset of the lists +/// instead of creating and merging new smaller lists. +/// +fn merge_sort( + l: List(a), + ln: Int, + compare: fn(a, a) -> Order, + down: Bool, +) -> List(a) { + let n = ln / 2 + let a = l + let b = drop(l, n) + case ln < 3 { + True -> + case down { + True -> merge_down(n, ln - n, a, b, [], compare) + False -> merge_up(n, ln - n, a, b, [], compare) + } + False -> + case down { + True -> + merge_down( + n, + ln + - n, + merge_sort(a, n, compare, False), + merge_sort(b, ln - n, compare, False), + [], + compare, + ) + False -> + merge_up( + n, + ln + - n, + merge_sort(a, n, compare, True), + merge_sort(b, ln - n, compare, True), + [], + compare, + ) + } + } +} + +/// Sorts from smallest to largest based upon the ordering specified by a given +/// function. +/// +/// ## Examples +/// +/// ```gleam +/// > import gleam/int +/// > list.sort([4, 3, 6, 5, 4, 1, 2], by: int.compare) +/// [1, 2, 3, 4, 4, 5, 6] +/// ``` +/// +pub fn sort(list: List(a), by compare: fn(a, a) -> Order) -> List(a) { + merge_sort(list, length(list), compare, True) +} + +/// Creates a list of ints ranging from a given start and finish. +/// +/// ## Examples +/// +/// ```gleam +/// > range(0, 0) +/// [0] +/// ``` +/// +/// ```gleam +/// > range(0, 5) +/// [0, 1, 2, 3, 4, 5] +/// ``` +/// +/// ```gleam +/// > range(1, -5) +/// [1, 0, -1, -2, -3, -4, -5] +/// ``` +/// +pub fn range(from start: Int, to stop: Int) -> List(Int) { + tail_recursive_range(start, stop, []) +} + +fn tail_recursive_range(start: Int, stop: Int, acc: List(Int)) -> List(Int) { + case int.compare(start, stop) { + order.Eq -> [stop, ..acc] + order.Gt -> tail_recursive_range(start, stop + 1, [stop, ..acc]) + order.Lt -> tail_recursive_range(start, stop - 1, [stop, ..acc]) + } +} + +fn do_repeat(a: a, times: Int, acc: List(a)) -> List(a) { + case times <= 0 { + True -> acc + False -> do_repeat(a, times - 1, [a, ..acc]) + } +} + +/// Builds a list of a given value a given number of times. +/// +/// ## Examples +/// +/// ```gleam +/// > repeat("a", times: 0) +/// [] +/// ``` +/// +/// ```gleam +/// > repeat("a", times: 5) +/// ["a", "a", "a", "a", "a"] +/// ``` +/// +pub fn repeat(item a: a, times times: Int) -> List(a) { + do_repeat(a, times, []) +} + +fn do_split(list: List(a), n: Int, taken: List(a)) -> #(List(a), List(a)) { + case n <= 0 { + True -> #(reverse(taken), list) + False -> + case list { + [] -> #(reverse(taken), []) + [x, ..xs] -> do_split(xs, n - 1, [x, ..taken]) + } + } +} + +/// Splits a list in two before the given index. +/// +/// If the list is not long enough to have the given index the before list will +/// be the input list, and the after list will be empty. +/// +/// ## Examples +/// +/// ```gleam +/// > split([6, 7, 8, 9], 0) +/// #([], [6, 7, 8, 9]) +/// ``` +/// +/// ```gleam +/// > split([6, 7, 8, 9], 2) +/// #([6, 7], [8, 9]) +/// ``` +/// +/// ```gleam +/// > split([6, 7, 8, 9], 4) +/// #([6, 7, 8, 9], []) +/// ``` +/// +pub fn split(list list: List(a), at index: Int) -> #(List(a), List(a)) { + do_split(list, index, []) +} + +fn do_split_while( + list: List(a), + f: fn(a) -> Bool, + acc: List(a), +) -> #(List(a), List(a)) { + case list { + [] -> #(reverse(acc), []) + [x, ..xs] -> + case f(x) { + False -> #(reverse(acc), list) + _ -> do_split_while(xs, f, [x, ..acc]) + } + } +} + +/// Splits a list in two before the first element that a given function returns +/// `False` for. +/// +/// If the function returns `True` for all elements the first list will be the +/// input list, and the second list will be empty. +/// +/// ## Examples +/// +/// ```gleam +/// > split_while([1, 2, 3, 4, 5], fn(x) { x <= 3 }) +/// #([1, 2, 3], [4, 5]) +/// ``` +/// +/// ```gleam +/// > split_while([1, 2, 3, 4, 5], fn(x) { x <= 5 }) +/// #([1, 2, 3, 4, 5], []) +/// ``` +/// +pub fn split_while( + list list: List(a), + satisfying predicate: fn(a) -> Bool, +) -> #(List(a), List(a)) { + do_split_while(list, predicate, []) +} + +/// Given a list of 2-element tuples, finds the first tuple that has a given +/// key as the first element and returns the second element. +/// +/// If no tuple is found with the given key then `Error(Nil)` is returned. +/// +/// This function may be useful for interacting with Erlang code where lists of +/// tuples are common. +/// +/// ## Examples +/// +/// ```gleam +/// > key_find([#("a", 0), #("b", 1)], "a") +/// Ok(0) +/// ``` +/// +/// ```gleam +/// > key_find([#("a", 0), #("b", 1)], "b") +/// Ok(1) +/// ``` +/// +/// ```gleam +/// > key_find([#("a", 0), #("b", 1)], "c") +/// Error(Nil) +/// ``` +/// +pub fn key_find( + in keyword_list: List(#(k, v)), + find desired_key: k, +) -> Result(v, Nil) { + find_map(keyword_list, fn(keyword) { + let #(key, value) = keyword + case key == desired_key { + True -> Ok(value) + False -> Error(Nil) + } + }) +} + +/// Given a list of 2-element tuples, finds all tuples that have a given +/// key as the first element and returns the second element. +/// +/// This function may be useful for interacting with Erlang code where lists of +/// tuples are common. +/// +/// ## Examples +/// +/// ```gleam +/// > key_filter([#("a", 0), #("b", 1), #("a", 2)], "a") +/// [0, 2] +/// ``` +/// +/// ```gleam +/// > key_filter([#("a", 0), #("b", 1)], "c") +/// [] +/// ``` +/// +pub fn key_filter( + in keyword_list: List(#(k, v)), + find desired_key: k, +) -> List(v) { + filter_map(keyword_list, fn(keyword) { + let #(key, value) = keyword + case key == desired_key { + True -> Ok(value) + False -> Error(Nil) + } + }) +} + +fn do_pop(haystack, predicate, checked) { + case haystack { + [] -> Error(Nil) + [x, ..rest] -> + case predicate(x) { + True -> Ok(#(x, append(reverse(checked), rest))) + False -> do_pop(rest, predicate, [x, ..checked]) + } + } +} + +/// Removes the first element in a given list for which the predicate function returns `True`. +/// +/// Returns `Error(Nil)` if no such element is found. +/// +/// ## Examples +/// +/// ```gleam +/// > pop([1, 2, 3], fn(x) { x > 2 }) +/// Ok(#(3, [1, 2])) +/// ``` +/// +/// ```gleam +/// > pop([1, 2, 3], fn(x) { x > 4 }) +/// Error(Nil) +/// ``` +/// +/// ```gleam +/// > pop([], fn(_) { True }) +/// Error(Nil) +/// ``` +/// +pub fn pop( + in haystack: List(a), + one_that is_desired: fn(a) -> Bool, +) -> Result(#(a, List(a)), Nil) { + do_pop(haystack, is_desired, []) +} + +fn do_pop_map(haystack, mapper, checked) { + case haystack { + [] -> Error(Nil) + [x, ..rest] -> + case mapper(x) { + Ok(y) -> Ok(#(y, append(reverse(checked), rest))) + Error(_) -> do_pop_map(rest, mapper, [x, ..checked]) + } + } +} + +/// Removes the first element in a given list for which the given function returns +/// `Ok(new_value)`, then returns the wrapped `new_value` as well as list with the value removed. +/// +/// Returns `Error(Nil)` if no such element is found. +/// +/// ## Examples +/// +/// ```gleam +/// > pop_map([[], [2], [3]], first) +/// Ok(#(2, [[], [3]])) +/// ``` +/// +/// ```gleam +/// > pop_map([[], []], first) +/// Error(Nil) +/// ``` +/// +/// ```gleam +/// > pop_map([], first) +/// Error(Nil) +/// ``` +/// +pub fn pop_map( + in haystack: List(a), + one_that is_desired: fn(a) -> Result(b, c), +) -> Result(#(b, List(a)), Nil) { + do_pop_map(haystack, is_desired, []) +} + +/// Given a list of 2-element tuples, finds the first tuple that has a given +/// key as the first element. This function will return the second element +/// of the found tuple and list with tuple removed. +/// +/// If no tuple is found with the given key then `Error(Nil)` is returned. +/// +/// ## Examples +/// +/// ```gleam +/// > key_pop([#("a", 0), #("b", 1)], "a") +/// Ok(#(0, [#("b", 1)])) +/// ``` +/// +/// ```gleam +/// > key_pop([#("a", 0), #("b", 1)], "b") +/// Ok(#(1, [#("a", 0)])) +/// ``` +/// +/// ```gleam +/// > key_pop([#("a", 0), #("b", 1)], "c") +/// Error(Nil) +/// ``` +/// +pub fn key_pop( + haystack: List(#(k, v)), + key: k, +) -> Result(#(v, List(#(k, v))), Nil) { + pop_map(haystack, fn(entry) { + let #(k, v) = entry + case k { + k if k == key -> Ok(v) + _ -> Error(Nil) + } + }) +} + +/// Given a list of 2-element tuples, inserts a key and value into the list. +/// +/// If there was already a tuple with the key then it is replaced, otherwise it +/// is added to the end of the list. +/// +/// ## Examples +/// +/// ```gleam +/// > key_set([#(5, 0), #(4, 1)], 4, 100) +/// [#(5, 0), #(4, 100)] +/// ``` +/// +/// ```gleam +/// > key_set([#(5, 0), #(4, 1)], 1, 100) +/// [#(5, 0), #(4, 1), #(1, 100)] +/// ``` +/// +pub fn key_set(list: List(#(a, b)), key: a, value: b) -> List(#(a, b)) { + case list { + [] -> [#(key, value)] + [#(k, _), ..rest] if k == key -> [#(key, value), ..rest] + [first, ..rest] -> [first, ..key_set(rest, key, value)] + } +} + +/// Calls a function for each element in a list, discarding the return value. +/// +/// Useful for calling a side effect for every item of a list. +/// +/// ```gleam +/// > list.each([1, 2, 3], io.println) +/// Nil +/// ``` +/// +pub fn each(list: List(a), f: fn(a) -> b) -> Nil { + case list { + [] -> Nil + [x, ..xs] -> { + f(x) + each(xs, f) + } + } +} + +/// Calls a `Result` returning function for each element in a list, discarding +/// the return value. If the function returns `Error` then the iteration is +/// stopped and the error is returned. +/// +/// Useful for calling a side effect for every item of a list. +/// +/// ## Examples +/// +/// ```gleam +/// > try_each( +/// > over: [1, 2, 3], +/// > with: function_that_might_fail, +/// > ) +/// Ok(Nil) +/// ``` +/// +pub fn try_each( + over list: List(a), + with fun: fn(a) -> Result(b, e), +) -> Result(Nil, e) { + case list { + [] -> Ok(Nil) + [x, ..xs] -> + case fun(x) { + Ok(_) -> try_each(over: xs, with: fun) + Error(e) -> Error(e) + } + } +} + +fn do_partition(list, categorise, trues, falses) { + case list { + [] -> #(reverse(trues), reverse(falses)) + [x, ..xs] -> + case categorise(x) { + True -> do_partition(xs, categorise, [x, ..trues], falses) + False -> do_partition(xs, categorise, trues, [x, ..falses]) + } + } +} + +/// Partitions a list into a tuple/pair of lists +/// by a given categorisation function. +/// +/// ## Examples +/// +/// ```gleam +/// > [1, 2, 3, 4, 5] |> list.partition(int.is_odd) +/// #([1, 3, 5], [2, 4]) +/// ``` +/// +pub fn partition( + list: List(a), + with categorise: fn(a) -> Bool, +) -> #(List(a), List(a)) { + do_partition(list, categorise, [], []) +} + +/// Returns all the permutations of a list. +/// +/// ## Examples +/// +/// ```gleam +/// > permutations([1, 2]) +/// [[1, 2], [2, 1]] +/// ``` +/// +pub fn permutations(l: List(a)) -> List(List(a)) { + case l { + [] -> [[]] + _ -> + l + |> index_map(fn(i, i_idx) { + l + |> index_fold([], fn(acc, j, j_idx) { + case i_idx == j_idx { + True -> acc + False -> [j, ..acc] + } + }) + |> reverse + |> permutations + |> map(fn(permutation) { [i, ..permutation] }) + }) + |> concat + } +} + +fn do_window(acc: List(List(a)), l: List(a), n: Int) -> List(List(a)) { + let window = take(l, n) + + case length(window) == n { + True -> do_window([window, ..acc], drop(l, 1), n) + False -> acc + } +} + +/// Returns a list of sliding windows. +/// +/// ## Examples +/// +/// ```gleam +/// > window([1,2,3,4,5], 3) +/// [[1, 2, 3], [2, 3, 4], [3, 4, 5]] +/// ``` +/// +/// ```gleam +/// > window([1, 2], 4) +/// [] +/// ``` +/// +pub fn window(l: List(a), by n: Int) -> List(List(a)) { + do_window([], l, n) + |> reverse +} + +/// Returns a list of tuples containing two contiguous elements. +/// +/// ## Examples +/// +/// ```gleam +/// > window_by_2([1,2,3,4]) +/// [#(1, 2), #(2, 3), #(3, 4)] +/// ``` +/// +/// ```gleam +/// > window_by_2([1]) +/// [] +/// ``` +/// +pub fn window_by_2(l: List(a)) -> List(#(a, a)) { + zip(l, drop(l, 1)) +} + +/// Drops the first elements in a given list for which the predicate function returns `True`. +/// +/// ## Examples +/// +/// ```gleam +/// > drop_while([1, 2, 3, 4], fn (x) { x < 3 }) +/// [3, 4] +/// ``` +/// +pub fn drop_while( + in list: List(a), + satisfying predicate: fn(a) -> Bool, +) -> List(a) { + case list { + [] -> [] + [x, ..xs] -> + case predicate(x) { + True -> drop_while(xs, predicate) + False -> [x, ..xs] + } + } +} + +fn do_take_while( + list: List(a), + predicate: fn(a) -> Bool, + acc: List(a), +) -> List(a) { + case list { + [] -> reverse(acc) + [first, ..rest] -> + case predicate(first) { + True -> do_take_while(rest, predicate, [first, ..acc]) + False -> reverse(acc) + } + } +} + +/// Takes the first elements in a given list for which the predicate function returns `True`. +/// +/// ## Examples +/// +/// ```gleam +/// > take_while([1, 2, 3, 2, 4], fn (x) { x < 3 }) +/// [1, 2] +/// ``` +/// +pub fn take_while( + in list: List(a), + satisfying predicate: fn(a) -> Bool, +) -> List(a) { + do_take_while(list, predicate, []) +} + +fn do_chunk( + list: List(a), + f: fn(a) -> key, + previous_key: key, + current_chunk: List(a), + acc: List(List(a)), +) -> List(List(a)) { + case list { + [first, ..rest] -> { + let key = f(first) + case key == previous_key { + False -> { + let new_acc = [reverse(current_chunk), ..acc] + do_chunk(rest, f, key, [first], new_acc) + } + _true -> do_chunk(rest, f, key, [first, ..current_chunk], acc) + } + } + _empty -> reverse([reverse(current_chunk), ..acc]) + } +} + +/// Returns a list of chunks in which +/// the return value of calling `f` on each element is the same. +/// +/// ## Examples +/// +/// ```gleam +/// > [1, 2, 2, 3, 4, 4, 6, 7, 7] |> chunk(by: fn(n) { n % 2 }) +/// [[1], [2, 2], [3], [4, 4, 6], [7, 7]] +/// ``` +/// +pub fn chunk(in list: List(a), by f: fn(a) -> key) -> List(List(a)) { + case list { + [] -> [] + [first, ..rest] -> do_chunk(rest, f, f(first), [first], []) + } +} + +fn do_sized_chunk( + list: List(a), + count: Int, + left: Int, + current_chunk: List(a), + acc: List(List(a)), +) -> List(List(a)) { + case list { + [] -> + case current_chunk { + [] -> reverse(acc) + remaining -> reverse([reverse(remaining), ..acc]) + } + [first, ..rest] -> { + let chunk = [first, ..current_chunk] + case left > 1 { + False -> do_sized_chunk(rest, count, count, [], [reverse(chunk), ..acc]) + True -> do_sized_chunk(rest, count, left - 1, chunk, acc) + } + } + } +} + +/// Returns a list of chunks containing `count` elements each. +/// +/// If the last chunk does not have `count` elements, it is instead +/// a partial chunk, with less than `count` elements. +/// +/// For any `count` less than 1 this function behaves as if it was set to 1. +/// +/// ## Examples +/// +/// ```gleam +/// > [1, 2, 3, 4, 5, 6] |> sized_chunk(into: 2) +/// [[1, 2], [3, 4], [5, 6]] +/// ``` +/// +/// ```gleam +/// > [1, 2, 3, 4, 5, 6, 7, 8] |> sized_chunk(into: 3) +/// [[1, 2, 3], [4, 5, 6], [7, 8]] +/// ``` +/// +pub fn sized_chunk(in list: List(a), into count: Int) -> List(List(a)) { + do_sized_chunk(list, count, count, [], []) +} + +/// This function acts similar to fold, but does not take an initial state. +/// Instead, it starts from the first element in the list +/// and combines it with each subsequent element in turn using the given +/// function. The function is called as `fun(accumulator, current_element)`. +/// +/// Returns `Ok` to indicate a successful run, and `Error` if called on an +/// empty list. +/// +/// ## Examples +/// +/// ```gleam +/// > [] |> reduce(fn(acc, x) { acc + x }) +/// Error(Nil) +/// ``` +/// +/// ```gleam +/// > [1, 2, 3, 4, 5] |> reduce(fn(acc, x) { acc + x }) +/// Ok(15) +/// ``` +/// +pub fn reduce(over list: List(a), with fun: fn(a, a) -> a) -> Result(a, Nil) { + case list { + [] -> Error(Nil) + [first, ..rest] -> Ok(fold(rest, first, fun)) + } +} + +fn do_scan( + list: List(a), + accumulator: acc, + accumulated: List(acc), + fun: fn(acc, a) -> acc, +) -> List(acc) { + case list { + [] -> reverse(accumulated) + [x, ..xs] -> { + let next = fun(accumulator, x) + do_scan(xs, next, [next, ..accumulated], fun) + } + } +} + +/// Similar to `fold`, but yields the state of the accumulator at each stage. +/// +/// ## Examples +/// +/// ```gleam +/// > scan(over: [1, 2, 3], from: 100, with: fn(acc, i) { acc + i }) +/// [101, 103, 106] +/// ``` +/// +pub fn scan( + over list: List(a), + from initial: acc, + with fun: fn(acc, a) -> acc, +) -> List(acc) { + do_scan(list, initial, [], fun) +} + +/// Returns the last element in the given list. +/// +/// Returns `Error(Nil)` if the list is empty. +/// +/// This function runs in linear time. +/// For a collection oriented around performant access at either end, +/// see `gleam/queue.Queue`. +/// +/// ## Examples +/// +/// ```gleam +/// > last([]) +/// Error(Nil) +/// ``` +/// +/// ```gleam +/// > last([1, 2, 3, 4, 5]) +/// Ok(5) +/// ``` +/// +pub fn last(list: List(a)) -> Result(a, Nil) { + list + |> reduce(fn(_, elem) { elem }) +} + +/// Return unique combinations of elements in the list. +/// +/// ## Examples +/// +/// ```gleam +/// > combinations([1, 2, 3], 2) +/// [[1, 2], [1, 3], [2, 3]] +/// ``` +/// +/// ```gleam +/// > combinations([1, 2, 3, 4], 3) +/// [[1, 2, 3], [1, 2, 4], [1, 3, 4], [2, 3, 4]] +/// ``` +/// +pub fn combinations(items: List(a), by n: Int) -> List(List(a)) { + case n { + 0 -> [[]] + _ -> + case items { + [] -> [] + [x, ..xs] -> { + let first_combinations = + map(combinations(xs, n - 1), with: fn(com) { [x, ..com] }) + |> reverse + fold(first_combinations, combinations(xs, n), fn(acc, c) { + [c, ..acc] + }) + } + } + } +} + +fn do_combination_pairs(items: List(a)) -> List(List(#(a, a))) { + case items { + [] -> [] + [x, ..xs] -> { + let first_combinations = map(xs, with: fn(other) { #(x, other) }) + [first_combinations, ..do_combination_pairs(xs)] + } + } +} + +/// Return unique pair combinations of elements in the list +/// +/// ## Examples +/// +/// ```gleam +/// > combination_pairs([1, 2, 3]) +/// [#(1, 2), #(1, 3), #(2, 3)] +/// ``` +/// +pub fn combination_pairs(items: List(a)) -> List(#(a, a)) { + do_combination_pairs(items) + |> concat +} + +/// Make a list alternating the elements from the given lists +/// +/// ## Examples +/// +/// ```gleam +/// > list.interleave([[1, 2], [101, 102], [201, 202]]) +/// [1, 101, 201, 2, 102, 202] +/// ``` +/// +pub fn interleave(list: List(List(a))) -> List(a) { + transpose(list) + |> concat +} + +/// Transpose rows and columns of the list of lists. +/// +/// Notice: This function is not tail recursive, +/// and thus may exceed stack size if called, +/// with large lists (on target JavaScript). +/// +/// ## Examples +/// +/// ```gleam +/// > transpose([[1, 2, 3], [101, 102, 103]]) +/// [[1, 101], [2, 102], [3, 103]] +/// ``` +/// +pub fn transpose(list_of_list: List(List(a))) -> List(List(a)) { + let take_first = fn(list) { + case list { + [] -> [] + [f] -> [f] + [f, ..] -> [f] + } + } + + case list_of_list { + [] -> [] + [[], ..xss] -> transpose(xss) + rows -> { + let firsts = + rows + |> map(take_first) + |> concat + let rest = transpose(map(rows, drop(_, 1))) + [firsts, ..rest] + } + } +} + +fn do_shuffle_pair_unwrap(list: List(#(Float, a)), acc: List(a)) -> List(a) { + case list { + [] -> acc + [elem_pair, ..enumerable] -> + do_shuffle_pair_unwrap(enumerable, [elem_pair.1, ..acc]) + } +} + +fn do_shuffle_by_pair_indexes( + list_of_pairs: List(#(Float, a)), +) -> List(#(Float, a)) { + sort(list_of_pairs, fn(a_pair: #(Float, a), b_pair: #(Float, a)) -> Order { + float.compare(a_pair.0, b_pair.0) + }) +} + +/// Takes a list, randomly sorts all items and returns the shuffled list. +/// +/// This function uses `float.random` to decide the order of the elements. +/// +/// ## Example +/// +/// ```gleam +/// > range(1, 10) +/// > |> shuffle() +/// [1, 6, 9, 10, 3, 8, 4, 2, 7, 5] +/// ``` +/// +pub fn shuffle(list: List(a)) -> List(a) { + list + |> fold(from: [], with: fn(acc, a) { [#(float.random(), a), ..acc] }) + |> do_shuffle_by_pair_indexes() + |> do_shuffle_pair_unwrap([]) +} diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/map.gleam b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/map.gleam new file mode 100644 index 0000000..b3d0e0f --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/map.gleam @@ -0,0 +1,107 @@ +import gleam/option.{type Option} +import gleam/dict + +@deprecated("Please use the `gleam/dict` module instead") +pub type Map(key, value) = + dict.Dict(key, value) + +@deprecated("Please use the `gleam/dict` module instead") +pub fn size(map) -> Int { + dict.size(map) +} + +@deprecated("Please use the `gleam/dict` module instead") +pub fn to_list(map) -> List(#(key, value)) { + dict.to_list(map) +} + +@deprecated("Please use the `gleam/dict` module instead") +pub fn from_list(list: List(#(k, v))) { + dict.from_list(list) +} + +@deprecated("Please use the `gleam/dict` module instead") +pub fn has_key(map, key: k) -> Bool { + dict.has_key(map, key) +} + +@deprecated("Please use the `gleam/dict` module instead") +pub fn new() { + dict.new() +} + +@deprecated("Please use the `gleam/dict` module instead") +pub fn get(from, get: key) -> Result(value, Nil) { + dict.get(from, get) +} + +@deprecated("Please use the `gleam/dict` module instead") +pub fn insert(into map, for key: k, insert value: v) { + dict.insert(map, key, value) +} + +@deprecated("Please use the `gleam/dict` module instead") +pub fn map_values(in map, with fun: fn(k, v) -> w) { + dict.map_values(map, fun) +} + +@deprecated("Please use the `gleam/dict` module instead") +pub fn keys(map) -> List(keys) { + dict.keys(map) +} + +@target(javascript) +fn reverse_and_concat(remaining, accumulator) { + case remaining { + [] -> accumulator + [item, ..rest] -> reverse_and_concat(rest, [item, ..accumulator]) + } +} + +@target(javascript) +fn do_keys_acc(list: List(#(k, v)), acc: List(k)) -> List(k) { + case list { + [] -> reverse_and_concat(acc, []) + [x, ..xs] -> do_keys_acc(xs, [x.0, ..acc]) + } +} + +@deprecated("Please use the `gleam/dict` module instead") +pub fn values(map) -> List(values) { + dict.values(map) +} + +@deprecated("Please use the `gleam/dict` module instead") +pub fn filter(in map, keeping predicate: fn(k, v) -> Bool) { + dict.filter(map, predicate) +} + +@deprecated("Please use the `gleam/dict` module instead") +pub fn take(from map, keeping desired_keys: List(k)) { + dict.take(map, desired_keys) +} + +@deprecated("Please use the `gleam/dict` module instead") +pub fn merge(into map, from new_entries) { + dict.merge(map, new_entries) +} + +@deprecated("Please use the `gleam/dict` module instead") +pub fn delete(from map, delete key: k) { + dict.delete(map, key) +} + +@deprecated("Please use the `gleam/dict` module instead") +pub fn drop(from map, drop disallowed_keys: List(k)) { + dict.drop(map, disallowed_keys) +} + +@deprecated("Please use the `gleam/dict` module instead") +pub fn update(in map, update key: k, with fun: fn(Option(v)) -> v) { + dict.update(map, key, fun) +} + +@deprecated("Please use the `gleam/dict` module instead") +pub fn fold(over map, from initial: acc, with fun: fn(acc, k, v) -> acc) -> acc { + dict.fold(map, initial, fun) +} diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/option.gleam b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/option.gleam new file mode 100644 index 0000000..6015c0f --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/option.gleam @@ -0,0 +1,346 @@ +/// `Option` represents a value that may be present or not. `Some` means the value is +/// present, `None` means the value is not. +/// +/// This is Gleam's alternative to having a value that could be Null, as is +/// possible in some other languages. +/// +pub type Option(a) { + Some(a) + None +} + +fn do_all(list: List(Option(a)), acc: List(a)) -> Option(List(a)) { + case list { + [] -> Some(acc) + [x, ..rest] -> { + let accumulate = fn(acc, item) { + case acc, item { + Some(values), Some(value) -> Some([value, ..values]) + _, _ -> None + } + } + accumulate(do_all(rest, acc), x) + } + } +} + +/// Combines a list of `Option`s into a single `Option`. +/// If all elements in the list are `Some` then returns a `Some` holding the list of values. +/// If any element is `None` then returns`None`. +/// +/// ## Examples +/// +/// ```gleam +/// > all([Some(1), Some(2)]) +/// Some([1, 2]) +/// ``` +/// +/// ```gleam +/// > all([Some(1), None]) +/// None +/// ``` +/// +pub fn all(list: List(Option(a))) -> Option(List(a)) { + do_all(list, []) +} + +/// Checks whether the `Option` is a `Some` value. +/// +/// ## Examples +/// +/// ```gleam +/// > is_some(Some(1)) +/// True +/// ``` +/// +/// ```gleam +/// > is_some(None) +/// False +/// ``` +/// +pub fn is_some(option: Option(a)) -> Bool { + option != None +} + +/// Checks whether the `Option` is a `None` value. +/// +/// ## Examples +/// +/// ```gleam +/// > is_none(Some(1)) +/// False +/// ``` +/// +/// ```gleam +/// > is_none(None) +/// True +/// ``` +/// +pub fn is_none(option: Option(a)) -> Bool { + option == None +} + +/// Converts an `Option` type to a `Result` type. +/// +/// ## Examples +/// +/// ```gleam +/// > to_result(Some(1), "some_error") +/// Ok(1) +/// ``` +/// +/// ```gleam +/// > to_result(None, "some_error") +/// Error("some_error") +/// ``` +/// +pub fn to_result(option: Option(a), e) -> Result(a, e) { + case option { + Some(a) -> Ok(a) + _ -> Error(e) + } +} + +/// Converts a `Result` type to an `Option` type. +/// +/// ## Examples +/// +/// ```gleam +/// > from_result(Ok(1)) +/// Some(1) +/// ``` +/// +/// ```gleam +/// > from_result(Error("some_error")) +/// None +/// ``` +/// +pub fn from_result(result: Result(a, e)) -> Option(a) { + case result { + Ok(a) -> Some(a) + _ -> None + } +} + +/// Extracts the value from an `Option`, returning a default value if there is none. +/// +/// ## Examples +/// +/// ```gleam +/// > unwrap(Some(1), 0) +/// 1 +/// ``` +/// +/// ```gleam +/// > unwrap(None, 0) +/// 0 +/// ``` +/// +pub fn unwrap(option: Option(a), or default: a) -> a { + case option { + Some(x) -> x + None -> default + } +} + +/// Extracts the value from an `Option`, evaluating the default function if the option is `None`. +/// +/// ## Examples +/// +/// ```gleam +/// > lazy_unwrap(Some(1), fn() { 0 }) +/// 1 +/// ``` +/// +/// ```gleam +/// > lazy_unwrap(None, fn() { 0 }) +/// 0 +/// ``` +/// +pub fn lazy_unwrap(option: Option(a), or default: fn() -> a) -> a { + case option { + Some(x) -> x + None -> default() + } +} + +/// Updates a value held within the `Some` of an `Option` by calling a given function +/// on it. +/// +/// If the `Option` is a `None` rather than `Some`, the function is not called and the +/// `Option` stays the same. +/// +/// ## Examples +/// +/// ```gleam +/// > map(over: Some(1), with: fn(x) { x + 1 }) +/// Some(2) +/// ``` +/// +/// ```gleam +/// > map(over: None, with: fn(x) { x + 1 }) +/// None +/// ``` +/// +pub fn map(over option: Option(a), with fun: fn(a) -> b) -> Option(b) { + case option { + Some(x) -> Some(fun(x)) + None -> None + } +} + +/// Merges a nested `Option` into a single layer. +/// +/// ## Examples +/// +/// ```gleam +/// > flatten(Some(Some(1))) +/// Some(1) +/// ``` +/// +/// ```gleam +/// > flatten(Some(None)) +/// None +/// ``` +/// +/// ```gleam +/// > flatten(None) +/// None +/// ``` +/// +pub fn flatten(option: Option(Option(a))) -> Option(a) { + case option { + Some(x) -> x + None -> None + } +} + +/// Updates a value held within the `Some` of an `Option` by calling a given function +/// on it, where the given function also returns an `Option`. The two options are +/// then merged together into one `Option`. +/// +/// If the `Option` is a `None` rather than `Some` the function is not called and the +/// option stays the same. +/// +/// This function is the equivalent of calling `map` followed by `flatten`, and +/// it is useful for chaining together multiple functions that return `Option`. +/// +/// ## Examples +/// +/// ```gleam +/// > then(Some(1), fn(x) { Some(x + 1) }) +/// Some(2) +/// ``` +/// +/// ```gleam +/// > then(Some(1), fn(x) { Some(#("a", x)) }) +/// Some(#("a", 1)) +/// ``` +/// +/// ```gleam +/// > then(Some(1), fn(_) { None }) +/// None +/// ``` +/// +/// ```gleam +/// > then(None, fn(x) { Some(x + 1) }) +/// None +/// ``` +/// +pub fn then(option: Option(a), apply fun: fn(a) -> Option(b)) -> Option(b) { + case option { + Some(x) -> fun(x) + None -> None + } +} + +/// Returns the first value if it is `Some`, otherwise returns the second value. +/// +/// ## Examples +/// +/// ```gleam +/// > or(Some(1), Some(2)) +/// Some(1) +/// ``` +/// +/// ```gleam +/// > or(Some(1), None) +/// Some(1) +/// ``` +/// +/// ```gleam +/// > or(None, Some(2)) +/// Some(2) +/// ``` +/// +/// ```gleam +/// > or(None, None) +/// None +/// ``` +/// +pub fn or(first: Option(a), second: Option(a)) -> Option(a) { + case first { + Some(_) -> first + None -> second + } +} + +/// Returns the first value if it is `Some`, otherwise evaluates the given function for a fallback value. +/// +/// ## Examples +/// +/// ```gleam +/// > lazy_or(Some(1), fn() { Some(2) }) +/// Some(1) +/// ``` +/// +/// ```gleam +/// > lazy_or(Some(1), fn() { None }) +/// Some(1) +/// ``` +/// +/// ```gleam +/// > lazy_or(None, fn() { Some(2) }) +/// Some(2) +/// ``` +/// +/// ```gleam +/// > lazy_or(None, fn() { None }) +/// None +/// ``` +/// +pub fn lazy_or(first: Option(a), second: fn() -> Option(a)) -> Option(a) { + case first { + Some(_) -> first + None -> second() + } +} + +fn do_values(list: List(Option(a)), acc: List(a)) -> List(a) { + case list { + [] -> acc + [x, ..xs] -> { + let accumulate = fn(acc, item) { + case item { + Some(value) -> [value, ..acc] + None -> acc + } + } + accumulate(do_values(xs, acc), x) + } + } +} + +/// Given a list of `Option`s, +/// returns only the values inside `Some`. +/// +/// ## Examples +/// +/// ```gleam +/// > values([Some(1), None, Some(3)]) +/// [1, 3] +/// ``` +/// +pub fn values(options: List(Option(a))) -> List(a) { + do_values(options, []) +} diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/order.gleam b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/order.gleam new file mode 100644 index 0000000..12ce011 --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/order.gleam @@ -0,0 +1,133 @@ +/// Represents the result of a single comparison to determine the precise +/// ordering of two values. +/// +pub type Order { + /// Less-than + Lt + + /// Equal + Eq + + /// Greater than + Gt +} + +/// Inverts an order, so less-than becomes greater-than and greater-than +/// becomes less-than. +/// +/// ## Examples +/// +/// ```gleam +/// > negate(Lt) +/// Gt +/// ``` +/// +/// ```gleam +/// > negate(Eq) +/// Eq +/// ``` +/// +/// ```gleam +/// > negate(Lt) +/// Gt +/// ``` +/// +pub fn negate(order: Order) -> Order { + case order { + Lt -> Gt + Eq -> Eq + Gt -> Lt + } +} + +/// Produces a numeric representation of the order. +/// +/// ## Examples +/// +/// ```gleam +/// > to_int(Lt) +/// -1 +/// ``` +/// +/// ```gleam +/// > to_int(Eq) +/// 0 +/// ``` +/// +/// ```gleam +/// > to_int(Gt) +/// 1 +/// ``` +/// +pub fn to_int(order: Order) -> Int { + case order { + Lt -> -1 + Eq -> 0 + Gt -> 1 + } +} + +/// Compares two `Order` values to one another, producing a new `Order`. +/// +/// ## Examples +/// +/// ```gleam +/// > compare(Eq, with: Lt) +/// Gt +/// ``` +/// +pub fn compare(a: Order, with b: Order) -> Order { + case a, b { + x, y if x == y -> Eq + Lt, _ | Eq, Gt -> Lt + _, _ -> Gt + } +} + +/// Returns the largest of two orders given that `Gt > Eq > Lt`. +/// +/// ## Examples +/// +/// ```gleam +/// > max(Eq, Lt) +/// Eq +/// ``` +/// +pub fn max(a: Order, b: Order) -> Order { + case a, b { + Gt, _ -> Gt + Eq, Lt -> Eq + _, _ -> b + } +} + +/// Returns the smallest of two orders given that `Gt > Eq > Lt`. +/// +/// ## Examples +/// +/// ```gleam +/// > min(Eq, Lt) +/// Lt +/// ``` +/// +pub fn min(a: Order, b: Order) -> Order { + case a, b { + Lt, _ -> Lt + Eq, Gt -> Eq + _, _ -> b + } +} + +/// Inverts an ordering function, so less-than becomes greater-than and greater-than +/// becomes less-than. +/// +/// ## Examples +/// +/// ```gleam +/// > list.sort([1, 5, 4], by: reverse(int.compare)) +/// [5, 4, 1] +/// ``` +/// +pub fn reverse(orderer: fn(a, a) -> Order) -> fn(a, a) -> Order { + fn(a, b) { orderer(b, a) } +} diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/pair.gleam b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/pair.gleam new file mode 100644 index 0000000..894e6a8 --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/pair.gleam @@ -0,0 +1,85 @@ +/// Returns the first element in a pair. +/// +/// ## Examples +/// +/// ```gleam +/// > first(#(1, 2)) +/// 1 +/// ``` +/// +pub fn first(pair: #(a, b)) -> a { + let #(a, _) = pair + a +} + +/// Returns the second element in a pair. +/// +/// ## Examples +/// +/// ```gleam +/// > second(#(1, 2)) +/// 2 +/// ``` +/// +pub fn second(pair: #(a, b)) -> b { + let #(_, a) = pair + a +} + +/// Returns a new pair with the elements swapped. +/// +/// ## Examples +/// +/// ```gleam +/// > swap(#(1, 2)) +/// #(2, 1) +/// ``` +/// +pub fn swap(pair: #(a, b)) -> #(b, a) { + let #(a, b) = pair + #(b, a) +} + +/// Returns a new pair with the first element having had `with` applied to +/// it. +/// +/// ## Examples +/// +/// ```gleam +/// > #(1, 2) |> map_first(fn(n) { n * 2 }) +/// #(2, 2) +/// ``` +/// +pub fn map_first(of pair: #(a, b), with fun: fn(a) -> c) -> #(c, b) { + let #(a, b) = pair + #(fun(a), b) +} + +/// Returns a new pair with the second element having had `with` applied to +/// it. +/// +/// ## Examples +/// +/// ```gleam +/// > #(1, 2) |> map_second(fn(n) { n * 2 }) +/// #(1, 4) +/// ``` +/// +pub fn map_second(of pair: #(a, b), with fun: fn(b) -> c) -> #(a, c) { + let #(a, b) = pair + #(a, fun(b)) +} + +/// Returns a new pair with the given elements. This can also be done using the dedicated +/// syntax instead: `new(1, 2) == #(1, 2)`. +/// +/// ## Examples +/// +/// ```gleam +/// > new(1, 2) +/// #(1, 2) +/// ``` +/// +pub fn new(first: a, second: b) -> #(a, b) { + #(first, second) +} diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/queue.gleam b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/queue.gleam new file mode 100644 index 0000000..5bf60c8 --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/queue.gleam @@ -0,0 +1,292 @@ +import gleam/list + +/// A queue is an ordered collection of elements. It is similar to a list, but +/// unlike a list elements can be added to or removed from either the front or +/// the back in a performant fashion. +/// +/// The internal representation may be different for two queues with the same +/// elements in the same order if the queues were constructed in different +/// ways. This is the price paid for a queue's fast access at both the front +/// and the back. +/// +/// Because of unpredictable internal representation the equality operator `==` +/// may return surprising results, and the `is_equal` and `is_logically_equal` +/// functions are the recommended way to test queues for equality. +/// +pub opaque type Queue(element) { + Queue(in: List(element), out: List(element)) +} + +/// Creates a fresh queue that contains no values. +/// +pub fn new() -> Queue(a) { + Queue(in: [], out: []) +} + +/// Converts a list of elements into a queue of the same elements in the same +/// order. The first element in the list becomes the front element in the queue. +/// +/// This function runs in constant time. +/// +/// # Examples +/// +/// ```gleam +/// > [1, 2, 3] |> from_list |> length +/// 3 +/// ``` +/// +pub fn from_list(list: List(a)) -> Queue(a) { + Queue(in: [], out: list) +} + +/// Converts a queue of elements into a list of the same elements in the same +/// order. The front element in the queue becomes the first element in the list. +/// +/// This function runs in linear time. +/// +/// # Examples +/// +/// ```gleam +/// > new() |> push_back(1) |> push_back(2) |> to_list +/// [1, 2] +/// ``` +/// +pub fn to_list(queue: Queue(a)) -> List(a) { + queue.out + |> list.append(list.reverse(queue.in)) +} + +/// Determines whether or not the queue is empty. +/// +/// This function runs in constant time. +/// +/// ## Examples +/// +/// ```gleam +/// > [] |> from_list |> is_empty +/// True +/// ``` +/// +/// ```gleam +/// > [1] |> from_list |> is_empty +/// False +/// ``` +/// +/// ```gleam +/// > [1, 2] |> from_list |> is_empty +/// False +/// ``` +/// +pub fn is_empty(queue: Queue(a)) -> Bool { + queue.in == [] && queue.out == [] +} + +/// Counts the number of elements in a given queue. +/// +/// This function has to traverse the queue to determine the number of elements, +/// so it runs in linear time. +/// +/// ## Examples +/// +/// ```gleam +/// > length(from_list([])) +/// 0 +/// ``` +/// +/// ```gleam +/// > length(from_list([1])) +/// 1 +/// ``` +/// +/// ```gleam +/// > length(from_list([1, 2])) +/// 2 +/// ``` +/// +pub fn length(queue: Queue(a)) -> Int { + list.length(queue.in) + list.length(queue.out) +} + +/// Pushes an element onto the back of the queue. +/// +/// # Examples +/// +/// ```gleam +/// > [1, 2] |> from_list |> push_back(3) |> to_list +/// [1, 2, 3] +/// ``` +/// +pub fn push_back(onto queue: Queue(a), this item: a) -> Queue(a) { + Queue(in: [item, ..queue.in], out: queue.out) +} + +/// Pushes an element onto the front of the queue. +/// +/// # Examples +/// +/// ```gleam +/// > [0, 0] |> from_list |> push_front(1) |> to_list +/// [1, 0, 0] +/// ``` +/// +pub fn push_front(onto queue: Queue(a), this item: a) -> Queue(a) { + Queue(in: queue.in, out: [item, ..queue.out]) +} + +/// Gets the last element from the queue, returning the +/// element and a new queue without that element. +/// +/// This function typically runs in constant time, but will occasionally run in +/// linear time. +/// +/// # Examples +/// +/// ```gleam +/// > new() +/// > |> push_back(0) +/// > |> push_back(1) +/// > |> pop_back() +/// Ok(#(1, push_front(new(), 0))) +/// ``` +/// +/// ```gleam +/// > new() +/// > |> push_front(0) +/// > |> pop_back() +/// Ok(#(0, new())) +/// ``` +/// +/// ```gleam +/// > new() +/// > |> pop_back() +/// Error(Nil) +/// ``` +/// +pub fn pop_back(from queue: Queue(a)) -> Result(#(a, Queue(a)), Nil) { + case queue { + Queue(in: [], out: []) -> Error(Nil) + Queue(in: [], out: out) -> pop_back(Queue(in: list.reverse(out), out: [])) + Queue(in: [first, ..rest], out: out) -> { + let queue = Queue(in: rest, out: out) + Ok(#(first, queue)) + } + } +} + +/// Gets the first element from the queue, returning the +/// element and a new queue without that element. +/// +/// This function typically runs in constant time, but will occasionally run in +/// linear time. +/// +/// # Examples +/// +/// ```gleam +/// > queue.new() +/// > |> queue.push_front(1) +/// > |> queue.push_front(0) +/// > |> queue.pop_front() +/// Ok(#(0, queue.push_back(queue.new(), 1))) +/// ``` +/// +/// ```gleam +/// > queue.new() +/// > |> queue.push_back(0) +/// > |> queue.pop_front() +/// Ok(#(0, queue.new())) +/// ``` +/// +/// ```gleam +/// > queue.new() +/// > |> queue.pop_back() +/// Error(Nil) +/// ``` +/// +pub fn pop_front(from queue: Queue(a)) -> Result(#(a, Queue(a)), Nil) { + case queue { + Queue(in: [], out: []) -> Error(Nil) + Queue(in: in, out: []) -> pop_front(Queue(in: [], out: list.reverse(in))) + Queue(in: in, out: [first, ..rest]) -> { + let queue = Queue(in: in, out: rest) + Ok(#(first, queue)) + } + } +} + +/// Creates a new queue from a given queue containing the same elements, but in +/// the opposite order. +/// +/// This function runs in constant time. +/// +/// ## Examples +/// +/// ```gleam +/// > [] |> from_list |> reverse |> to_list +/// [] +/// ``` +/// +/// ```gleam +/// > [1] |> from_list |> reverse |> to_list +/// [1] +/// ``` +/// +/// ```gleam +/// > [1, 2] |> from_list |> reverse |> to_list +/// [2, 1] +/// ``` +/// +pub fn reverse(queue: Queue(a)) -> Queue(a) { + Queue(in: queue.out, out: queue.in) +} + +fn check_equal( + xs: List(t), + x_tail: List(t), + ys: List(t), + y_tail: List(t), + eq: fn(t, t) -> Bool, +) -> Bool { + case xs, x_tail, ys, y_tail { + [], [], [], [] -> True + [x, ..xs], _, [y, ..ys], _ -> + case eq(x, y) { + False -> False + True -> check_equal(xs, x_tail, ys, y_tail, eq) + } + [], [_, ..], _, _ -> check_equal(list.reverse(x_tail), [], ys, y_tail, eq) + _, _, [], [_, ..] -> check_equal(xs, x_tail, list.reverse(y_tail), [], eq) + _, _, _, _ -> False + } +} + +/// Checks whether two queues have equal elements in the same order, where the +/// equality of elements is determined by a given equality checking function. +/// +/// This function is useful as the internal representation may be different for +/// two queues with the same elements in the same order depending on how they +/// were constructed, so the equality operator `==` may return surprising +/// results. +/// +/// This function runs in linear time multiplied by the time taken by the +/// element equality checking function. +/// +pub fn is_logically_equal( + a: Queue(t), + to b: Queue(t), + checking element_is_equal: fn(t, t) -> Bool, +) -> Bool { + check_equal(a.out, a.in, b.out, b.in, element_is_equal) +} + +/// Checks whether two queues have the same elements in the same order. +/// +/// This function is useful as the internal representation may be different for +/// two queues with the same elements in the same order depending on how they +/// were constructed, so the equality operator `==` may return surprising +/// results. +/// +/// This function runs in linear time. +/// +pub fn is_equal(a: Queue(t), to b: Queue(t)) -> Bool { + check_equal(a.out, a.in, b.out, b.in, fn(a, b) { a == b }) +} diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/regex.gleam b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/regex.gleam new file mode 100644 index 0000000..9ffda78 --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/regex.gleam @@ -0,0 +1,214 @@ +//// This module contains regular expression matching functions for strings. +//// The matching algorithms of the library are based on the PCRE library, but not +//// all of the PCRE library is interfaced and some parts of the library go beyond +//// what PCRE offers. Currently PCRE version 8.40 (release date 2017-01-11) is used. + +import gleam/option.{type Option} + +pub type Regex + +/// The details about a particular match: +/// +pub type Match { + Match( + /// The full string of the match. + content: String, + /// A `Regex` can have subpatterns, sup-parts that are in parentheses. + submatches: List(Option(String)), + ) +} + +/// When a regular expression fails to compile: +/// +pub type CompileError { + CompileError( + /// The problem encountered that caused the compilation to fail + error: String, + /// The byte index into the string to where the problem was found + /// This value may not be correct in JavaScript environments. + byte_index: Int, + ) +} + +pub type Options { + Options(case_insensitive: Bool, multi_line: Bool) +} + +/// Creates a `Regex` with some additional options. +/// +/// ## Examples +/// +/// ```gleam +/// > let options = Options(case_insensitive: False, multi_line: True) +/// > let assert Ok(re) = compile("^[0-9]", with: options) +/// > check(re, "abc\n123") +/// True +/// ``` +/// +/// ```gleam +/// > let options = Options(case_insensitive: True, multi_line: False) +/// > let assert Ok(re) = compile("[A-Z]", with: options) +/// > check(re, "abc123") +/// True +/// ``` +/// +pub fn compile( + pattern: String, + with options: Options, +) -> Result(Regex, CompileError) { + do_compile(pattern, options) +} + +@external(erlang, "gleam_stdlib", "compile_regex") +@external(javascript, "../gleam_stdlib.mjs", "compile_regex") +fn do_compile(a: String, with with: Options) -> Result(Regex, CompileError) + +/// Creates a new `Regex`. +/// +/// ## Examples +/// +/// ```gleam +/// > let assert Ok(re) = from_string("[0-9]") +/// > check(re, "abc123") +/// True +/// ``` +/// +/// ```gleam +/// > check(re, "abcxyz") +/// False +/// ``` +/// +/// ```gleam +/// > from_string("[0-9") +/// Error( +/// CompileError( +/// error: "missing terminating ] for character class", +/// byte_index: 4 +/// ) +/// ) +/// ``` +/// +pub fn from_string(pattern: String) -> Result(Regex, CompileError) { + compile(pattern, Options(case_insensitive: False, multi_line: False)) +} + +/// Returns a boolean indicating whether there was a match or not. +/// +/// ## Examples +/// +/// ```gleam +/// > let assert Ok(re) = from_string("^f.o.?") +/// > check(with: re, content: "foo") +/// True +/// ``` +/// +/// ```gleam +/// > check(with: re, content: "boo") +/// False +/// ``` +/// +pub fn check(with regex: Regex, content content: String) -> Bool { + do_check(regex, content) +} + +@external(erlang, "gleam_stdlib", "regex_check") +@external(javascript, "../gleam_stdlib.mjs", "regex_check") +fn do_check(a: Regex, b: String) -> Bool + +/// Splits a string. +/// +/// ## Examples +/// +/// ```gleam +/// > let assert Ok(re) = from_string(" *, *") +/// > split(with: re, content: "foo,32, 4, 9 ,0") +/// ["foo", "32", "4", "9", "0"] +/// ``` +/// +pub fn split(with regex: Regex, content string: String) -> List(String) { + do_split(regex, string) +} + +@target(erlang) +@external(erlang, "gleam_stdlib", "regex_split") +fn do_split(a: Regex, b: String) -> List(String) + +@target(javascript) +fn do_split(regex, string) -> List(String) { + js_split(string, regex) +} + +@target(javascript) +@external(javascript, "../gleam_stdlib.mjs", "split") +fn js_split(a: String, b: Regex) -> List(String) + +/// Collects all matches of the regular expression. +/// +/// ## Examples +/// +/// ```gleam +/// > let assert Ok(re) = from_string("[oi]n a (\\w+)") +/// > scan(with: re, content: "I am on a boat in a lake.") +/// [ +/// Match( +/// content: "on a boat", +/// submatches: [Some("boat")] +/// ), +/// Match( +/// content: "in a lake", +/// submatches: [Some("lake")] +/// ) +/// ] +/// ``` +/// +/// ```gleam +/// > let assert Ok(re) = regex.from_string("([+|\\-])?(\\d+)(\\w+)?") +/// > scan(with: re, content: "-36") +/// [ +/// Match( +/// content: "-36", +/// submatches: [Some("-"), Some("36")] +/// ) +/// ] +/// +/// > scan(with: re, content: "36") +/// [ +/// Match( +/// content: "36", +/// submatches: [None, Some("36")] +/// ) +/// ] +/// ``` +/// +/// ```gleam +/// > let assert Ok(re) = regex.from_string("var\\s*(\\w+)\\s*(int|string)?\\s*=\\s*(.*)") +/// > scan(with: re, content: "var age = 32") +/// [ +/// Match( +/// content: "var age = 32", +/// submatches: [Some("age"), None, Some("32")] +/// ) +/// ] +/// ``` +/// +/// ```gleam +/// > let assert Ok(re) = regex.from_string("let (\\w+) = (\\w+)") +/// > scan(with: re, content: "let age = 32") +/// [ +/// Match( +/// content: "let age = 32", +/// submatches: [Some("age"), Some("32")] +/// ) +/// ] +/// +/// > scan(with: re, content: "const age = 32") +/// [] +/// ``` +/// +pub fn scan(with regex: Regex, content string: String) -> List(Match) { + do_scan(regex, string) +} + +@external(erlang, "gleam_stdlib", "regex_scan") +@external(javascript, "../gleam_stdlib.mjs", "regex_scan") +fn do_scan(a: Regex, b: String) -> List(Match) diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/result.gleam b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/result.gleam new file mode 100644 index 0000000..fb6dddb --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/result.gleam @@ -0,0 +1,482 @@ +//// Result represents the result of something that may succeed or not. +//// `Ok` means it was successful, `Error` means it was not successful. + +import gleam/list + +/// Checks whether the result is an `Ok` value. +/// +/// ## Examples +/// +/// ```gleam +/// > is_ok(Ok(1)) +/// True +/// ``` +/// +/// ```gleam +/// > is_ok(Error(Nil)) +/// False +/// ``` +/// +pub fn is_ok(result: Result(a, e)) -> Bool { + case result { + Error(_) -> False + Ok(_) -> True + } +} + +/// Checks whether the result is an `Error` value. +/// +/// ## Examples +/// +/// ```gleam +/// > is_error(Ok(1)) +/// False +/// ``` +/// +/// ```gleam +/// > is_error(Error(Nil)) +/// True +/// ``` +/// +pub fn is_error(result: Result(a, e)) -> Bool { + case result { + Ok(_) -> False + Error(_) -> True + } +} + +/// Updates a value held within the `Ok` of a result by calling a given function +/// on it. +/// +/// If the result is an `Error` rather than `Ok` the function is not called and the +/// result stays the same. +/// +/// ## Examples +/// +/// ```gleam +/// > map(over: Ok(1), with: fn(x) { x + 1 }) +/// Ok(2) +/// ``` +/// +/// ```gleam +/// > map(over: Error(1), with: fn(x) { x + 1 }) +/// Error(1) +/// ``` +/// +pub fn map(over result: Result(a, e), with fun: fn(a) -> b) -> Result(b, e) { + case result { + Ok(x) -> Ok(fun(x)) + Error(e) -> Error(e) + } +} + +/// Updates a value held within the `Error` of a result by calling a given function +/// on it. +/// +/// If the result is `Ok` rather than `Error` the function is not called and the +/// result stays the same. +/// +/// ## Examples +/// +/// ```gleam +/// > map_error(over: Error(1), with: fn(x) { x + 1 }) +/// Error(2) +/// ``` +/// +/// ```gleam +/// > map_error(over: Ok(1), with: fn(x) { x + 1 }) +/// Ok(1) +/// ``` +/// +pub fn map_error( + over result: Result(a, e), + with fun: fn(e) -> f, +) -> Result(a, f) { + case result { + Ok(x) -> Ok(x) + Error(error) -> Error(fun(error)) + } +} + +/// Merges a nested `Result` into a single layer. +/// +/// ## Examples +/// +/// ```gleam +/// > flatten(Ok(Ok(1))) +/// Ok(1) +/// ``` +/// +/// ```gleam +/// > flatten(Ok(Error(""))) +/// Error("") +/// ``` +/// +/// ```gleam +/// > flatten(Error(Nil)) +/// Error(Nil) +/// ``` +/// +pub fn flatten(result: Result(Result(a, e), e)) -> Result(a, e) { + case result { + Ok(x) -> x + Error(error) -> Error(error) + } +} + +/// "Updates" an `Ok` result by passing its value to a function that yields a result, +/// and returning the yielded result. (This may "replace" the `Ok` with an `Error`.) +/// +/// If the input is an `Error` rather than an `Ok`, the function is not called and +/// the original `Error` is returned. +/// +/// This function is the equivalent of calling `map` followed by `flatten`, and +/// it is useful for chaining together multiple functions that may fail. +/// +/// ## Examples +/// +/// ```gleam +/// > try(Ok(1), fn(x) { Ok(x + 1) }) +/// Ok(2) +/// ``` +/// +/// ```gleam +/// > try(Ok(1), fn(x) { Ok(#("a", x)) }) +/// Ok(#("a", 1)) +/// ``` +/// +/// ```gleam +/// > try(Ok(1), fn(_) { Error("Oh no") }) +/// Error("Oh no") +/// ``` +/// +/// ```gleam +/// > try(Error(Nil), fn(x) { Ok(x + 1) }) +/// Error(Nil) +/// ``` +/// +pub fn try( + result: Result(a, e), + apply fun: fn(a) -> Result(b, e), +) -> Result(b, e) { + case result { + Ok(x) -> fun(x) + Error(e) -> Error(e) + } +} + +/// An alias for `try`. See the documentation for that function for more information. +/// +pub fn then( + result: Result(a, e), + apply fun: fn(a) -> Result(b, e), +) -> Result(b, e) { + try(result, fun) +} + +/// Extracts the `Ok` value from a result, returning a default value if the result +/// is an `Error`. +/// +/// ## Examples +/// +/// ```gleam +/// > unwrap(Ok(1), 0) +/// 1 +/// ``` +/// +/// ```gleam +/// > unwrap(Error(""), 0) +/// 0 +/// ``` +/// +pub fn unwrap(result: Result(a, e), or default: a) -> a { + case result { + Ok(v) -> v + Error(_) -> default + } +} + +/// Extracts the `Ok` value from a result, evaluating the default function if the result +/// is an `Error`. +/// +/// ## Examples +/// +/// ```gleam +/// > lazy_unwrap(Ok(1), fn() { 0 }) +/// 1 +/// ``` +/// +/// ```gleam +/// > lazy_unwrap(Error(""), fn() { 0 }) +/// 0 +/// ``` +/// +pub fn lazy_unwrap(result: Result(a, e), or default: fn() -> a) -> a { + case result { + Ok(v) -> v + Error(_) -> default() + } +} + +/// Extracts the `Error` value from a result, returning a default value if the result +/// is an `Ok`. +/// +/// ## Examples +/// +/// ```gleam +/// > unwrap_error(Error(1), 0) +/// 1 +/// ``` +/// +/// ```gleam +/// > unwrap_error(Ok(""), 0) +/// 0 +/// ``` +/// +pub fn unwrap_error(result: Result(a, e), or default: e) -> e { + case result { + Ok(_) -> default + Error(e) -> e + } +} + +/// Extracts the inner value from a result. Both the value and error must be of +/// the same type. +/// +/// ## Examples +/// +/// ```gleam +/// > unwrap_both(Error(1)) +/// 1 +/// ``` +/// +/// ```gleam +/// > unwrap_both(Ok(2)) +/// 2 +/// ``` +/// +pub fn unwrap_both(result: Result(a, a)) -> a { + case result { + Ok(a) -> a + Error(a) -> a + } +} + +/// Transforms any error into `Error(Nil)`. +/// +/// ## Examples +/// +/// ```gleam +/// > nil_error(Error(1)) +/// Error(Nil) +/// ``` +/// +/// ```gleam +/// > nil_error(Ok(1)) +/// Ok(1) +/// ``` +/// +pub fn nil_error(result: Result(a, e)) -> Result(a, Nil) { + map_error(result, fn(_) { Nil }) +} + +/// Returns the first value if it is `Ok`, otherwise returns the second value. +/// +/// ## Examples +/// +/// ```gleam +/// > or(Ok(1), Ok(2)) +/// Ok(1) +/// ``` +/// +/// ```gleam +/// > or(Ok(1), Error("Error 2")) +/// Ok(1) +/// ``` +/// +/// ```gleam +/// > or(Error("Error 1"), Ok(2)) +/// Ok(2) +/// ``` +/// +/// ```gleam +/// > or(Error("Error 1"), Error("Error 2")) +/// Error("Error 2") +/// ``` +/// +pub fn or(first: Result(a, e), second: Result(a, e)) -> Result(a, e) { + case first { + Ok(_) -> first + Error(_) -> second + } +} + +/// Returns the first value if it is `Ok`, otherwise evaluates the given function for a fallback value. +/// +/// ## Examples +/// +/// ```gleam +/// > lazy_or(Ok(1), fn() { Ok(2) }) +/// Ok(1) +/// ``` +/// +/// ```gleam +/// > lazy_or(Ok(1), fn() { Error("Error 2") }) +/// Ok(1) +/// ``` +/// +/// ```gleam +/// > lazy_or(Error("Error 1"), fn() { Ok(2) }) +/// Ok(2) +/// ``` +/// +/// ```gleam +/// > lazy_or(Error("Error 1"), fn() { Error("Error 2") }) +/// Error("Error 2") +/// ``` +/// +pub fn lazy_or( + first: Result(a, e), + second: fn() -> Result(a, e), +) -> Result(a, e) { + case first { + Ok(_) -> first + Error(_) -> second() + } +} + +/// Combines a list of results into a single result. +/// If all elements in the list are `Ok` then returns an `Ok` holding the list of values. +/// If any element is `Error` then returns the first error. +/// +/// ## Examples +/// +/// ```gleam +/// > all([Ok(1), Ok(2)]) +/// Ok([1, 2]) +/// ``` +/// +/// ```gleam +/// > all([Ok(1), Error("e")]) +/// Error("e") +/// ``` +/// +pub fn all(results: List(Result(a, e))) -> Result(List(a), e) { + list.try_map(results, fn(x) { x }) +} + +/// Given a list of results, returns a pair where the first element is a list +/// of all the values inside `Ok` and the second element is a list with all the +/// values inside `Error`. The values in both lists appear in reverse order with +/// respect to their position in the original list of results. +/// +/// ## Examples +/// +/// ```gleam +/// > partition([Ok(1), Error("a"), Error("b"), Ok(2)]) +/// #([2, 1], ["b", "a"]) +/// ``` +/// +pub fn partition(results: List(Result(a, e))) -> #(List(a), List(e)) { + do_partition(results, [], []) +} + +fn do_partition(results: List(Result(a, e)), oks: List(a), errors: List(e)) { + case results { + [] -> #(oks, errors) + [Ok(a), ..rest] -> do_partition(rest, [a, ..oks], errors) + [Error(e), ..rest] -> do_partition(rest, oks, [e, ..errors]) + } +} + +/// Replace the value within a result +/// +/// ## Examples +/// +/// ```gleam +/// > replace(Ok(1), Nil) +/// Ok(Nil) +/// ``` +/// +/// ```gleam +/// > replace(Error(1), Nil) +/// Error(1) +/// ``` +/// +pub fn replace(result: Result(a, e), value: b) -> Result(b, e) { + case result { + Ok(_) -> Ok(value) + Error(error) -> Error(error) + } +} + +/// Replace the error within a result +/// +/// ## Examples +/// +/// ```gleam +/// > replace_error(Error(1), Nil) +/// Error(Nil) +/// ``` +/// +/// ```gleam +/// > replace_error(Ok(1), Nil) +/// Ok(1) +/// ``` +/// +pub fn replace_error(result: Result(a, e1), error: e2) -> Result(a, e2) { + case result { + Ok(x) -> Ok(x) + Error(_) -> Error(error) + } +} + +/// Given a list of results, returns only the values inside `Ok`. +/// +/// ## Examples +/// +/// ```gleam +/// > values([Ok(1), Error("a"), Ok(3)]) +/// [1, 3] +/// ``` +/// +pub fn values(results: List(Result(a, e))) -> List(a) { + list.filter_map(results, fn(r) { r }) +} + +/// Updates a value held within the `Error` of a result by calling a given function +/// on it, where the given function also returns a result. The two results are +/// then merged together into one result. +/// +/// If the result is an `Ok` rather than `Error` the function is not called and the +/// result stays the same. +/// +/// This function is useful for chaining together computations that may fail +/// and trying to recover from possible errors. +/// +/// ## Examples +/// +/// ```gleam +/// > Ok(1) |> try_recover(with: fn(_) { Error("failed to recover") }) +/// Ok(1) +/// ``` +/// +/// ```gleam +/// > Error(1) |> try_recover(with: fn(error) { Ok(error + 1) }) +/// Ok(2) +/// ``` +/// +/// ```gleam +/// > Error(1) |> try_recover(with: fn(error) { Error("failed to recover") }) +/// Error("failed to recover") +/// ``` +/// +pub fn try_recover( + result: Result(a, e), + with fun: fn(e) -> Result(a, f), +) -> Result(a, f) { + case result { + Ok(value) -> Ok(value) + Error(error) -> fun(error) + } +} diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/set.gleam b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/set.gleam new file mode 100644 index 0000000..4fd7545 --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/set.gleam @@ -0,0 +1,262 @@ +import gleam/list +import gleam/dict.{type Dict} +import gleam/result + +// A list is used as the map value as an empty list has the smallest +// representation in Erlang's binary format +@target(erlang) +type Token = + List(Nil) + +@target(erlang) +const token = [] + +@target(javascript) +type Token = + Nil + +@target(javascript) +const token = Nil + +/// A set is a collection of unique members of the same type. +/// +/// It is implemented using the `gleam/map` module, so inserts and lookups have +/// logarithmic time complexity. +/// +pub opaque type Set(member) { + Set(map: Dict(member, Token)) +} + +/// Creates a new empty set. +/// +pub fn new() -> Set(member) { + Set(dict.new()) +} + +/// Gets the number of members in a set. +/// +/// This function runs in constant time. +/// +/// ## Examples +/// +/// ```gleam +/// > new() +/// > |> insert(1) +/// > |> insert(2) +/// > |> size +/// 2 +/// ``` +/// +pub fn size(set: Set(member)) -> Int { + dict.size(set.map) +} + +/// Inserts an member into the set. +/// +/// This function runs in logarithmic time. +/// +/// ## Examples +/// +/// ```gleam +/// > new() +/// > |> insert(1) +/// > |> insert(2) +/// > |> size +/// 2 +/// ``` +/// +pub fn insert(into set: Set(member), this member: member) -> Set(member) { + Set(map: dict.insert(set.map, member, token)) +} + +/// Checks whether a set contains a given member. +/// +/// This function runs in logarithmic time. +/// +/// ## Examples +/// +/// ```gleam +/// > new() +/// > |> insert(2) +/// > |> contains(2) +/// True +/// ``` +/// +/// ```gleam +/// > new() +/// > |> insert(2) +/// > |> contains(1) +/// False +/// ``` +/// +pub fn contains(in set: Set(member), this member: member) -> Bool { + set.map + |> dict.get(member) + |> result.is_ok +} + +/// Removes a member from a set. If the set does not contain the member then +/// the set is returned unchanged. +/// +/// This function runs in logarithmic time. +/// +/// ## Examples +/// +/// ```gleam +/// > new() +/// > |> insert(2) +/// > |> delete(2) +/// > |> contains(1) +/// False +/// ``` +/// +pub fn delete(from set: Set(member), this member: member) -> Set(member) { + Set(map: dict.delete(set.map, member)) +} + +/// Converts the set into a list of the contained members. +/// +/// The list has no specific ordering, any unintentional ordering may change in +/// future versions of Gleam or Erlang. +/// +/// This function runs in linear time. +/// +/// ## Examples +/// +/// ```gleam +/// > new() |> insert(2) |> to_list +/// [2] +/// ``` +/// +pub fn to_list(set: Set(member)) -> List(member) { + dict.keys(set.map) +} + +/// Creates a new set of the members in a given list. +/// +/// This function runs in loglinear time. +/// +/// ## Examples +/// +/// ```gleam +/// > import gleam/list +/// > [1, 1, 2, 4, 3, 2] |> from_list |> to_list |> list.sort +/// [1, 3, 3, 4] +/// ``` +/// +pub fn from_list(members: List(member)) -> Set(member) { + let map = + list.fold(over: members, from: dict.new(), with: fn(m, k) { + dict.insert(m, k, token) + }) + Set(map) +} + +/// Combines all entries into a single value by calling a given function on each +/// one. +/// +/// Sets are not ordered so the values are not returned in any specific order. +/// Do not write code that relies on the order entries are used by this +/// function as it may change in later versions of Gleam or Erlang. +/// +/// # Examples +/// +/// ```gleam +/// > from_list([1, 3, 9]) +/// > |> fold(0, fn(member, accumulator) { accumulator + member }) +/// 13 +/// ``` +/// +pub fn fold( + over set: Set(member), + from initial: acc, + with reducer: fn(acc, member) -> acc, +) -> acc { + dict.fold(over: set.map, from: initial, with: fn(a, k, _) { reducer(a, k) }) +} + +/// Creates a new set from an existing set, minus any members that a given +/// function returns `False` for. +/// +/// This function runs in loglinear time. +/// +/// ## Examples +/// +/// ```gleam +/// > import gleam/int +/// > from_list([1, 4, 6, 3, 675, 44, 67]) +/// > |> filter(for: int.is_even) +/// > |> to_list +/// [4, 6, 44] +/// ``` +/// +pub fn filter( + in set: Set(member), + keeping predicate: fn(member) -> Bool, +) -> Set(member) { + Set(dict.filter(in: set.map, keeping: fn(m, _) { predicate(m) })) +} + +pub fn drop(from set: Set(member), drop disallowed: List(member)) -> Set(member) { + list.fold(over: disallowed, from: set, with: delete) +} + +/// Creates a new map from a given map, only including any members which are in +/// a given list. +/// +/// This function runs in loglinear time. +/// +/// ## Examples +/// +/// ```gleam +/// > from_list([1, 2, 3]) +/// > |> take([1, 3, 5]) +/// > |> to_list +/// [1, 3] +/// ``` +/// +pub fn take(from set: Set(member), keeping desired: List(member)) -> Set(member) { + Set(dict.take(from: set.map, keeping: desired)) +} + +fn order(first: Set(member), second: Set(member)) -> #(Set(member), Set(member)) { + case dict.size(first.map) > dict.size(second.map) { + True -> #(first, second) + False -> #(second, first) + } +} + +/// Creates a new set that contains all members of both given sets. +/// +/// This function runs in loglinear time. +/// +/// ## Examples +/// +/// ```gleam +/// > union(from_list([1, 2]), from_list([2, 3])) |> to_list +/// [1, 2, 3] +/// ``` +/// +pub fn union(of first: Set(member), and second: Set(member)) -> Set(member) { + let #(larger, smaller) = order(first, second) + fold(over: smaller, from: larger, with: insert) +} + +/// Creates a new set that contains members that are present in both given sets. +/// +/// This function runs in loglinear time. +/// +/// ## Examples +/// +/// ```gleam +/// > intersection(from_list([1, 2]), from_list([2, 3])) |> to_list +/// [2] +/// ``` +/// +pub fn intersection( + of first: Set(member), + and second: Set(member), +) -> Set(member) { + let #(larger, smaller) = order(first, second) + take(from: larger, keeping: to_list(smaller)) +} diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/string.gleam b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/string.gleam new file mode 100644 index 0000000..652784d --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/string.gleam @@ -0,0 +1,905 @@ +//// Strings in Gleam are UTF-8 binaries. They can be written in your code as +//// text surrounded by `"double quotes"`. + +import gleam/iterator.{type Iterator} +import gleam/list +import gleam/option.{type Option, None, Some} +import gleam/order +import gleam/string_builder.{type StringBuilder} + +/// Determines if a `String` is empty. +/// +/// ## Examples +/// +/// ```gleam +/// > is_empty("") +/// True +/// ``` +/// +/// ```gleam +/// > is_empty("the world") +/// False +/// ``` +/// +pub fn is_empty(str: String) -> Bool { + str == "" +} + +/// Gets the number of grapheme clusters in a given `String`. +/// +/// This function has to iterate across the whole string to count the number of +/// graphemes, so it runs in linear time. +/// +/// ## Examples +/// +/// ```gleam +/// > length("Gleam") +/// 5 +/// ``` +/// +/// ```gleam +/// > length("ß↑e̊") +/// 3 +/// ``` +/// +/// ```gleam +/// > length("") +/// 0 +/// ``` +/// +pub fn length(string: String) -> Int { + do_length(string) +} + +@external(erlang, "string", "length") +@external(javascript, "../gleam_stdlib.mjs", "string_length") +fn do_length(a: String) -> Int + +/// Reverses a `String`. +/// +/// This function has to iterate across the whole `String` so it runs in linear +/// time. +/// +/// ## Examples +/// +/// ```gleam +/// > reverse("stressed") +/// "desserts" +/// ``` +/// +pub fn reverse(string: String) -> String { + do_reverse(string) +} + +@target(erlang) +fn do_reverse(string: String) -> String { + string + |> string_builder.from_string + |> string_builder.reverse + |> string_builder.to_string +} + +@target(javascript) +fn do_reverse(string: String) -> String { + string + |> to_graphemes + |> list.reverse + |> concat +} + +/// Creates a new `String` by replacing all occurrences of a given substring. +/// +/// ## Examples +/// +/// ```gleam +/// > replace("www.example.com", each: ".", with: "-") +/// "www-example-com" +/// ``` +/// +/// ```gleam +/// > replace("a,b,c,d,e", each: ",", with: "/") +/// "a/b/c/d/e" +/// ``` +/// +pub fn replace( + in string: String, + each pattern: String, + with substitute: String, +) -> String { + string + |> string_builder.from_string + |> string_builder.replace(each: pattern, with: substitute) + |> string_builder.to_string +} + +/// Creates a new `String` with all the graphemes in the input `String` converted to +/// lowercase. +/// +/// Useful for case-insensitive comparisons. +/// +/// ## Examples +/// +/// ```gleam +/// > lowercase("X-FILES") +/// "x-files" +/// ``` +/// +pub fn lowercase(string: String) -> String { + do_lowercase(string) +} + +@external(erlang, "string", "lowercase") +@external(javascript, "../gleam_stdlib.mjs", "lowercase") +fn do_lowercase(a: String) -> String + +/// Creates a new `String` with all the graphemes in the input `String` converted to +/// uppercase. +/// +/// Useful for case-insensitive comparisons and VIRTUAL YELLING. +/// +/// ## Examples +/// +/// ```gleam +/// > uppercase("skinner") +/// "SKINNER" +/// ``` +/// +pub fn uppercase(string: String) -> String { + do_uppercase(string) +} + +@external(erlang, "string", "uppercase") +@external(javascript, "../gleam_stdlib.mjs", "uppercase") +fn do_uppercase(a: String) -> String + +/// Compares two `String`s to see which is "larger" by comparing their graphemes. +/// +/// This does not compare the size or length of the given `String`s. +/// +/// ## Examples +/// +/// ```gleam +/// > compare("Anthony", "Anthony") +/// order.Eq +/// ``` +/// +/// ```gleam +/// > compare("A", "B") +/// order.Lt +/// ``` +/// +pub fn compare(a: String, b: String) -> order.Order { + case a == b { + True -> order.Eq + _ -> + case less_than(a, b) { + True -> order.Lt + _ -> order.Gt + } + } +} + +@external(erlang, "gleam_stdlib", "less_than") +@external(javascript, "../gleam_stdlib.mjs", "less_than") +fn less_than(a: String, b: String) -> Bool + +/// Takes a substring given a start grapheme index and a length. Negative indexes +/// are taken starting from the *end* of the list. +/// +/// ## Examples +/// +/// ```gleam +/// > slice(from: "gleam", at_index: 1, length: 2) +/// "le" +/// ``` +/// +/// ```gleam +/// > slice(from: "gleam", at_index: 1, length: 10) +/// "leam" +/// ``` +/// +/// ```gleam +/// > slice(from: "gleam", at_index: 10, length: 3) +/// "" +/// ``` +/// +/// ```gleam +/// > slice(from: "gleam", at_index: -2, length: 2) +/// "am" +/// ``` +/// +/// ```gleam +/// > slice(from: "gleam", at_index: -12, length: 2) +/// "" +/// ``` +/// +pub fn slice(from string: String, at_index idx: Int, length len: Int) -> String { + case len < 0 { + True -> "" + False -> + case idx < 0 { + True -> { + let translated_idx = length(string) + idx + case translated_idx < 0 { + True -> "" + False -> do_slice(string, translated_idx, len) + } + } + False -> do_slice(string, idx, len) + } + } +} + +@external(erlang, "string", "slice") +fn do_slice(string: String, idx: Int, len: Int) -> String { + string + |> to_graphemes + |> list.drop(idx) + |> list.take(len) + |> concat +} + +/// Drops contents of the first `String` that occur before the second `String`. +/// If the `from` string does not contain the `before` string, `from` is returned unchanged. +/// +/// ## Examples +/// +/// ```gleam +/// > crop(from: "The Lone Gunmen", before: "Lone") +/// "Lone Gunmen" +/// ``` +/// +@external(erlang, "gleam_stdlib", "crop_string") +@external(javascript, "../gleam_stdlib.mjs", "crop_string") +pub fn crop(from string: String, before substring: String) -> String + +/// Drops *n* graphemes from the left side of a `String`. +/// +/// ## Examples +/// +/// ```gleam +/// > drop_left(from: "The Lone Gunmen", up_to: 2) +/// "e Lone Gunmen" +/// ``` +/// +pub fn drop_left(from string: String, up_to num_graphemes: Int) -> String { + case num_graphemes < 0 { + True -> string + False -> slice(string, num_graphemes, length(string) - num_graphemes) + } +} + +/// Drops *n* graphemes from the right side of a `String`. +/// +/// ## Examples +/// +/// ```gleam +/// > drop_right(from: "Cigarette Smoking Man", up_to: 2) +/// "Cigarette Smoking M" +/// ``` +/// +pub fn drop_right(from string: String, up_to num_graphemes: Int) -> String { + case num_graphemes < 0 { + True -> string + False -> slice(string, 0, length(string) - num_graphemes) + } +} + +/// Checks if the first `String` contains the second. +/// +/// ## Examples +/// +/// ```gleam +/// > contains(does: "theory", contain: "ory") +/// True +/// ``` +/// +/// ```gleam +/// > contains(does: "theory", contain: "the") +/// True +/// ``` +/// +/// ```gleam +/// > contains(does: "theory", contain: "THE") +/// False +/// ``` +/// +@external(erlang, "gleam_stdlib", "contains_string") +@external(javascript, "../gleam_stdlib.mjs", "contains_string") +pub fn contains(does haystack: String, contain needle: String) -> Bool + +/// Checks whether the first `String` starts with the second one. +/// +/// ## Examples +/// +/// ```gleam +/// > starts_with("theory", "ory") +/// False +/// ``` +/// +pub fn starts_with(string: String, prefix: String) -> Bool { + do_starts_with(string, prefix) +} + +@external(erlang, "gleam_stdlib", "string_starts_with") +@external(javascript, "../gleam_stdlib.mjs", "starts_with") +fn do_starts_with(a: String, b: String) -> Bool + +/// Checks whether the first `String` ends with the second one. +/// +/// ## Examples +/// +/// ```gleam +/// > ends_with("theory", "ory") +/// True +/// ``` +/// +pub fn ends_with(string: String, suffix: String) -> Bool { + do_ends_with(string, suffix) +} + +@external(erlang, "gleam_stdlib", "string_ends_with") +@external(javascript, "../gleam_stdlib.mjs", "ends_with") +fn do_ends_with(a: String, b: String) -> Bool + +/// Creates a list of `String`s by splitting a given string on a given substring. +/// +/// ## Examples +/// +/// ```gleam +/// > split("home/gleam/desktop/", on: "/") +/// ["home", "gleam", "desktop", ""] +/// ``` +/// +pub fn split(x: String, on substring: String) -> List(String) { + case substring { + "" -> to_graphemes(x) + _ -> + x + |> string_builder.from_string + |> string_builder.split(on: substring) + |> list.map(with: string_builder.to_string) + } +} + +/// Splits a `String` a single time on the given substring. +/// +/// Returns an `Error` if substring not present. +/// +/// ## Examples +/// +/// ```gleam +/// > split_once("home/gleam/desktop/", on: "/") +/// Ok(#("home", "gleam/desktop/")) +/// ``` +/// +/// ```gleam +/// > split_once("home/gleam/desktop/", on: "?") +/// Error(Nil) +/// ``` +/// +pub fn split_once( + x: String, + on substring: String, +) -> Result(#(String, String), Nil) { + do_split_once(x, substring) +} + +@target(erlang) +@external(erlang, "string", "split") +fn erl_split(a: String, b: String) -> List(String) + +@target(erlang) +fn do_split_once(x: String, substring: String) -> Result(#(String, String), Nil) { + case erl_split(x, substring) { + [first, rest] -> Ok(#(first, rest)) + _ -> Error(Nil) + } +} + +@target(javascript) +@external(javascript, "../gleam_stdlib.mjs", "split_once") +fn do_split_once( + x x: String, + substring substring: String, +) -> Result(#(String, String), Nil) + +/// Creates a new `String` by joining two `String`s together. +/// +/// This function copies both `String`s and runs in linear time. If you find +/// yourself joining `String`s frequently consider using the [`string_builder`](../gleam/string_builder.html) +/// module as it can append `String`s much faster! +/// +/// ## Examples +/// +/// ```gleam +/// > append(to: "butter", suffix: "fly") +/// "butterfly" +/// ``` +/// +pub fn append(to first: String, suffix second: String) -> String { + first + |> string_builder.from_string + |> string_builder.append(second) + |> string_builder.to_string +} + +/// Creates a new `String` by joining many `String`s together. +/// +/// This function copies both `String`s and runs in linear time. If you find +/// yourself joining `String`s frequently consider using the [`string_builder`](../gleam/string_builder.html) +/// module as it can append `String`s much faster! +/// +/// ## Examples +/// +/// ```gleam +/// > concat(["never", "the", "less"]) +/// "nevertheless" +/// ``` +/// +pub fn concat(strings: List(String)) -> String { + strings + |> string_builder.from_strings + |> string_builder.to_string +} + +/// Creates a new `String` by repeating a `String` a given number of times. +/// +/// This function runs in linear time. +/// +/// ## Examples +/// +/// ```gleam +/// > repeat("ha", times: 3) +/// "hahaha" +/// ``` +/// +pub fn repeat(string: String, times times: Int) -> String { + iterator.repeat(string) + |> iterator.take(times) + |> iterator.to_list + |> concat +} + +/// Joins many `String`s together with a given separator. +/// +/// This function runs in linear time. +/// +/// ## Examples +/// +/// ```gleam +/// > join(["home","evan","Desktop"], with: "/") +/// "home/evan/Desktop" +/// ``` +/// +pub fn join(strings: List(String), with separator: String) -> String { + do_join(strings, separator) +} + +@external(javascript, "../gleam_stdlib.mjs", "join") +fn do_join(strings: List(String), separator: String) -> String { + strings + |> list.intersperse(with: separator) + |> concat +} + +/// Pads a `String` on the left until it has at least given number of graphemes. +/// +/// ## Examples +/// +/// ```gleam +/// > pad_left("121", to: 5, with: ".") +/// "..121" +/// ``` +/// +/// ```gleam +/// > pad_left("121", to: 3, with: ".") +/// "121" +/// ``` +/// +/// ```gleam +/// > pad_left("121", to: 2, with: ".") +/// "121" +/// ``` +/// +pub fn pad_left(string: String, to desired_length: Int, with pad_string: String) { + let current_length = length(string) + let to_pad_length = desired_length - current_length + padding(to_pad_length, pad_string) + |> iterator.append(iterator.single(string)) + |> iterator.to_list + |> concat +} + +/// Pads a `String` on the right until it has a given length. +/// +/// ## Examples +/// +/// ```gleam +/// > pad_right("123", to: 5, with: ".") +/// "123.." +/// ``` +/// +/// ```gleam +/// > pad_right("123", to: 3, with: ".") +/// "123" +/// ``` +/// +/// ```gleam +/// > pad_right("123", to: 2, with: ".") +/// "123" +/// ``` +/// +pub fn pad_right( + string: String, + to desired_length: Int, + with pad_string: String, +) { + let current_length = length(string) + let to_pad_length = desired_length - current_length + iterator.single(string) + |> iterator.append(padding(to_pad_length, pad_string)) + |> iterator.to_list + |> concat +} + +fn padding(size: Int, pad_string: String) -> Iterator(String) { + let pad_length = length(pad_string) + let num_pads = size / pad_length + let extra = size % pad_length + iterator.repeat(pad_string) + |> iterator.take(num_pads) + |> iterator.append(iterator.single(slice(pad_string, 0, extra))) +} + +/// Removes whitespace on both sides of a `String`. +/// +/// ## Examples +/// +/// ```gleam +/// > trim(" hats \n") +/// "hats" +/// ``` +/// +pub fn trim(string: String) -> String { + do_trim(string) +} + +@target(erlang) +fn do_trim(string: String) -> String { + erl_trim(string, Both) +} + +@target(erlang) +type Direction { + Leading + Trailing + Both +} + +@target(erlang) +@external(erlang, "string", "trim") +fn erl_trim(a: String, b: Direction) -> String + +@target(javascript) +@external(javascript, "../gleam_stdlib.mjs", "trim") +fn do_trim(string string: String) -> String + +/// Removes whitespace on the left of a `String`. +/// +/// ## Examples +/// +/// ```gleam +/// > trim_left(" hats \n") +/// "hats \n" +/// ``` +/// +pub fn trim_left(string: String) -> String { + do_trim_left(string) +} + +@target(erlang) +fn do_trim_left(string: String) -> String { + erl_trim(string, Leading) +} + +@target(javascript) +@external(javascript, "../gleam_stdlib.mjs", "trim_left") +fn do_trim_left(string string: String) -> String + +/// Removes whitespace on the right of a `String`. +/// +/// ## Examples +/// +/// ```gleam +/// > trim_right(" hats \n") +/// " hats" +/// ``` +/// +pub fn trim_right(string: String) -> String { + do_trim_right(string) +} + +@target(erlang) +fn do_trim_right(string: String) -> String { + erl_trim(string, Trailing) +} + +@target(javascript) +@external(javascript, "../gleam_stdlib.mjs", "trim_right") +fn do_trim_right(string string: String) -> String + +/// Splits a non-empty `String` into its first element (head) and rest (tail). +/// This lets you pattern match on `String`s exactly as you would with lists. +/// +/// Note on JavaScript using the function to iterate over a string will likely +/// be slower than using `to_graphemes` due to string slicing being more +/// expensive on JavaScript than Erlang. +/// +/// ## Examples +/// +/// ```gleam +/// > pop_grapheme("gleam") +/// Ok(#("g", "leam")) +/// ``` +/// +/// ```gleam +/// > pop_grapheme("") +/// Error(Nil) +/// ``` +/// +pub fn pop_grapheme(string: String) -> Result(#(String, String), Nil) { + do_pop_grapheme(string) +} + +@external(erlang, "gleam_stdlib", "string_pop_grapheme") +@external(javascript, "../gleam_stdlib.mjs", "pop_grapheme") +fn do_pop_grapheme(string string: String) -> Result(#(String, String), Nil) + +/// Converts a `String` to a list of +/// [graphemes](https://en.wikipedia.org/wiki/Grapheme). +/// +/// ```gleam +/// > to_graphemes("abc") +/// ["a", "b", "c"] +/// ``` +/// +@external(javascript, "../gleam_stdlib.mjs", "graphemes") +pub fn to_graphemes(string: String) -> List(String) { + do_to_graphemes(string, []) + |> list.reverse +} + +fn do_to_graphemes(string: String, acc: List(String)) -> List(String) { + case pop_grapheme(string) { + Ok(#(grapheme, rest)) -> do_to_graphemes(rest, [grapheme, ..acc]) + _ -> acc + } +} + +@external(erlang, "gleam_stdlib", "identity") +@external(javascript, "../gleam_stdlib.mjs", "codepoint") +fn unsafe_int_to_utf_codepoint(a: Int) -> UtfCodepoint + +/// Converts a `String` to a `List` of `UtfCodepoint`. +/// +/// See <https://en.wikipedia.org/wiki/Code_point> and +/// <https://en.wikipedia.org/wiki/Unicode#Codespace_and_Code_Points> for an +/// explanation on code points. +/// +/// ## Examples +/// +/// ```gleam +/// > "a" |> to_utf_codepoints +/// [UtfCodepoint(97)] +/// ``` +/// +/// ```gleam +/// // Semantically the same as: +/// // ["🏳", "️", "", "🌈"] or: +/// // [waving_white_flag, variant_selector_16, zero_width_joiner, rainbow] +/// > "🏳️🌈" |> to_utf_codepoints +/// [UtfCodepoint(127987), UtfCodepoint(65039), UtfCodepoint(8205), UtfCodepoint(127752)] +/// ``` +/// +pub fn to_utf_codepoints(string: String) -> List(UtfCodepoint) { + do_to_utf_codepoints(string) +} + +@target(erlang) +fn do_to_utf_codepoints(string: String) -> List(UtfCodepoint) { + do_to_utf_codepoints_impl(<<string:utf8>>, []) + |> list.reverse +} + +@target(erlang) +fn do_to_utf_codepoints_impl( + bit_array: BitArray, + acc: List(UtfCodepoint), +) -> List(UtfCodepoint) { + case bit_array { + <<first:utf8_codepoint, rest:bytes>> -> + do_to_utf_codepoints_impl(rest, [first, ..acc]) + _ -> acc + } +} + +@target(javascript) +fn do_to_utf_codepoints(string: String) -> List(UtfCodepoint) { + string + |> string_to_codepoint_integer_list + |> list.map(unsafe_int_to_utf_codepoint) +} + +@target(javascript) +@external(javascript, "../gleam_stdlib.mjs", "string_to_codepoint_integer_list") +fn string_to_codepoint_integer_list(a: String) -> List(Int) + +/// Converts a `List` of `UtfCodepoint`s to a `String`. +/// +/// See <https://en.wikipedia.org/wiki/Code_point> and +/// <https://en.wikipedia.org/wiki/Unicode#Codespace_and_Code_Points> for an +/// explanation on code points. +/// +/// ## Examples +/// +/// ```gleam +/// > { +/// > let assert #(Ok(a), Ok(b), Ok(c)) = #( +/// > utf_codepoint(97), +/// > utf_codepoint(98), +/// > utf_codepoint(99), +/// > ) +/// > [a, b, c] +/// > } +/// > |> from_utf_codepoints +/// "abc" +/// ``` +/// +@external(erlang, "gleam_stdlib", "utf_codepoint_list_to_string") +@external(javascript, "../gleam_stdlib.mjs", "utf_codepoint_list_to_string") +pub fn from_utf_codepoints(utf_codepoints: List(UtfCodepoint)) -> String + +/// Converts an integer to a `UtfCodepoint`. +/// +/// Returns an `Error` if the integer does not represent a valid UTF codepoint. +/// +pub fn utf_codepoint(value: Int) -> Result(UtfCodepoint, Nil) { + case value { + i if i > 1_114_111 -> Error(Nil) + 65_534 | 65_535 -> Error(Nil) + i if i >= 55_296 && i <= 57_343 -> Error(Nil) + i -> Ok(unsafe_int_to_utf_codepoint(i)) + } +} + +/// Converts an UtfCodepoint to its ordinal code point value. +/// +/// ## Examples +/// +/// ```gleam +/// > let assert [utf_codepoint, ..] = to_utf_codepoints("💜") +/// > utf_codepoint_to_int(utf_codepoint) +/// 128156 +/// ``` +/// +pub fn utf_codepoint_to_int(cp: UtfCodepoint) -> Int { + do_utf_codepoint_to_int(cp) +} + +@external(erlang, "gleam_stdlib", "identity") +@external(javascript, "../gleam_stdlib.mjs", "utf_codepoint_to_int") +fn do_utf_codepoint_to_int(cp cp: UtfCodepoint) -> Int + +/// Converts a `String` into `Option(String)` where an empty `String` becomes +/// `None`. +/// +/// ## Examples +/// +/// ```gleam +/// > to_option("") +/// None +/// ``` +/// +/// ```gleam +/// > to_option("hats") +/// Some("hats") +/// ``` +/// +pub fn to_option(s: String) -> Option(String) { + case s { + "" -> None + _ -> Some(s) + } +} + +/// Returns the first grapheme cluster in a given `String` and wraps it in a +/// `Result(String, Nil)`. If the `String` is empty, it returns `Error(Nil)`. +/// Otherwise, it returns `Ok(String)`. +/// +/// ## Examples +/// +/// ```gleam +/// > first("") +/// Error(Nil) +/// ``` +/// +/// ```gleam +/// > first("icecream") +/// Ok("i") +/// ``` +/// +pub fn first(s: String) -> Result(String, Nil) { + case pop_grapheme(s) { + Ok(#(first, _)) -> Ok(first) + Error(e) -> Error(e) + } +} + +/// Returns the last grapheme cluster in a given `String` and wraps it in a +/// `Result(String, Nil)`. If the `String` is empty, it returns `Error(Nil)`. +/// Otherwise, it returns `Ok(String)`. +/// +/// ## Examples +/// +/// ```gleam +/// > last("") +/// Error(Nil) +/// ``` +/// +/// ```gleam +/// > last("icecream") +/// Ok("m") +/// ``` +/// +pub fn last(s: String) -> Result(String, Nil) { + case pop_grapheme(s) { + Ok(#(first, "")) -> Ok(first) + Ok(#(_, rest)) -> Ok(slice(rest, -1, 1)) + Error(e) -> Error(e) + } +} + +/// Creates a new `String` with the first grapheme in the input `String` +/// converted to uppercase and the remaining graphemes to lowercase. +/// +/// ## Examples +/// +/// ```gleam +/// > capitalise("mamouna") +/// "Mamouna" +/// ``` +/// +pub fn capitalise(s: String) -> String { + case pop_grapheme(s) { + Ok(#(first, rest)) -> append(to: uppercase(first), suffix: lowercase(rest)) + _ -> "" + } +} + +/// Returns a `String` representation of a term in Gleam syntax. +/// +pub fn inspect(term: anything) -> String { + do_inspect(term) + |> string_builder.to_string +} + +@external(erlang, "gleam_stdlib", "inspect") +@external(javascript, "../gleam_stdlib.mjs", "inspect") +fn do_inspect(term term: anything) -> StringBuilder + +/// Returns the number of bytes in a `String`. +/// +/// This function runs in constant time on Erlang and in linear time on +/// JavaScript. +/// +/// ## Examples +/// +/// ```gleam +/// > byte_size("🏳️⚧️🏳️🌈👩🏾❤️👨🏻") +/// 58 +/// ``` +/// +@external(erlang, "erlang", "byte_size") +@external(javascript, "../gleam_stdlib.mjs", "byte_size") +pub fn byte_size(string: String) -> Int diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/string_builder.gleam b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/string_builder.gleam new file mode 100644 index 0000000..5792ca8 --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/string_builder.gleam @@ -0,0 +1,298 @@ +import gleam/list + +/// `StringBuilder` is a type used for efficiently building strings. +/// +/// When we append one string to another the strings must be copied to a +/// new location in memory so that they can sit together. This behaviour +/// enables efficient reading of the string but copying can be expensive, +/// especially if we want to join many strings together. +/// +/// `StringBuilder` is different in that it can be joined together in constant time +/// using minimal memory, and then can be efficiently converted to a string +/// using the `to_string` function. +/// +/// On Erlang this type is compatible with Erlang's iodata. On JavaScript this +/// type is compatible with normal strings. +/// +pub type StringBuilder + +/// Create an empty `StringBuilder`. Useful as the start of a pipe chaining many +/// builders together. +/// +pub fn new() -> StringBuilder { + do_from_strings([]) +} + +/// Prepends a `String` onto the start of some `StringBuilder`. +/// +/// Runs in constant time. +/// +pub fn prepend( + to builder: StringBuilder, + prefix prefix: String, +) -> StringBuilder { + append_builder(from_string(prefix), builder) +} + +/// Appends a `String` onto the end of some `StringBuilder`. +/// +/// Runs in constant time. +/// +pub fn append(to builder: StringBuilder, suffix second: String) -> StringBuilder { + append_builder(builder, from_string(second)) +} + +/// Prepends some `StringBuilder` onto the start of another. +/// +/// Runs in constant time. +/// +pub fn prepend_builder( + to builder: StringBuilder, + prefix prefix: StringBuilder, +) -> StringBuilder { + do_append(prefix, builder) +} + +/// Appends some `StringBuilder` onto the end of another. +/// +/// Runs in constant time. +/// +pub fn append_builder( + to builder: StringBuilder, + suffix suffix: StringBuilder, +) -> StringBuilder { + do_append(builder, suffix) +} + +@external(erlang, "gleam_stdlib", "iodata_append") +@external(javascript, "../gleam_stdlib.mjs", "add") +fn do_append(a: StringBuilder, b: StringBuilder) -> StringBuilder + +/// Converts a list of strings into a builder. +/// +/// Runs in constant time. +/// +pub fn from_strings(strings: List(String)) -> StringBuilder { + do_from_strings(strings) +} + +@external(erlang, "gleam_stdlib", "identity") +@external(javascript, "../gleam_stdlib.mjs", "concat") +fn do_from_strings(a: List(String)) -> StringBuilder + +/// Joins a list of builders into a single builder. +/// +/// Runs in constant time. +/// +pub fn concat(builders: List(StringBuilder)) -> StringBuilder { + do_concat(builders) +} + +@external(erlang, "gleam_stdlib", "identity") +@external(javascript, "../gleam_stdlib.mjs", "concat") +fn do_concat(a: List(StringBuilder)) -> StringBuilder + +/// Converts a string into a builder. +/// +/// Runs in constant time. +/// +pub fn from_string(string: String) -> StringBuilder { + do_from_string(string) +} + +@external(erlang, "gleam_stdlib", "identity") +@external(javascript, "../gleam_stdlib.mjs", "identity") +fn do_from_string(a: String) -> StringBuilder + +/// Turns an `StringBuilder` into a `String` +/// +/// This function is implemented natively by the virtual machine and is highly +/// optimised. +/// +pub fn to_string(builder: StringBuilder) -> String { + do_to_string(builder) +} + +@external(erlang, "unicode", "characters_to_binary") +@external(javascript, "../gleam_stdlib.mjs", "identity") +fn do_to_string(a: StringBuilder) -> String + +/// Returns the size of the `StringBuilder` in bytes. +/// +pub fn byte_size(builder: StringBuilder) -> Int { + do_byte_size(builder) +} + +@external(erlang, "erlang", "iolist_size") +@external(javascript, "../gleam_stdlib.mjs", "length") +fn do_byte_size(a: StringBuilder) -> Int + +/// Joins the given builders into a new builder separated with the given string +/// +pub fn join(builders: List(StringBuilder), with sep: String) -> StringBuilder { + builders + |> list.intersperse(from_string(sep)) + |> concat +} + +/// Converts a builder to a new builder where the contents have been +/// lowercased. +/// +pub fn lowercase(builder: StringBuilder) -> StringBuilder { + do_lowercase(builder) +} + +@external(erlang, "string", "lowercase") +@external(javascript, "../gleam_stdlib.mjs", "lowercase") +fn do_lowercase(a: StringBuilder) -> StringBuilder + +/// Converts a builder to a new builder where the contents have been +/// uppercased. +/// +pub fn uppercase(builder: StringBuilder) -> StringBuilder { + do_uppercase(builder) +} + +@external(erlang, "string", "uppercase") +@external(javascript, "../gleam_stdlib.mjs", "uppercase") +fn do_uppercase(a: StringBuilder) -> StringBuilder + +/// Converts a builder to a new builder with the contents reversed. +/// +pub fn reverse(builder: StringBuilder) -> StringBuilder { + do_reverse(builder) +} + +@target(erlang) +@external(erlang, "string", "reverse") +fn do_reverse(a: StringBuilder) -> StringBuilder + +@target(javascript) +fn do_reverse(builder: StringBuilder) -> StringBuilder { + builder + |> to_string + |> do_to_graphemes + |> list.reverse + |> from_strings +} + +@target(javascript) +@external(javascript, "../gleam_stdlib.mjs", "graphemes") +fn do_to_graphemes(string string: String) -> List(String) + +/// Splits a builder on a given pattern into a list of builders. +/// +pub fn split(iodata: StringBuilder, on pattern: String) -> List(StringBuilder) { + do_split(iodata, pattern) +} + +@target(erlang) +type Direction { + All +} + +@target(erlang) +@external(erlang, "string", "split") +fn erl_split(a: StringBuilder, b: String, c: Direction) -> List(StringBuilder) + +@target(erlang) +fn do_split(iodata: StringBuilder, pattern: String) -> List(StringBuilder) { + erl_split(iodata, pattern, All) +} + +@target(javascript) +@external(javascript, "../gleam_stdlib.mjs", "split") +fn do_split( + builder builder: StringBuilder, + pattern pattern: String, +) -> List(StringBuilder) + +/// Replaces all instances of a pattern with a given string substitute. +/// +pub fn replace( + in builder: StringBuilder, + each pattern: String, + with substitute: String, +) -> StringBuilder { + do_replace(builder, pattern, substitute) +} + +@target(erlang) +fn do_replace( + iodata: StringBuilder, + pattern: String, + substitute: String, +) -> StringBuilder { + erl_replace(iodata, pattern, substitute, All) +} + +@target(erlang) +@external(erlang, "string", "replace") +fn erl_replace( + a: StringBuilder, + b: String, + c: String, + d: Direction, +) -> StringBuilder + +@target(javascript) +@external(javascript, "../gleam_stdlib.mjs", "string_replace") +fn do_replace(a: StringBuilder, b: String, c: String) -> StringBuilder + +/// Compares two builders to determine if they have the same textual content. +/// +/// Comparing two iodata using the `==` operator may return `False` even if they +/// have the same content as they may have been build in different ways, so +/// using this function is often preferred. +/// +/// ## Examples +/// +/// ```gleam +/// > from_strings(["a", "b"]) == from_string("ab") +/// False +/// ``` +/// +/// ```gleam +/// > is_equal(from_strings(["a", "b"]), from_string("ab")) +/// True +/// ``` +/// +pub fn is_equal(a: StringBuilder, b: StringBuilder) -> Bool { + do_is_equal(a, b) +} + +@external(erlang, "string", "equal") +@external(javascript, "../gleam_stdlib.mjs", "equal") +fn do_is_equal(a: StringBuilder, b: StringBuilder) -> Bool + +/// Inspects a builder to determine if it is equivalent to an empty string. +/// +/// ## Examples +/// +/// ```gleam +/// > from_string("ok") |> is_empty +/// False +/// ``` +/// +/// ```gleam +/// > from_string("") |> is_empty +/// True +/// ``` +/// +/// ```gleam +/// > from_strings([]) |> is_empty +/// True +/// ``` +/// +pub fn is_empty(builder: StringBuilder) -> Bool { + do_is_empty(builder) +} + +@target(erlang) +@external(erlang, "string", "is_empty") +fn do_is_empty(a: StringBuilder) -> Bool + +@target(javascript) +fn do_is_empty(builder: StringBuilder) -> Bool { + from_string("") == builder +} diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/uri.gleam b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/uri.gleam new file mode 100644 index 0000000..11f6ea6 --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam/uri.gleam @@ -0,0 +1,462 @@ +//// Utilities for working with URIs +//// +//// This module provides functions for working with URIs (for example, parsing +//// URIs or encoding query strings). The functions in this module are implemented +//// according to [RFC 3986](https://tools.ietf.org/html/rfc3986). +//// +//// Query encoding (Form encoding) is defined in the +//// [W3C specification](https://www.w3.org/TR/html52/sec-forms.html#urlencoded-form-data). + +import gleam/int +import gleam/list +import gleam/option.{type Option, None, Some} +import gleam/string +import gleam/string_builder.{type StringBuilder} +@target(javascript) +import gleam/pair +@target(javascript) +import gleam/regex +@target(javascript) +import gleam/result + +/// Type representing holding the parsed components of an URI. +/// All components of a URI are optional, except the path. +/// +pub type Uri { + Uri( + scheme: Option(String), + userinfo: Option(String), + host: Option(String), + port: Option(Int), + path: String, + query: Option(String), + fragment: Option(String), + ) +} + +/// Parses a compliant URI string into the `Uri` Type. +/// If the string is not a valid URI string then an error is returned. +/// +/// The opposite operation is `uri.to_string`. +/// +/// ## Examples +/// +/// ```gleam +/// > parse("https://example.com:1234/a/b?query=true#fragment") +/// Ok( +/// Uri( +/// scheme: Some("https"), +/// userinfo: None, +/// host: Some("example.com"), +/// port: Some(1234), +/// path: "/a/b", +/// query: Some("query=true"), +/// fragment: Some("fragment") +/// ) +/// ) +/// ``` +/// +pub fn parse(uri_string: String) -> Result(Uri, Nil) { + do_parse(uri_string) +} + +@target(erlang) +@external(erlang, "gleam_stdlib", "uri_parse") +fn do_parse(a: String) -> Result(Uri, Nil) + +@target(javascript) +fn do_parse(uri_string: String) -> Result(Uri, Nil) { + // From https://tools.ietf.org/html/rfc3986#appendix-B + let pattern = + // 12 3 4 5 6 7 8 + "^(([a-z][a-z0-9\\+\\-\\.]*):)?(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#.*)?" + let matches = + pattern + |> regex_submatches(uri_string) + |> pad_list(8) + + let #(scheme, authority, path, query, fragment) = case matches { + [ + _scheme_with_colon, + scheme, + authority_with_slashes, + _authority, + path, + query_with_question_mark, + _query, + fragment, + ] -> #( + scheme, + authority_with_slashes, + path, + query_with_question_mark, + fragment, + ) + _ -> #(None, None, None, None, None) + } + + let scheme = noneify_empty_string(scheme) + let path = option.unwrap(path, "") + let query = noneify_query(query) + let #(userinfo, host, port) = split_authority(authority) + let fragment = + fragment + |> option.to_result(Nil) + |> result.try(string.pop_grapheme) + |> result.map(pair.second) + |> option.from_result + let scheme = + scheme + |> noneify_empty_string + |> option.map(string.lowercase) + Ok(Uri( + scheme: scheme, + userinfo: userinfo, + host: host, + port: port, + path: path, + query: query, + fragment: fragment, + )) +} + +@target(javascript) +fn regex_submatches(pattern: String, string: String) -> List(Option(String)) { + pattern + |> regex.compile(regex.Options(case_insensitive: True, multi_line: False)) + |> result.nil_error + |> result.map(regex.scan(_, string)) + |> result.try(list.first) + |> result.map(fn(m: regex.Match) { m.submatches }) + |> result.unwrap([]) +} + +@target(javascript) +fn noneify_query(x: Option(String)) -> Option(String) { + case x { + None -> None + Some(x) -> + case string.pop_grapheme(x) { + Ok(#("?", query)) -> Some(query) + _ -> None + } + } +} + +@target(javascript) +fn noneify_empty_string(x: Option(String)) -> Option(String) { + case x { + Some("") | None -> None + Some(_) -> x + } +} + +// Split an authority into its userinfo, host and port parts. +@target(javascript) +fn split_authority( + authority: Option(String), +) -> #(Option(String), Option(String), Option(Int)) { + case option.unwrap(authority, "") { + "" -> #(None, None, None) + "//" -> #(None, Some(""), None) + authority -> { + let matches = + "^(//)?((.*)@)?(\\[[a-zA-Z0-9:.]*\\]|[^:]*)(:(\\d*))?" + |> regex_submatches(authority) + |> pad_list(6) + case matches { + [_, _, userinfo, host, _, port] -> { + let userinfo = noneify_empty_string(userinfo) + let host = noneify_empty_string(host) + let port = + port + |> option.unwrap("") + |> int.parse + |> option.from_result + #(userinfo, host, port) + } + _ -> #(None, None, None) + } + } + } +} + +@target(javascript) +fn pad_list(list: List(Option(a)), size: Int) -> List(Option(a)) { + list + |> list.append(list.repeat(None, extra_required(list, size))) +} + +@target(javascript) +fn extra_required(list: List(a), remaining: Int) -> Int { + case list { + _ if remaining == 0 -> 0 + [] -> remaining + [_, ..xs] -> extra_required(xs, remaining - 1) + } +} + +/// Parses an urlencoded query string into a list of key value pairs. +/// Returns an error for invalid encoding. +/// +/// The opposite operation is `uri.query_to_string`. +/// +/// ## Examples +/// +/// ```gleam +/// > parse_query("a=1&b=2") +/// Ok([#("a", "1"), #("b", "2")]) +/// ``` +/// +pub fn parse_query(query: String) -> Result(List(#(String, String)), Nil) { + do_parse_query(query) +} + +@external(erlang, "gleam_stdlib", "parse_query") +@external(javascript, "../gleam_stdlib.mjs", "parse_query") +fn do_parse_query(a: String) -> Result(List(#(String, String)), Nil) + +/// Encodes a list of key value pairs as a URI query string. +/// +/// The opposite operation is `uri.parse_query`. +/// +/// ## Examples +/// +/// ```gleam +/// > query_to_string([#("a", "1"), #("b", "2")]) +/// "a=1&b=2" +/// ``` +/// +pub fn query_to_string(query: List(#(String, String))) -> String { + query + |> list.map(query_pair) + |> list.intersperse(string_builder.from_string("&")) + |> string_builder.concat + |> string_builder.to_string +} + +fn query_pair(pair: #(String, String)) -> StringBuilder { + string_builder.from_strings([ + percent_encode(pair.0), + "=", + percent_encode(pair.1), + ]) +} + +/// Encodes a string into a percent encoded representation. +/// +/// ## Examples +/// +/// ```gleam +/// > percent_encode("100% great") +/// "100%25%20great" +/// ``` +/// +pub fn percent_encode(value: String) -> String { + do_percent_encode(value) +} + +@external(erlang, "gleam_stdlib", "percent_encode") +@external(javascript, "../gleam_stdlib.mjs", "percent_encode") +fn do_percent_encode(a: String) -> String + +/// Decodes a percent encoded string. +/// +/// ## Examples +/// +/// ```gleam +/// > percent_decode("100%25+great") +/// Ok("100% great") +/// ``` +/// +pub fn percent_decode(value: String) -> Result(String, Nil) { + do_percent_decode(value) +} + +@external(erlang, "gleam_stdlib", "percent_decode") +@external(javascript, "../gleam_stdlib.mjs", "percent_decode") +fn do_percent_decode(a: String) -> Result(String, Nil) + +fn do_remove_dot_segments( + input: List(String), + accumulator: List(String), +) -> List(String) { + case input { + [] -> list.reverse(accumulator) + [segment, ..rest] -> { + let accumulator = case segment, accumulator { + "", accumulator -> accumulator + ".", accumulator -> accumulator + "..", [] -> [] + "..", [_, ..accumulator] -> accumulator + segment, accumulator -> [segment, ..accumulator] + } + do_remove_dot_segments(rest, accumulator) + } + } +} + +fn remove_dot_segments(input: List(String)) -> List(String) { + do_remove_dot_segments(input, []) +} + +/// Splits the path section of a URI into it's constituent segments. +/// +/// Removes empty segments and resolves dot-segments as specified in +/// [section 5.2](https://www.ietf.org/rfc/rfc3986.html#section-5.2) of the RFC. +/// +/// ## Examples +/// +/// ```gleam +/// > path_segments("/users/1") +/// ["users" ,"1"] +/// ``` +/// +pub fn path_segments(path: String) -> List(String) { + remove_dot_segments(string.split(path, "/")) +} + +/// Encodes a `Uri` value as a URI string. +/// +/// The opposite operation is `uri.parse`. +/// +/// ## Examples +/// +/// ```gleam +/// > let uri = Uri(Some("http"), None, Some("example.com"), ...) +/// > to_string(uri) +/// "http://example.com" +/// ``` +/// +pub fn to_string(uri: Uri) -> String { + let parts = case uri.fragment { + Some(fragment) -> ["#", fragment] + _ -> [] + } + let parts = case uri.query { + Some(query) -> ["?", query, ..parts] + _ -> parts + } + let parts = [uri.path, ..parts] + let parts = case uri.host, string.starts_with(uri.path, "/") { + Some(host), False if host != "" -> ["/", ..parts] + _, _ -> parts + } + let parts = case uri.host, uri.port { + Some(_), Some(port) -> [":", int.to_string(port), ..parts] + _, _ -> parts + } + let parts = case uri.scheme, uri.userinfo, uri.host { + Some(s), Some(u), Some(h) -> [s, "://", u, "@", h, ..parts] + Some(s), None, Some(h) -> [s, "://", h, ..parts] + Some(s), Some(_), None | Some(s), None, None -> [s, ":", ..parts] + None, None, Some(h) -> ["//", h, ..parts] + _, _, _ -> parts + } + string.concat(parts) +} + +/// Fetches the origin of a URI. +/// +/// Returns the origin of a uri as defined in +/// [RFC 6454](https://tools.ietf.org/html/rfc6454) +/// +/// The supported URI schemes are `http` and `https`. +/// URLs without a scheme will return `Error`. +/// +/// ## Examples +/// +/// ```gleam +/// > let assert Ok(uri) = parse("http://example.com/path?foo#bar") +/// > origin(uri) +/// Ok("http://example.com") +/// ``` +/// +pub fn origin(uri: Uri) -> Result(String, Nil) { + let Uri(scheme: scheme, host: host, port: port, ..) = uri + case scheme { + Some("https") if port == Some(443) -> { + let origin = Uri(scheme, None, host, None, "", None, None) + Ok(to_string(origin)) + } + Some("http") if port == Some(80) -> { + let origin = Uri(scheme, None, host, None, "", None, None) + Ok(to_string(origin)) + } + Some(s) if s == "http" || s == "https" -> { + let origin = Uri(scheme, None, host, port, "", None, None) + Ok(to_string(origin)) + } + _ -> Error(Nil) + } +} + +fn drop_last(elements: List(a)) -> List(a) { + list.take(from: elements, up_to: list.length(elements) - 1) +} + +fn join_segments(segments: List(String)) -> String { + string.join(["", ..segments], "/") +} + +/// Resolves a URI with respect to the given base URI. +/// +/// The base URI must be an absolute URI or this function will return an error. +/// The algorithm for merging uris is described in +/// [RFC 3986](https://tools.ietf.org/html/rfc3986#section-5.2). +/// +pub fn merge(base: Uri, relative: Uri) -> Result(Uri, Nil) { + case base { + Uri(scheme: Some(_), host: Some(_), ..) -> + case relative { + Uri(host: Some(_), ..) -> { + let path = + string.split(relative.path, "/") + |> remove_dot_segments() + |> join_segments() + let resolved = + Uri( + option.or(relative.scheme, base.scheme), + None, + relative.host, + option.or(relative.port, base.port), + path, + relative.query, + relative.fragment, + ) + Ok(resolved) + } + _ -> { + let #(new_path, new_query) = case relative.path { + "" -> #(base.path, option.or(relative.query, base.query)) + _ -> { + let path_segments = case string.starts_with(relative.path, "/") { + True -> string.split(relative.path, "/") + False -> + string.split(base.path, "/") + |> drop_last() + |> list.append(string.split(relative.path, "/")) + } + let path = + path_segments + |> remove_dot_segments() + |> join_segments() + #(path, relative.query) + } + } + let resolved = + Uri( + base.scheme, + None, + base.host, + base.port, + new_path, + new_query, + relative.fragment, + ) + Ok(resolved) + } + } + _ -> Error(Nil) + } +} diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@base.erl b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@base.erl new file mode 100644 index 0000000..d8d44c3 --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@base.erl @@ -0,0 +1,20 @@ +-module(gleam@base). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). + +-export([encode64/2, decode64/1, url_encode64/2, url_decode64/1]). + +-spec encode64(bitstring(), boolean()) -> binary(). +encode64(Input, Padding) -> + gleam@bit_array:base64_encode(Input, Padding). + +-spec decode64(binary()) -> {ok, bitstring()} | {error, nil}. +decode64(Encoded) -> + gleam@bit_array:base64_decode(Encoded). + +-spec url_encode64(bitstring(), boolean()) -> binary(). +url_encode64(Input, Padding) -> + gleam@bit_array:base64_url_encode(Input, Padding). + +-spec url_decode64(binary()) -> {ok, bitstring()} | {error, nil}. +url_decode64(Encoded) -> + gleam@bit_array:base64_url_decode(Encoded). diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@bit_array.erl b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@bit_array.erl new file mode 100644 index 0000000..ae8e5ff --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@bit_array.erl @@ -0,0 +1,102 @@ +-module(gleam@bit_array). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). + +-export([from_string/1, byte_size/1, slice/3, is_utf8/1, to_string/1, concat/1, append/2, base64_encode/2, base64_decode/1, base64_url_encode/2, base64_url_decode/1, base16_encode/1, base16_decode/1]). + +-spec from_string(binary()) -> bitstring(). +from_string(X) -> + gleam_stdlib:identity(X). + +-spec byte_size(bitstring()) -> integer(). +byte_size(X) -> + erlang:byte_size(X). + +-spec slice(bitstring(), integer(), integer()) -> {ok, bitstring()} | + {error, nil}. +slice(String, Position, Length) -> + gleam_stdlib:bit_array_slice(String, Position, Length). + +-spec do_is_utf8(bitstring()) -> boolean(). +do_is_utf8(Bits) -> + case Bits of + <<>> -> + true; + + <<_/utf8, Rest/binary>> -> + do_is_utf8(Rest); + + _ -> + false + end. + +-spec is_utf8(bitstring()) -> boolean(). +is_utf8(Bits) -> + do_is_utf8(Bits). + +-spec do_to_string(bitstring()) -> {ok, binary()} | {error, nil}. +do_to_string(Bits) -> + case is_utf8(Bits) of + true -> + {ok, gleam_stdlib:identity(Bits)}; + + false -> + {error, nil} + end. + +-spec to_string(bitstring()) -> {ok, binary()} | {error, nil}. +to_string(Bits) -> + do_to_string(Bits). + +-spec concat(list(bitstring())) -> bitstring(). +concat(Bit_arrays) -> + gleam_stdlib:bit_array_concat(Bit_arrays). + +-spec append(bitstring(), bitstring()) -> bitstring(). +append(First, Second) -> + gleam_stdlib:bit_array_concat([First, Second]). + +-spec base64_encode(bitstring(), boolean()) -> binary(). +base64_encode(Input, Padding) -> + Encoded = base64:encode(Input), + case Padding of + true -> + Encoded; + + false -> + gleam@string:replace(Encoded, <<"="/utf8>>, <<""/utf8>>) + end. + +-spec base64_decode(binary()) -> {ok, bitstring()} | {error, nil}. +base64_decode(Encoded) -> + Padded = case erlang:byte_size(gleam_stdlib:identity(Encoded)) rem 4 of + 0 -> + Encoded; + + N -> + gleam@string:append( + Encoded, + gleam@string:repeat(<<"="/utf8>>, 4 - N) + ) + end, + gleam_stdlib:base_decode64(Padded). + +-spec base64_url_encode(bitstring(), boolean()) -> binary(). +base64_url_encode(Input, Padding) -> + _pipe = base64_encode(Input, Padding), + _pipe@1 = gleam@string:replace(_pipe, <<"+"/utf8>>, <<"-"/utf8>>), + gleam@string:replace(_pipe@1, <<"/"/utf8>>, <<"_"/utf8>>). + +-spec base64_url_decode(binary()) -> {ok, bitstring()} | {error, nil}. +base64_url_decode(Encoded) -> + _pipe = Encoded, + _pipe@1 = gleam@string:replace(_pipe, <<"-"/utf8>>, <<"+"/utf8>>), + _pipe@2 = gleam@string:replace(_pipe@1, <<"_"/utf8>>, <<"/"/utf8>>), + base64_decode(_pipe@2). + +-spec base16_encode(bitstring()) -> binary(). +base16_encode(Input) -> + binary:encode_hex(Input). + +-spec base16_decode(binary()) -> {ok, bitstring()} | {error, nil}. +base16_decode(Input) -> + gleam_stdlib:base16_decode(Input). diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@bit_builder.erl b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@bit_builder.erl new file mode 100644 index 0000000..648605f --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@bit_builder.erl @@ -0,0 +1,66 @@ +-module(gleam@bit_builder). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). + +-export([new/0, prepend/2, append/2, prepend_builder/2, append_builder/2, prepend_string/2, append_string/2, concat/1, concat_bit_strings/1, from_string/1, from_string_builder/1, from_bit_string/1, to_bit_string/1, byte_size/1]). + +-spec new() -> gleam@bytes_builder:bytes_builder(). +new() -> + gleam@bytes_builder:new(). + +-spec prepend(gleam@bytes_builder:bytes_builder(), bitstring()) -> gleam@bytes_builder:bytes_builder(). +prepend(To, Prefix) -> + gleam@bytes_builder:prepend(To, Prefix). + +-spec append(gleam@bytes_builder:bytes_builder(), bitstring()) -> gleam@bytes_builder:bytes_builder(). +append(To, Suffix) -> + gleam@bytes_builder:append(To, Suffix). + +-spec prepend_builder( + gleam@bytes_builder:bytes_builder(), + gleam@bytes_builder:bytes_builder() +) -> gleam@bytes_builder:bytes_builder(). +prepend_builder(To, Prefix) -> + gleam@bytes_builder:prepend_builder(To, Prefix). + +-spec append_builder( + gleam@bytes_builder:bytes_builder(), + gleam@bytes_builder:bytes_builder() +) -> gleam@bytes_builder:bytes_builder(). +append_builder(First, Second) -> + gleam_stdlib:iodata_append(First, Second). + +-spec prepend_string(gleam@bytes_builder:bytes_builder(), binary()) -> gleam@bytes_builder:bytes_builder(). +prepend_string(To, Prefix) -> + gleam@bytes_builder:prepend_string(To, Prefix). + +-spec append_string(gleam@bytes_builder:bytes_builder(), binary()) -> gleam@bytes_builder:bytes_builder(). +append_string(To, Suffix) -> + gleam@bytes_builder:append_string(To, Suffix). + +-spec concat(list(gleam@bytes_builder:bytes_builder())) -> gleam@bytes_builder:bytes_builder(). +concat(Builders) -> + gleam_stdlib:identity(Builders). + +-spec concat_bit_strings(list(bitstring())) -> gleam@bytes_builder:bytes_builder(). +concat_bit_strings(Bits) -> + gleam_stdlib:identity(Bits). + +-spec from_string(binary()) -> gleam@bytes_builder:bytes_builder(). +from_string(String) -> + gleam_stdlib:wrap_list(String). + +-spec from_string_builder(gleam@string_builder:string_builder()) -> gleam@bytes_builder:bytes_builder(). +from_string_builder(Builder) -> + gleam_stdlib:wrap_list(Builder). + +-spec from_bit_string(bitstring()) -> gleam@bytes_builder:bytes_builder(). +from_bit_string(Bits) -> + gleam_stdlib:wrap_list(Bits). + +-spec to_bit_string(gleam@bytes_builder:bytes_builder()) -> bitstring(). +to_bit_string(Builder) -> + erlang:list_to_bitstring(Builder). + +-spec byte_size(gleam@bytes_builder:bytes_builder()) -> integer(). +byte_size(Builder) -> + erlang:iolist_size(Builder). diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@bit_string.erl b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@bit_string.erl new file mode 100644 index 0000000..d1eea04 --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@bit_string.erl @@ -0,0 +1,33 @@ +-module(gleam@bit_string). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). + +-export([from_string/1, byte_size/1, append/2, slice/3, is_utf8/1, to_string/1, concat/1]). + +-spec from_string(binary()) -> bitstring(). +from_string(X) -> + gleam_stdlib:identity(X). + +-spec byte_size(bitstring()) -> integer(). +byte_size(X) -> + erlang:byte_size(X). + +-spec append(bitstring(), bitstring()) -> bitstring(). +append(First, Second) -> + gleam@bit_array:append(First, Second). + +-spec slice(bitstring(), integer(), integer()) -> {ok, bitstring()} | + {error, nil}. +slice(String, Position, Length) -> + gleam_stdlib:bit_array_slice(String, Position, Length). + +-spec is_utf8(bitstring()) -> boolean(). +is_utf8(Bits) -> + gleam@bit_array:is_utf8(Bits). + +-spec to_string(bitstring()) -> {ok, binary()} | {error, nil}. +to_string(Bits) -> + gleam@bit_array:to_string(Bits). + +-spec concat(list(bitstring())) -> bitstring(). +concat(Bit_strings) -> + gleam_stdlib:bit_array_concat(Bit_strings). diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@bool.erl b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@bool.erl new file mode 100644 index 0000000..4881f2b --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@bool.erl @@ -0,0 +1,162 @@ +-module(gleam@bool). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). + +-export(['and'/2, 'or'/2, negate/1, nor/2, nand/2, exclusive_or/2, exclusive_nor/2, compare/2, max/2, min/2, to_int/1, to_string/1, guard/3, lazy_guard/3]). + +-spec 'and'(boolean(), boolean()) -> boolean(). +'and'(A, B) -> + A andalso B. + +-spec 'or'(boolean(), boolean()) -> boolean(). +'or'(A, B) -> + A orelse B. + +-spec negate(boolean()) -> boolean(). +negate(Bool) -> + case Bool of + true -> + false; + + false -> + true + end. + +-spec nor(boolean(), boolean()) -> boolean(). +nor(A, B) -> + case {A, B} of + {false, false} -> + true; + + {false, true} -> + false; + + {true, false} -> + false; + + {true, true} -> + false + end. + +-spec nand(boolean(), boolean()) -> boolean(). +nand(A, B) -> + case {A, B} of + {false, false} -> + true; + + {false, true} -> + true; + + {true, false} -> + true; + + {true, true} -> + false + end. + +-spec exclusive_or(boolean(), boolean()) -> boolean(). +exclusive_or(A, B) -> + case {A, B} of + {false, false} -> + false; + + {false, true} -> + true; + + {true, false} -> + true; + + {true, true} -> + false + end. + +-spec exclusive_nor(boolean(), boolean()) -> boolean(). +exclusive_nor(A, B) -> + case {A, B} of + {false, false} -> + true; + + {false, true} -> + false; + + {true, false} -> + false; + + {true, true} -> + true + end. + +-spec compare(boolean(), boolean()) -> gleam@order:order(). +compare(A, B) -> + case {A, B} of + {true, true} -> + eq; + + {true, false} -> + gt; + + {false, false} -> + eq; + + {false, true} -> + lt + end. + +-spec max(boolean(), boolean()) -> boolean(). +max(A, B) -> + case A of + true -> + true; + + false -> + B + end. + +-spec min(boolean(), boolean()) -> boolean(). +min(A, B) -> + case A of + false -> + false; + + true -> + B + end. + +-spec to_int(boolean()) -> integer(). +to_int(Bool) -> + case Bool of + false -> + 0; + + true -> + 1 + end. + +-spec to_string(boolean()) -> binary(). +to_string(Bool) -> + case Bool of + false -> + <<"False"/utf8>>; + + true -> + <<"True"/utf8>> + end. + +-spec guard(boolean(), DCG, fun(() -> DCG)) -> DCG. +guard(Requirement, Consequence, Alternative) -> + case Requirement of + true -> + Consequence; + + false -> + Alternative() + end. + +-spec lazy_guard(boolean(), fun(() -> DCH), fun(() -> DCH)) -> DCH. +lazy_guard(Requirement, Consequence, Alternative) -> + case Requirement of + true -> + Consequence(); + + false -> + Alternative() + end. diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@bytes_builder.erl b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@bytes_builder.erl new file mode 100644 index 0000000..61851fc --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@bytes_builder.erl @@ -0,0 +1,87 @@ +-module(gleam@bytes_builder). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). + +-export([append_builder/2, prepend_builder/2, concat/1, new/0, from_string/1, prepend_string/2, append_string/2, from_string_builder/1, from_bit_array/1, prepend/2, append/2, concat_bit_arrays/1, to_bit_array/1, byte_size/1]). +-export_type([bytes_builder/0]). + +-opaque bytes_builder() :: {bytes, bitstring()} | + {text, gleam@string_builder:string_builder()} | + {many, list(bytes_builder())}. + +-spec append_builder(bytes_builder(), bytes_builder()) -> bytes_builder(). +append_builder(First, Second) -> + gleam_stdlib:iodata_append(First, Second). + +-spec prepend_builder(bytes_builder(), bytes_builder()) -> bytes_builder(). +prepend_builder(Second, First) -> + gleam_stdlib:iodata_append(First, Second). + +-spec concat(list(bytes_builder())) -> bytes_builder(). +concat(Builders) -> + gleam_stdlib:identity(Builders). + +-spec new() -> bytes_builder(). +new() -> + gleam_stdlib:identity([]). + +-spec from_string(binary()) -> bytes_builder(). +from_string(String) -> + gleam_stdlib:wrap_list(String). + +-spec prepend_string(bytes_builder(), binary()) -> bytes_builder(). +prepend_string(Second, First) -> + gleam_stdlib:iodata_append(gleam_stdlib:wrap_list(First), Second). + +-spec append_string(bytes_builder(), binary()) -> bytes_builder(). +append_string(First, Second) -> + gleam_stdlib:iodata_append(First, gleam_stdlib:wrap_list(Second)). + +-spec from_string_builder(gleam@string_builder:string_builder()) -> bytes_builder(). +from_string_builder(Builder) -> + gleam_stdlib:wrap_list(Builder). + +-spec from_bit_array(bitstring()) -> bytes_builder(). +from_bit_array(Bits) -> + gleam_stdlib:wrap_list(Bits). + +-spec prepend(bytes_builder(), bitstring()) -> bytes_builder(). +prepend(Second, First) -> + gleam_stdlib:iodata_append(gleam_stdlib:wrap_list(First), Second). + +-spec append(bytes_builder(), bitstring()) -> bytes_builder(). +append(First, Second) -> + gleam_stdlib:iodata_append(First, gleam_stdlib:wrap_list(Second)). + +-spec concat_bit_arrays(list(bitstring())) -> bytes_builder(). +concat_bit_arrays(Bits) -> + gleam_stdlib:identity(Bits). + +-spec to_list(list(list(bytes_builder())), list(bitstring())) -> list(bitstring()). +to_list(Stack, Acc) -> + case Stack of + [] -> + Acc; + + [[] | Remaining_stack] -> + to_list(Remaining_stack, Acc); + + [[{bytes, Bits} | Rest] | Remaining_stack@1] -> + to_list([Rest | Remaining_stack@1], [Bits | Acc]); + + [[{text, Builder} | Rest@1] | Remaining_stack@2] -> + Bits@1 = gleam_stdlib:identity( + gleam@string_builder:to_string(Builder) + ), + to_list([Rest@1 | Remaining_stack@2], [Bits@1 | Acc]); + + [[{many, Builders} | Rest@2] | Remaining_stack@3] -> + to_list([Builders, Rest@2 | Remaining_stack@3], Acc) + end. + +-spec to_bit_array(bytes_builder()) -> bitstring(). +to_bit_array(Builder) -> + erlang:list_to_bitstring(Builder). + +-spec byte_size(bytes_builder()) -> integer(). +byte_size(Builder) -> + erlang:iolist_size(Builder). diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@dict.erl b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@dict.erl new file mode 100644 index 0000000..250f5b8 --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@dict.erl @@ -0,0 +1,171 @@ +-module(gleam@dict). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). + +-export([size/1, to_list/1, new/0, get/2, has_key/2, insert/3, from_list/1, keys/1, values/1, take/2, merge/2, delete/2, drop/2, update/3, fold/3, map_values/2, filter/2]). +-export_type([dict/2]). + +-type dict(KS, KT) :: any() | {gleam_phantom, KS, KT}. + +-spec size(dict(any(), any())) -> integer(). +size(Dict) -> + maps:size(Dict). + +-spec to_list(dict(KY, KZ)) -> list({KY, KZ}). +to_list(Dict) -> + maps:to_list(Dict). + +-spec new() -> dict(any(), any()). +new() -> + maps:new(). + +-spec get(dict(MF, MG), MF) -> {ok, MG} | {error, nil}. +get(From, Get) -> + gleam_stdlib:map_get(From, Get). + +-spec has_key(dict(LP, any()), LP) -> boolean(). +has_key(Dict, Key) -> + maps:is_key(Key, Dict). + +-spec insert(dict(MR, MS), MR, MS) -> dict(MR, MS). +insert(Dict, Key, Value) -> + maps:put(Key, Value, Dict). + +-spec fold_list_of_pair(list({LI, LJ}), dict(LI, LJ)) -> dict(LI, LJ). +fold_list_of_pair(List, Initial) -> + case List of + [] -> + Initial; + + [X | Rest] -> + fold_list_of_pair( + Rest, + insert(Initial, erlang:element(1, X), erlang:element(2, X)) + ) + end. + +-spec from_list(list({LD, LE})) -> dict(LD, LE). +from_list(List) -> + maps:from_list(List). + +-spec reverse_and_concat(list(TS), list(TS)) -> list(TS). +reverse_and_concat(Remaining, Accumulator) -> + case Remaining of + [] -> + Accumulator; + + [Item | Rest] -> + reverse_and_concat(Rest, [Item | Accumulator]) + end. + +-spec do_keys_acc(list({OE, any()}), list(OE)) -> list(OE). +do_keys_acc(List, Acc) -> + case List of + [] -> + reverse_and_concat(Acc, []); + + [X | Xs] -> + do_keys_acc(Xs, [erlang:element(1, X) | Acc]) + end. + +-spec keys(dict(NR, any())) -> list(NR). +keys(Dict) -> + maps:keys(Dict). + +-spec do_values_acc(list({any(), OU}), list(OU)) -> list(OU). +do_values_acc(List, Acc) -> + case List of + [] -> + reverse_and_concat(Acc, []); + + [X | Xs] -> + do_values_acc(Xs, [erlang:element(2, X) | Acc]) + end. + +-spec values(dict(any(), OK)) -> list(OK). +values(Dict) -> + maps:values(Dict). + +-spec insert_taken(dict(PY, PZ), list(PY), dict(PY, PZ)) -> dict(PY, PZ). +insert_taken(Dict, Desired_keys, Acc) -> + Insert = fun(Taken, Key) -> case get(Dict, Key) of + {ok, Value} -> + insert(Taken, Key, Value); + + _ -> + Taken + end end, + case Desired_keys of + [] -> + Acc; + + [X | Xs] -> + insert_taken(Dict, Xs, Insert(Acc, X)) + end. + +-spec take(dict(PK, PL), list(PK)) -> dict(PK, PL). +take(Dict, Desired_keys) -> + maps:with(Desired_keys, Dict). + +-spec insert_pair(dict(QX, QY), {QX, QY}) -> dict(QX, QY). +insert_pair(Dict, Pair) -> + insert(Dict, erlang:element(1, Pair), erlang:element(2, Pair)). + +-spec fold_inserts(list({RD, RE}), dict(RD, RE)) -> dict(RD, RE). +fold_inserts(New_entries, Dict) -> + case New_entries of + [] -> + Dict; + + [X | Xs] -> + fold_inserts(Xs, insert_pair(Dict, X)) + end. + +-spec merge(dict(QH, QI), dict(QH, QI)) -> dict(QH, QI). +merge(Dict, New_entries) -> + maps:merge(Dict, New_entries). + +-spec delete(dict(RK, RL), RK) -> dict(RK, RL). +delete(Dict, Key) -> + maps:remove(Key, Dict). + +-spec drop(dict(RW, RX), list(RW)) -> dict(RW, RX). +drop(Dict, Disallowed_keys) -> + case Disallowed_keys of + [] -> + Dict; + + [X | Xs] -> + drop(delete(Dict, X), Xs) + end. + +-spec update(dict(SD, SE), SD, fun((gleam@option:option(SE)) -> SE)) -> dict(SD, SE). +update(Dict, Key, Fun) -> + _pipe = Dict, + _pipe@1 = get(_pipe, Key), + _pipe@2 = gleam@option:from_result(_pipe@1), + _pipe@3 = Fun(_pipe@2), + insert(Dict, Key, _pipe@3). + +-spec do_fold(list({SK, SL}), SN, fun((SN, SK, SL) -> SN)) -> SN. +do_fold(List, Initial, Fun) -> + case List of + [] -> + Initial; + + [{K, V} | Rest] -> + do_fold(Rest, Fun(Initial, K, V), Fun) + end. + +-spec fold(dict(SO, SP), SS, fun((SS, SO, SP) -> SS)) -> SS. +fold(Dict, Initial, Fun) -> + _pipe = Dict, + _pipe@1 = maps:to_list(_pipe), + do_fold(_pipe@1, Initial, Fun). + +-spec map_values(dict(ND, NE), fun((ND, NE) -> NH)) -> dict(ND, NH). +map_values(Dict, Fun) -> + maps:map(Fun, Dict). + +-spec filter(dict(OY, OZ), fun((OY, OZ) -> boolean())) -> dict(OY, OZ). +filter(Dict, Predicate) -> + maps:filter(Predicate, Dict). diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@dynamic.erl b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@dynamic.erl new file mode 100644 index 0000000..f7ebb42 --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@dynamic.erl @@ -0,0 +1,808 @@ +-module(gleam@dynamic). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). + +-export([from/1, unsafe_coerce/1, dynamic/1, bit_array/1, bit_string/1, classify/1, int/1, float/1, bool/1, shallow_list/1, optional/1, any/1, decode1/2, result/2, list/1, string/1, field/2, optional_field/2, element/2, tuple2/2, tuple3/3, tuple4/4, tuple5/5, tuple6/6, dict/2, map/2, decode2/3, decode3/4, decode4/5, decode5/6, decode6/7, decode7/8, decode8/9, decode9/10]). +-export_type([dynamic_/0, decode_error/0, unknown_tuple/0]). + +-type dynamic_() :: any(). + +-type decode_error() :: {decode_error, binary(), binary(), list(binary())}. + +-type unknown_tuple() :: any(). + +-spec from(any()) -> dynamic_(). +from(A) -> + gleam_stdlib:identity(A). + +-spec unsafe_coerce(dynamic_()) -> any(). +unsafe_coerce(A) -> + gleam_stdlib:identity(A). + +-spec dynamic(dynamic_()) -> {ok, dynamic_()} | {error, list(decode_error())}. +dynamic(Value) -> + {ok, Value}. + +-spec bit_array(dynamic_()) -> {ok, bitstring()} | {error, list(decode_error())}. +bit_array(Data) -> + gleam_stdlib:decode_bit_array(Data). + +-spec bit_string(dynamic_()) -> {ok, bitstring()} | + {error, list(decode_error())}. +bit_string(Data) -> + bit_array(Data). + +-spec put_expected(decode_error(), binary()) -> decode_error(). +put_expected(Error, Expected) -> + erlang:setelement(2, Error, Expected). + +-spec classify(dynamic_()) -> binary(). +classify(Data) -> + gleam_stdlib:classify_dynamic(Data). + +-spec int(dynamic_()) -> {ok, integer()} | {error, list(decode_error())}. +int(Data) -> + gleam_stdlib:decode_int(Data). + +-spec float(dynamic_()) -> {ok, float()} | {error, list(decode_error())}. +float(Data) -> + gleam_stdlib:decode_float(Data). + +-spec bool(dynamic_()) -> {ok, boolean()} | {error, list(decode_error())}. +bool(Data) -> + gleam_stdlib:decode_bool(Data). + +-spec shallow_list(dynamic_()) -> {ok, list(dynamic_())} | + {error, list(decode_error())}. +shallow_list(Value) -> + gleam_stdlib:decode_list(Value). + +-spec optional(fun((dynamic_()) -> {ok, DPG} | {error, list(decode_error())})) -> fun((dynamic_()) -> {ok, + gleam@option:option(DPG)} | + {error, list(decode_error())}). +optional(Decode) -> + fun(Value) -> gleam_stdlib:decode_option(Value, Decode) end. + +-spec at_least_decode_tuple_error(integer(), dynamic_()) -> {ok, any()} | + {error, list(decode_error())}. +at_least_decode_tuple_error(Size, Data) -> + S = case Size of + 1 -> + <<""/utf8>>; + + _ -> + <<"s"/utf8>> + end, + Error = begin + _pipe = [<<"Tuple of at least "/utf8>>, + gleam@int:to_string(Size), + <<" element"/utf8>>, + S], + _pipe@1 = gleam@string_builder:from_strings(_pipe), + _pipe@2 = gleam@string_builder:to_string(_pipe@1), + {decode_error, _pipe@2, classify(Data), []} + end, + {error, [Error]}. + +-spec any(list(fun((dynamic_()) -> {ok, DTN} | {error, list(decode_error())}))) -> fun((dynamic_()) -> {ok, + DTN} | + {error, list(decode_error())}). +any(Decoders) -> + fun(Data) -> case Decoders of + [] -> + {error, + [{decode_error, <<"another type"/utf8>>, classify(Data), []}]}; + + [Decoder | Decoders@1] -> + case Decoder(Data) of + {ok, Decoded} -> + {ok, Decoded}; + + {error, _} -> + (any(Decoders@1))(Data) + end + end end. + +-spec all_errors({ok, any()} | {error, list(decode_error())}) -> list(decode_error()). +all_errors(Result) -> + case Result of + {ok, _} -> + []; + + {error, Errors} -> + Errors + end. + +-spec decode1( + fun((DTR) -> DTS), + fun((dynamic_()) -> {ok, DTR} | {error, list(decode_error())}) +) -> fun((dynamic_()) -> {ok, DTS} | {error, list(decode_error())}). +decode1(Constructor, T1) -> + fun(Value) -> case T1(Value) of + {ok, A} -> + {ok, Constructor(A)}; + + A@1 -> + {error, all_errors(A@1)} + end end. + +-spec push_path(decode_error(), any()) -> decode_error(). +push_path(Error, Name) -> + Name@1 = from(Name), + Decoder = any( + [fun string/1, + fun(X) -> gleam@result:map(int(X), fun gleam@int:to_string/1) end] + ), + Name@3 = case Decoder(Name@1) of + {ok, Name@2} -> + Name@2; + + {error, _} -> + _pipe = [<<"<"/utf8>>, classify(Name@1), <<">"/utf8>>], + _pipe@1 = gleam@string_builder:from_strings(_pipe), + gleam@string_builder:to_string(_pipe@1) + end, + erlang:setelement(4, Error, [Name@3 | erlang:element(4, Error)]). + +-spec result( + fun((dynamic_()) -> {ok, DOU} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, DOW} | {error, list(decode_error())}) +) -> fun((dynamic_()) -> {ok, {ok, DOU} | {error, DOW}} | + {error, list(decode_error())}). +result(Decode_ok, Decode_error) -> + fun(Value) -> + gleam@result:'try'( + gleam_stdlib:decode_result(Value), + fun(Inner_result) -> case Inner_result of + {ok, Raw} -> + gleam@result:'try'( + begin + _pipe = Decode_ok(Raw), + map_errors( + _pipe, + fun(_capture) -> + push_path(_capture, <<"ok"/utf8>>) + end + ) + end, + fun(Value@1) -> {ok, {ok, Value@1}} end + ); + + {error, Raw@1} -> + gleam@result:'try'( + begin + _pipe@1 = Decode_error(Raw@1), + map_errors( + _pipe@1, + fun(_capture@1) -> + push_path(_capture@1, <<"error"/utf8>>) + end + ) + end, + fun(Value@2) -> {ok, {error, Value@2}} end + ) + end end + ) + end. + +-spec list(fun((dynamic_()) -> {ok, DPB} | {error, list(decode_error())})) -> fun((dynamic_()) -> {ok, + list(DPB)} | + {error, list(decode_error())}). +list(Decoder_type) -> + fun(Dynamic) -> + gleam@result:'try'(shallow_list(Dynamic), fun(List) -> _pipe = List, + _pipe@1 = gleam@list:try_map(_pipe, Decoder_type), + map_errors( + _pipe@1, + fun(_capture) -> push_path(_capture, <<"*"/utf8>>) end + ) end) + end. + +-spec map_errors( + {ok, DNP} | {error, list(decode_error())}, + fun((decode_error()) -> decode_error()) +) -> {ok, DNP} | {error, list(decode_error())}. +map_errors(Result, F) -> + gleam@result:map_error( + Result, + fun(_capture) -> gleam@list:map(_capture, F) end + ). + +-spec decode_string(dynamic_()) -> {ok, binary()} | + {error, list(decode_error())}. +decode_string(Data) -> + _pipe = bit_array(Data), + _pipe@1 = map_errors( + _pipe, + fun(_capture) -> put_expected(_capture, <<"String"/utf8>>) end + ), + gleam@result:'try'( + _pipe@1, + fun(Raw) -> case gleam@bit_array:to_string(Raw) of + {ok, String} -> + {ok, String}; + + {error, nil} -> + {error, + [{decode_error, + <<"String"/utf8>>, + <<"BitArray"/utf8>>, + []}]} + end end + ). + +-spec string(dynamic_()) -> {ok, binary()} | {error, list(decode_error())}. +string(Data) -> + decode_string(Data). + +-spec field( + any(), + fun((dynamic_()) -> {ok, DPQ} | {error, list(decode_error())}) +) -> fun((dynamic_()) -> {ok, DPQ} | {error, list(decode_error())}). +field(Name, Inner_type) -> + fun(Value) -> + Missing_field_error = {decode_error, + <<"field"/utf8>>, + <<"nothing"/utf8>>, + []}, + gleam@result:'try'( + gleam_stdlib:decode_field(Value, Name), + fun(Maybe_inner) -> _pipe = Maybe_inner, + _pipe@1 = gleam@option:to_result(_pipe, [Missing_field_error]), + _pipe@2 = gleam@result:'try'(_pipe@1, Inner_type), + map_errors( + _pipe@2, + fun(_capture) -> push_path(_capture, Name) end + ) end + ) + end. + +-spec optional_field( + any(), + fun((dynamic_()) -> {ok, DPU} | {error, list(decode_error())}) +) -> fun((dynamic_()) -> {ok, gleam@option:option(DPU)} | + {error, list(decode_error())}). +optional_field(Name, Inner_type) -> + fun(Value) -> + gleam@result:'try'( + gleam_stdlib:decode_field(Value, Name), + fun(Maybe_inner) -> case Maybe_inner of + none -> + {ok, none}; + + {some, Dynamic_inner} -> + _pipe = Dynamic_inner, + _pipe@1 = gleam_stdlib:decode_option(_pipe, Inner_type), + map_errors( + _pipe@1, + fun(_capture) -> push_path(_capture, Name) end + ) + end end + ) + end. + +-spec element( + integer(), + fun((dynamic_()) -> {ok, DQC} | {error, list(decode_error())}) +) -> fun((dynamic_()) -> {ok, DQC} | {error, list(decode_error())}). +element(Index, Inner_type) -> + fun(Data) -> + gleam@result:'try'( + gleam_stdlib:decode_tuple(Data), + fun(Tuple) -> + Size = gleam_stdlib:size_of_tuple(Tuple), + gleam@result:'try'(case Index >= 0 of + true -> + case Index < Size of + true -> + gleam_stdlib:tuple_get(Tuple, Index); + + false -> + at_least_decode_tuple_error(Index + 1, Data) + end; + + false -> + case gleam@int:absolute_value(Index) =< Size of + true -> + gleam_stdlib:tuple_get(Tuple, Size + Index); + + false -> + at_least_decode_tuple_error( + gleam@int:absolute_value(Index), + Data + ) + end + end, fun(Data@1) -> _pipe = Inner_type(Data@1), + map_errors( + _pipe, + fun(_capture) -> push_path(_capture, Index) end + ) end) + end + ) + end. + +-spec tuple_errors({ok, any()} | {error, list(decode_error())}, binary()) -> list(decode_error()). +tuple_errors(Result, Name) -> + case Result of + {ok, _} -> + []; + + {error, Errors} -> + gleam@list:map( + Errors, + fun(_capture) -> push_path(_capture, Name) end + ) + end. + +-spec tuple2( + fun((dynamic_()) -> {ok, DRC} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, DRE} | {error, list(decode_error())}) +) -> fun((dynamic_()) -> {ok, {DRC, DRE}} | {error, list(decode_error())}). +tuple2(Decode1, Decode2) -> + fun(Value) -> + gleam@result:'try'( + gleam_stdlib:decode_tuple2(Value), + fun(_use0) -> + {A, B} = _use0, + case {Decode1(A), Decode2(B)} of + {{ok, A@1}, {ok, B@1}} -> + {ok, {A@1, B@1}}; + + {A@2, B@2} -> + _pipe = tuple_errors(A@2, <<"0"/utf8>>), + _pipe@1 = gleam@list:append( + _pipe, + tuple_errors(B@2, <<"1"/utf8>>) + ), + {error, _pipe@1} + end + end + ) + end. + +-spec tuple3( + fun((dynamic_()) -> {ok, DRH} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, DRJ} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, DRL} | {error, list(decode_error())}) +) -> fun((dynamic_()) -> {ok, {DRH, DRJ, DRL}} | {error, list(decode_error())}). +tuple3(Decode1, Decode2, Decode3) -> + fun(Value) -> + gleam@result:'try'( + gleam_stdlib:decode_tuple3(Value), + fun(_use0) -> + {A, B, C} = _use0, + case {Decode1(A), Decode2(B), Decode3(C)} of + {{ok, A@1}, {ok, B@1}, {ok, C@1}} -> + {ok, {A@1, B@1, C@1}}; + + {A@2, B@2, C@2} -> + _pipe = tuple_errors(A@2, <<"0"/utf8>>), + _pipe@1 = gleam@list:append( + _pipe, + tuple_errors(B@2, <<"1"/utf8>>) + ), + _pipe@2 = gleam@list:append( + _pipe@1, + tuple_errors(C@2, <<"2"/utf8>>) + ), + {error, _pipe@2} + end + end + ) + end. + +-spec tuple4( + fun((dynamic_()) -> {ok, DRO} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, DRQ} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, DRS} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, DRU} | {error, list(decode_error())}) +) -> fun((dynamic_()) -> {ok, {DRO, DRQ, DRS, DRU}} | + {error, list(decode_error())}). +tuple4(Decode1, Decode2, Decode3, Decode4) -> + fun(Value) -> + gleam@result:'try'( + gleam_stdlib:decode_tuple4(Value), + fun(_use0) -> + {A, B, C, D} = _use0, + case {Decode1(A), Decode2(B), Decode3(C), Decode4(D)} of + {{ok, A@1}, {ok, B@1}, {ok, C@1}, {ok, D@1}} -> + {ok, {A@1, B@1, C@1, D@1}}; + + {A@2, B@2, C@2, D@2} -> + _pipe = tuple_errors(A@2, <<"0"/utf8>>), + _pipe@1 = gleam@list:append( + _pipe, + tuple_errors(B@2, <<"1"/utf8>>) + ), + _pipe@2 = gleam@list:append( + _pipe@1, + tuple_errors(C@2, <<"2"/utf8>>) + ), + _pipe@3 = gleam@list:append( + _pipe@2, + tuple_errors(D@2, <<"3"/utf8>>) + ), + {error, _pipe@3} + end + end + ) + end. + +-spec tuple5( + fun((dynamic_()) -> {ok, DRX} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, DRZ} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, DSB} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, DSD} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, DSF} | {error, list(decode_error())}) +) -> fun((dynamic_()) -> {ok, {DRX, DRZ, DSB, DSD, DSF}} | + {error, list(decode_error())}). +tuple5(Decode1, Decode2, Decode3, Decode4, Decode5) -> + fun(Value) -> + gleam@result:'try'( + gleam_stdlib:decode_tuple5(Value), + fun(_use0) -> + {A, B, C, D, E} = _use0, + case {Decode1(A), + Decode2(B), + Decode3(C), + Decode4(D), + Decode5(E)} of + {{ok, A@1}, {ok, B@1}, {ok, C@1}, {ok, D@1}, {ok, E@1}} -> + {ok, {A@1, B@1, C@1, D@1, E@1}}; + + {A@2, B@2, C@2, D@2, E@2} -> + _pipe = tuple_errors(A@2, <<"0"/utf8>>), + _pipe@1 = gleam@list:append( + _pipe, + tuple_errors(B@2, <<"1"/utf8>>) + ), + _pipe@2 = gleam@list:append( + _pipe@1, + tuple_errors(C@2, <<"2"/utf8>>) + ), + _pipe@3 = gleam@list:append( + _pipe@2, + tuple_errors(D@2, <<"3"/utf8>>) + ), + _pipe@4 = gleam@list:append( + _pipe@3, + tuple_errors(E@2, <<"4"/utf8>>) + ), + {error, _pipe@4} + end + end + ) + end. + +-spec tuple6( + fun((dynamic_()) -> {ok, DSI} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, DSK} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, DSM} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, DSO} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, DSQ} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, DSS} | {error, list(decode_error())}) +) -> fun((dynamic_()) -> {ok, {DSI, DSK, DSM, DSO, DSQ, DSS}} | + {error, list(decode_error())}). +tuple6(Decode1, Decode2, Decode3, Decode4, Decode5, Decode6) -> + fun(Value) -> + gleam@result:'try'( + gleam_stdlib:decode_tuple6(Value), + fun(_use0) -> + {A, B, C, D, E, F} = _use0, + case {Decode1(A), + Decode2(B), + Decode3(C), + Decode4(D), + Decode5(E), + Decode6(F)} of + {{ok, A@1}, + {ok, B@1}, + {ok, C@1}, + {ok, D@1}, + {ok, E@1}, + {ok, F@1}} -> + {ok, {A@1, B@1, C@1, D@1, E@1, F@1}}; + + {A@2, B@2, C@2, D@2, E@2, F@2} -> + _pipe = tuple_errors(A@2, <<"0"/utf8>>), + _pipe@1 = gleam@list:append( + _pipe, + tuple_errors(B@2, <<"1"/utf8>>) + ), + _pipe@2 = gleam@list:append( + _pipe@1, + tuple_errors(C@2, <<"2"/utf8>>) + ), + _pipe@3 = gleam@list:append( + _pipe@2, + tuple_errors(D@2, <<"3"/utf8>>) + ), + _pipe@4 = gleam@list:append( + _pipe@3, + tuple_errors(E@2, <<"4"/utf8>>) + ), + _pipe@5 = gleam@list:append( + _pipe@4, + tuple_errors(F@2, <<"5"/utf8>>) + ), + {error, _pipe@5} + end + end + ) + end. + +-spec dict( + fun((dynamic_()) -> {ok, DSV} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, DSX} | {error, list(decode_error())}) +) -> fun((dynamic_()) -> {ok, gleam@dict:dict(DSV, DSX)} | + {error, list(decode_error())}). +dict(Key_type, Value_type) -> + fun(Value) -> + gleam@result:'try'( + gleam_stdlib:decode_map(Value), + fun(Map) -> + gleam@result:'try'( + begin + _pipe = Map, + _pipe@1 = maps:to_list(_pipe), + gleam@list:try_map( + _pipe@1, + fun(Pair) -> + {K, V} = Pair, + gleam@result:'try'( + begin + _pipe@2 = Key_type(K), + map_errors( + _pipe@2, + fun(_capture) -> + push_path( + _capture, + <<"keys"/utf8>> + ) + end + ) + end, + fun(K@1) -> + gleam@result:'try'( + begin + _pipe@3 = Value_type(V), + map_errors( + _pipe@3, + fun(_capture@1) -> + push_path( + _capture@1, + <<"values"/utf8>> + ) + end + ) + end, + fun(V@1) -> {ok, {K@1, V@1}} end + ) + end + ) + end + ) + end, + fun(Pairs) -> {ok, maps:from_list(Pairs)} end + ) + end + ) + end. + +-spec map( + fun((dynamic_()) -> {ok, DTC} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, DTE} | {error, list(decode_error())}) +) -> fun((dynamic_()) -> {ok, gleam@dict:dict(DTC, DTE)} | + {error, list(decode_error())}). +map(Key_type, Value_type) -> + dict(Key_type, Value_type). + +-spec decode2( + fun((DTV, DTW) -> DTX), + fun((dynamic_()) -> {ok, DTV} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, DTW} | {error, list(decode_error())}) +) -> fun((dynamic_()) -> {ok, DTX} | {error, list(decode_error())}). +decode2(Constructor, T1, T2) -> + fun(Value) -> case {T1(Value), T2(Value)} of + {{ok, A}, {ok, B}} -> + {ok, Constructor(A, B)}; + + {A@1, B@1} -> + {error, gleam@list:concat([all_errors(A@1), all_errors(B@1)])} + end end. + +-spec decode3( + fun((DUB, DUC, DUD) -> DUE), + fun((dynamic_()) -> {ok, DUB} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, DUC} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, DUD} | {error, list(decode_error())}) +) -> fun((dynamic_()) -> {ok, DUE} | {error, list(decode_error())}). +decode3(Constructor, T1, T2, T3) -> + fun(Value) -> case {T1(Value), T2(Value), T3(Value)} of + {{ok, A}, {ok, B}, {ok, C}} -> + {ok, Constructor(A, B, C)}; + + {A@1, B@1, C@1} -> + {error, + gleam@list:concat( + [all_errors(A@1), all_errors(B@1), all_errors(C@1)] + )} + end end. + +-spec decode4( + fun((DUJ, DUK, DUL, DUM) -> DUN), + fun((dynamic_()) -> {ok, DUJ} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, DUK} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, DUL} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, DUM} | {error, list(decode_error())}) +) -> fun((dynamic_()) -> {ok, DUN} | {error, list(decode_error())}). +decode4(Constructor, T1, T2, T3, T4) -> + fun(X) -> case {T1(X), T2(X), T3(X), T4(X)} of + {{ok, A}, {ok, B}, {ok, C}, {ok, D}} -> + {ok, Constructor(A, B, C, D)}; + + {A@1, B@1, C@1, D@1} -> + {error, + gleam@list:concat( + [all_errors(A@1), + all_errors(B@1), + all_errors(C@1), + all_errors(D@1)] + )} + end end. + +-spec decode5( + fun((DUT, DUU, DUV, DUW, DUX) -> DUY), + fun((dynamic_()) -> {ok, DUT} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, DUU} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, DUV} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, DUW} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, DUX} | {error, list(decode_error())}) +) -> fun((dynamic_()) -> {ok, DUY} | {error, list(decode_error())}). +decode5(Constructor, T1, T2, T3, T4, T5) -> + fun(X) -> case {T1(X), T2(X), T3(X), T4(X), T5(X)} of + {{ok, A}, {ok, B}, {ok, C}, {ok, D}, {ok, E}} -> + {ok, Constructor(A, B, C, D, E)}; + + {A@1, B@1, C@1, D@1, E@1} -> + {error, + gleam@list:concat( + [all_errors(A@1), + all_errors(B@1), + all_errors(C@1), + all_errors(D@1), + all_errors(E@1)] + )} + end end. + +-spec decode6( + fun((DVF, DVG, DVH, DVI, DVJ, DVK) -> DVL), + fun((dynamic_()) -> {ok, DVF} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, DVG} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, DVH} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, DVI} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, DVJ} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, DVK} | {error, list(decode_error())}) +) -> fun((dynamic_()) -> {ok, DVL} | {error, list(decode_error())}). +decode6(Constructor, T1, T2, T3, T4, T5, T6) -> + fun(X) -> case {T1(X), T2(X), T3(X), T4(X), T5(X), T6(X)} of + {{ok, A}, {ok, B}, {ok, C}, {ok, D}, {ok, E}, {ok, F}} -> + {ok, Constructor(A, B, C, D, E, F)}; + + {A@1, B@1, C@1, D@1, E@1, F@1} -> + {error, + gleam@list:concat( + [all_errors(A@1), + all_errors(B@1), + all_errors(C@1), + all_errors(D@1), + all_errors(E@1), + all_errors(F@1)] + )} + end end. + +-spec decode7( + fun((DVT, DVU, DVV, DVW, DVX, DVY, DVZ) -> DWA), + fun((dynamic_()) -> {ok, DVT} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, DVU} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, DVV} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, DVW} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, DVX} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, DVY} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, DVZ} | {error, list(decode_error())}) +) -> fun((dynamic_()) -> {ok, DWA} | {error, list(decode_error())}). +decode7(Constructor, T1, T2, T3, T4, T5, T6, T7) -> + fun(X) -> case {T1(X), T2(X), T3(X), T4(X), T5(X), T6(X), T7(X)} of + {{ok, A}, {ok, B}, {ok, C}, {ok, D}, {ok, E}, {ok, F}, {ok, G}} -> + {ok, Constructor(A, B, C, D, E, F, G)}; + + {A@1, B@1, C@1, D@1, E@1, F@1, G@1} -> + {error, + gleam@list:concat( + [all_errors(A@1), + all_errors(B@1), + all_errors(C@1), + all_errors(D@1), + all_errors(E@1), + all_errors(F@1), + all_errors(G@1)] + )} + end end. + +-spec decode8( + fun((DWJ, DWK, DWL, DWM, DWN, DWO, DWP, DWQ) -> DWR), + fun((dynamic_()) -> {ok, DWJ} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, DWK} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, DWL} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, DWM} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, DWN} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, DWO} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, DWP} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, DWQ} | {error, list(decode_error())}) +) -> fun((dynamic_()) -> {ok, DWR} | {error, list(decode_error())}). +decode8(Constructor, T1, T2, T3, T4, T5, T6, T7, T8) -> + fun(X) -> case {T1(X), T2(X), T3(X), T4(X), T5(X), T6(X), T7(X), T8(X)} of + {{ok, A}, + {ok, B}, + {ok, C}, + {ok, D}, + {ok, E}, + {ok, F}, + {ok, G}, + {ok, H}} -> + {ok, Constructor(A, B, C, D, E, F, G, H)}; + + {A@1, B@1, C@1, D@1, E@1, F@1, G@1, H@1} -> + {error, + gleam@list:concat( + [all_errors(A@1), + all_errors(B@1), + all_errors(C@1), + all_errors(D@1), + all_errors(E@1), + all_errors(F@1), + all_errors(G@1), + all_errors(H@1)] + )} + end end. + +-spec decode9( + fun((DXB, DXC, DXD, DXE, DXF, DXG, DXH, DXI, DXJ) -> DXK), + fun((dynamic_()) -> {ok, DXB} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, DXC} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, DXD} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, DXE} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, DXF} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, DXG} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, DXH} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, DXI} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, DXJ} | {error, list(decode_error())}) +) -> fun((dynamic_()) -> {ok, DXK} | {error, list(decode_error())}). +decode9(Constructor, T1, T2, T3, T4, T5, T6, T7, T8, T9) -> + fun(X) -> + case {T1(X), T2(X), T3(X), T4(X), T5(X), T6(X), T7(X), T8(X), T9(X)} of + {{ok, A}, + {ok, B}, + {ok, C}, + {ok, D}, + {ok, E}, + {ok, F}, + {ok, G}, + {ok, H}, + {ok, I}} -> + {ok, Constructor(A, B, C, D, E, F, G, H, I)}; + + {A@1, B@1, C@1, D@1, E@1, F@1, G@1, H@1, I@1} -> + {error, + gleam@list:concat( + [all_errors(A@1), + all_errors(B@1), + all_errors(C@1), + all_errors(D@1), + all_errors(E@1), + all_errors(F@1), + all_errors(G@1), + all_errors(H@1), + all_errors(I@1)] + )} + end + end. diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@float.erl b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@float.erl new file mode 100644 index 0000000..b428ee8 --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@float.erl @@ -0,0 +1,181 @@ +-module(gleam@float). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). + +-export([parse/1, to_string/1, compare/2, min/2, max/2, clamp/3, ceiling/1, floor/1, round/1, truncate/1, absolute_value/1, loosely_compare/3, loosely_equals/3, power/2, square_root/1, negate/1, sum/1, product/1, random/0, divide/2, add/2, multiply/2, subtract/2]). + +-spec parse(binary()) -> {ok, float()} | {error, nil}. +parse(String) -> + gleam_stdlib:parse_float(String). + +-spec to_string(float()) -> binary(). +to_string(X) -> + gleam_stdlib:float_to_string(X). + +-spec compare(float(), float()) -> gleam@order:order(). +compare(A, B) -> + case A =:= B of + true -> + eq; + + false -> + case A < B of + true -> + lt; + + false -> + gt + end + end. + +-spec min(float(), float()) -> float(). +min(A, B) -> + case A < B of + true -> + A; + + false -> + B + end. + +-spec max(float(), float()) -> float(). +max(A, B) -> + case A > B of + true -> + A; + + false -> + B + end. + +-spec clamp(float(), float(), float()) -> float(). +clamp(X, Min_bound, Max_bound) -> + _pipe = X, + _pipe@1 = min(_pipe, Max_bound), + max(_pipe@1, Min_bound). + +-spec ceiling(float()) -> float(). +ceiling(X) -> + math:ceil(X). + +-spec floor(float()) -> float(). +floor(X) -> + math:floor(X). + +-spec round(float()) -> integer(). +round(X) -> + erlang:round(X). + +-spec truncate(float()) -> integer(). +truncate(X) -> + erlang:trunc(X). + +-spec absolute_value(float()) -> float(). +absolute_value(X) -> + case X >= +0.0 of + true -> + X; + + _ -> + +0.0 - X + end. + +-spec loosely_compare(float(), float(), float()) -> gleam@order:order(). +loosely_compare(A, B, Tolerance) -> + Difference = absolute_value(A - B), + case Difference =< Tolerance of + true -> + eq; + + false -> + compare(A, B) + end. + +-spec loosely_equals(float(), float(), float()) -> boolean(). +loosely_equals(A, B, Tolerance) -> + Difference = absolute_value(A - B), + Difference =< Tolerance. + +-spec power(float(), float()) -> {ok, float()} | {error, nil}. +power(Base, Exponent) -> + Fractional = (ceiling(Exponent) - Exponent) > +0.0, + case ((Base < +0.0) andalso Fractional) orelse ((Base =:= +0.0) andalso (Exponent + < +0.0)) of + true -> + {error, nil}; + + false -> + {ok, math:pow(Base, Exponent)} + end. + +-spec square_root(float()) -> {ok, float()} | {error, nil}. +square_root(X) -> + power(X, 0.5). + +-spec negate(float()) -> float(). +negate(X) -> + -1.0 * X. + +-spec do_sum(list(float()), float()) -> float(). +do_sum(Numbers, Initial) -> + case Numbers of + [] -> + Initial; + + [X | Rest] -> + do_sum(Rest, X + Initial) + end. + +-spec sum(list(float())) -> float(). +sum(Numbers) -> + _pipe = Numbers, + do_sum(_pipe, +0.0). + +-spec do_product(list(float()), float()) -> float(). +do_product(Numbers, Initial) -> + case Numbers of + [] -> + Initial; + + [X | Rest] -> + do_product(Rest, X * Initial) + end. + +-spec product(list(float())) -> float(). +product(Numbers) -> + case Numbers of + [] -> + 1.0; + + _ -> + do_product(Numbers, 1.0) + end. + +-spec random() -> float(). +random() -> + rand:uniform(). + +-spec divide(float(), float()) -> {ok, float()} | {error, nil}. +divide(A, B) -> + case B of + +0.0 -> + {error, nil}; + + B@1 -> + {ok, case B@1 of + +0.0 -> +0.0; + -0.0 -> -0.0; + Gleam@denominator -> A / Gleam@denominator + end} + end. + +-spec add(float(), float()) -> float(). +add(A, B) -> + A + B. + +-spec multiply(float(), float()) -> float(). +multiply(A, B) -> + A * B. + +-spec subtract(float(), float()) -> float(). +subtract(A, B) -> + A - B. diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@function.erl b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@function.erl new file mode 100644 index 0000000..ed29603 --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@function.erl @@ -0,0 +1,67 @@ +-module(gleam@function). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). + +-export([compose/2, curry2/1, curry3/1, curry4/1, curry5/1, curry6/1, flip/1, identity/1, constant/1, tap/2, apply1/2, apply2/3, apply3/4]). + +-spec compose(fun((DJZ) -> DKA), fun((DKA) -> DKB)) -> fun((DJZ) -> DKB). +compose(Fun1, Fun2) -> + fun(A) -> Fun2(Fun1(A)) end. + +-spec curry2(fun((DKC, DKD) -> DKE)) -> fun((DKC) -> fun((DKD) -> DKE)). +curry2(Fun) -> + fun(A) -> fun(B) -> Fun(A, B) end end. + +-spec curry3(fun((DKG, DKH, DKI) -> DKJ)) -> fun((DKG) -> fun((DKH) -> fun((DKI) -> DKJ))). +curry3(Fun) -> + fun(A) -> fun(B) -> fun(C) -> Fun(A, B, C) end end end. + +-spec curry4(fun((DKL, DKM, DKN, DKO) -> DKP)) -> fun((DKL) -> fun((DKM) -> fun((DKN) -> fun((DKO) -> DKP)))). +curry4(Fun) -> + fun(A) -> fun(B) -> fun(C) -> fun(D) -> Fun(A, B, C, D) end end end end. + +-spec curry5(fun((DKR, DKS, DKT, DKU, DKV) -> DKW)) -> fun((DKR) -> fun((DKS) -> fun((DKT) -> fun((DKU) -> fun((DKV) -> DKW))))). +curry5(Fun) -> + fun(A) -> + fun(B) -> + fun(C) -> fun(D) -> fun(E) -> Fun(A, B, C, D, E) end end end + end + end. + +-spec curry6(fun((DKY, DKZ, DLA, DLB, DLC, DLD) -> DLE)) -> fun((DKY) -> fun((DKZ) -> fun((DLA) -> fun((DLB) -> fun((DLC) -> fun((DLD) -> DLE)))))). +curry6(Fun) -> + fun(A) -> + fun(B) -> + fun(C) -> + fun(D) -> fun(E) -> fun(F) -> Fun(A, B, C, D, E, F) end end end + end + end + end. + +-spec flip(fun((DLG, DLH) -> DLI)) -> fun((DLH, DLG) -> DLI). +flip(Fun) -> + fun(B, A) -> Fun(A, B) end. + +-spec identity(DLJ) -> DLJ. +identity(X) -> + X. + +-spec constant(DLK) -> fun((any()) -> DLK). +constant(Value) -> + fun(_) -> Value end. + +-spec tap(DLM, fun((DLM) -> any())) -> DLM. +tap(Arg, Effect) -> + Effect(Arg), + Arg. + +-spec apply1(fun((DLO) -> DLP), DLO) -> DLP. +apply1(Fun, Arg1) -> + Fun(Arg1). + +-spec apply2(fun((DLQ, DLR) -> DLS), DLQ, DLR) -> DLS. +apply2(Fun, Arg1, Arg2) -> + Fun(Arg1, Arg2). + +-spec apply3(fun((DLT, DLU, DLV) -> DLW), DLT, DLU, DLV) -> DLW. +apply3(Fun, Arg1, Arg2, Arg3) -> + Fun(Arg1, Arg2, Arg3). diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@int.erl b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@int.erl new file mode 100644 index 0000000..7f4e576 --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@int.erl @@ -0,0 +1,332 @@ +-module(gleam@int). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). + +-export([absolute_value/1, parse/1, base_parse/2, to_string/1, to_base_string/2, to_base2/1, to_base8/1, to_base16/1, to_base36/1, to_float/1, power/2, square_root/1, compare/2, min/2, max/2, clamp/3, is_even/1, is_odd/1, negate/1, sum/1, product/1, digits/2, undigits/2, random/1, divide/2, remainder/2, modulo/2, floor_divide/2, add/2, multiply/2, subtract/2, bitwise_and/2, bitwise_not/1, bitwise_or/2, bitwise_exclusive_or/2, bitwise_shift_left/2, bitwise_shift_right/2]). +-export_type([invalid_base/0]). + +-type invalid_base() :: invalid_base. + +-spec absolute_value(integer()) -> integer(). +absolute_value(X) -> + case X >= 0 of + true -> + X; + + false -> + X * -1 + end. + +-spec parse(binary()) -> {ok, integer()} | {error, nil}. +parse(String) -> + gleam_stdlib:parse_int(String). + +-spec base_parse(binary(), integer()) -> {ok, integer()} | {error, nil}. +base_parse(String, Base) -> + case (Base >= 2) andalso (Base =< 36) of + true -> + gleam_stdlib:int_from_base_string(String, Base); + + false -> + {error, nil} + end. + +-spec to_string(integer()) -> binary(). +to_string(X) -> + erlang:integer_to_binary(X). + +-spec to_base_string(integer(), integer()) -> {ok, binary()} | + {error, invalid_base()}. +to_base_string(X, Base) -> + case (Base >= 2) andalso (Base =< 36) of + true -> + {ok, erlang:integer_to_binary(X, Base)}; + + false -> + {error, invalid_base} + end. + +-spec to_base2(integer()) -> binary(). +to_base2(X) -> + erlang:integer_to_binary(X, 2). + +-spec to_base8(integer()) -> binary(). +to_base8(X) -> + erlang:integer_to_binary(X, 8). + +-spec to_base16(integer()) -> binary(). +to_base16(X) -> + erlang:integer_to_binary(X, 16). + +-spec to_base36(integer()) -> binary(). +to_base36(X) -> + erlang:integer_to_binary(X, 36). + +-spec to_float(integer()) -> float(). +to_float(X) -> + erlang:float(X). + +-spec power(integer(), float()) -> {ok, float()} | {error, nil}. +power(Base, Exponent) -> + _pipe = Base, + _pipe@1 = to_float(_pipe), + gleam@float:power(_pipe@1, Exponent). + +-spec square_root(integer()) -> {ok, float()} | {error, nil}. +square_root(X) -> + _pipe = X, + _pipe@1 = to_float(_pipe), + gleam@float:square_root(_pipe@1). + +-spec compare(integer(), integer()) -> gleam@order:order(). +compare(A, B) -> + case A =:= B of + true -> + eq; + + false -> + case A < B of + true -> + lt; + + false -> + gt + end + end. + +-spec min(integer(), integer()) -> integer(). +min(A, B) -> + case A < B of + true -> + A; + + false -> + B + end. + +-spec max(integer(), integer()) -> integer(). +max(A, B) -> + case A > B of + true -> + A; + + false -> + B + end. + +-spec clamp(integer(), integer(), integer()) -> integer(). +clamp(X, Min_bound, Max_bound) -> + _pipe = X, + _pipe@1 = min(_pipe, Max_bound), + max(_pipe@1, Min_bound). + +-spec is_even(integer()) -> boolean(). +is_even(X) -> + (X rem 2) =:= 0. + +-spec is_odd(integer()) -> boolean(). +is_odd(X) -> + (X rem 2) /= 0. + +-spec negate(integer()) -> integer(). +negate(X) -> + -1 * X. + +-spec do_sum(list(integer()), integer()) -> integer(). +do_sum(Numbers, Initial) -> + case Numbers of + [] -> + Initial; + + [X | Rest] -> + do_sum(Rest, X + Initial) + end. + +-spec sum(list(integer())) -> integer(). +sum(Numbers) -> + _pipe = Numbers, + do_sum(_pipe, 0). + +-spec do_product(list(integer()), integer()) -> integer(). +do_product(Numbers, Initial) -> + case Numbers of + [] -> + Initial; + + [X | Rest] -> + do_product(Rest, X * Initial) + end. + +-spec product(list(integer())) -> integer(). +product(Numbers) -> + case Numbers of + [] -> + 1; + + _ -> + do_product(Numbers, 1) + end. + +-spec do_digits(integer(), integer(), list(integer())) -> list(integer()). +do_digits(X, Base, Acc) -> + case absolute_value(X) < Base of + true -> + [X | Acc]; + + false -> + do_digits(case Base of + 0 -> 0; + Gleam@denominator -> X div Gleam@denominator + end, Base, [case Base of + 0 -> 0; + Gleam@denominator@1 -> X rem Gleam@denominator@1 + end | Acc]) + end. + +-spec digits(integer(), integer()) -> {ok, list(integer())} | + {error, invalid_base()}. +digits(X, Base) -> + case Base < 2 of + true -> + {error, invalid_base}; + + false -> + {ok, do_digits(X, Base, [])} + end. + +-spec do_undigits(list(integer()), integer(), integer()) -> {ok, integer()} | + {error, invalid_base()}. +do_undigits(Numbers, Base, Acc) -> + case Numbers of + [] -> + {ok, Acc}; + + [Digit | _] when Digit >= Base -> + {error, invalid_base}; + + [Digit@1 | Rest] -> + do_undigits(Rest, Base, (Acc * Base) + Digit@1) + end. + +-spec undigits(list(integer()), integer()) -> {ok, integer()} | + {error, invalid_base()}. +undigits(Numbers, Base) -> + case Base < 2 of + true -> + {error, invalid_base}; + + false -> + do_undigits(Numbers, Base, 0) + end. + +-spec random(integer()) -> integer(). +random(Max) -> + _pipe = (rand:uniform() * to_float(Max)), + _pipe@1 = gleam@float:floor(_pipe), + gleam@float:round(_pipe@1). + +-spec divide(integer(), integer()) -> {ok, integer()} | {error, nil}. +divide(Dividend, Divisor) -> + case Divisor of + 0 -> + {error, nil}; + + Divisor@1 -> + {ok, case Divisor@1 of + 0 -> 0; + Gleam@denominator -> Dividend div Gleam@denominator + end} + end. + +-spec remainder(integer(), integer()) -> {ok, integer()} | {error, nil}. +remainder(Dividend, Divisor) -> + case Divisor of + 0 -> + {error, nil}; + + Divisor@1 -> + {ok, case Divisor@1 of + 0 -> 0; + Gleam@denominator -> Dividend rem Gleam@denominator + end} + end. + +-spec modulo(integer(), integer()) -> {ok, integer()} | {error, nil}. +modulo(Dividend, Divisor) -> + case Divisor of + 0 -> + {error, nil}; + + _ -> + Remainder = case Divisor of + 0 -> 0; + Gleam@denominator -> Dividend rem Gleam@denominator + end, + case (Remainder * Divisor) < 0 of + true -> + {ok, Remainder + Divisor}; + + false -> + {ok, Remainder} + end + end. + +-spec floor_divide(integer(), integer()) -> {ok, integer()} | {error, nil}. +floor_divide(Dividend, Divisor) -> + case Divisor of + 0 -> + {error, nil}; + + Divisor@1 -> + case ((Dividend * Divisor@1) < 0) andalso ((case Divisor@1 of + 0 -> 0; + Gleam@denominator -> Dividend rem Gleam@denominator + end) /= 0) of + true -> + {ok, (case Divisor@1 of + 0 -> 0; + Gleam@denominator@1 -> Dividend div Gleam@denominator@1 + end) - 1}; + + false -> + {ok, case Divisor@1 of + 0 -> 0; + Gleam@denominator@2 -> Dividend div Gleam@denominator@2 + end} + end + end. + +-spec add(integer(), integer()) -> integer(). +add(A, B) -> + A + B. + +-spec multiply(integer(), integer()) -> integer(). +multiply(A, B) -> + A * B. + +-spec subtract(integer(), integer()) -> integer(). +subtract(A, B) -> + A - B. + +-spec bitwise_and(integer(), integer()) -> integer(). +bitwise_and(X, Y) -> + erlang:'band'(X, Y). + +-spec bitwise_not(integer()) -> integer(). +bitwise_not(X) -> + erlang:'bnot'(X). + +-spec bitwise_or(integer(), integer()) -> integer(). +bitwise_or(X, Y) -> + erlang:'bor'(X, Y). + +-spec bitwise_exclusive_or(integer(), integer()) -> integer(). +bitwise_exclusive_or(X, Y) -> + erlang:'bxor'(X, Y). + +-spec bitwise_shift_left(integer(), integer()) -> integer(). +bitwise_shift_left(X, Y) -> + erlang:'bsl'(X, Y). + +-spec bitwise_shift_right(integer(), integer()) -> integer(). +bitwise_shift_right(X, Y) -> + erlang:'bsr'(X, Y). diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@io.erl b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@io.erl new file mode 100644 index 0000000..cd01cbc --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@io.erl @@ -0,0 +1,27 @@ +-module(gleam@io). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). + +-export([print/1, print_error/1, println/1, println_error/1, debug/1]). + +-spec print(binary()) -> nil. +print(String) -> + gleam_stdlib:print(String). + +-spec print_error(binary()) -> nil. +print_error(String) -> + gleam_stdlib:print_error(String). + +-spec println(binary()) -> nil. +println(String) -> + gleam_stdlib:println(String). + +-spec println_error(binary()) -> nil. +println_error(String) -> + gleam_stdlib:println_error(String). + +-spec debug(DBW) -> DBW. +debug(Term) -> + _pipe = Term, + _pipe@1 = gleam@string:inspect(_pipe), + gleam_stdlib:println_error(_pipe@1), + Term. diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@iterator.erl b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@iterator.erl new file mode 100644 index 0000000..3d293ba --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@iterator.erl @@ -0,0 +1,744 @@ +-module(gleam@iterator). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). + +-export([unfold/2, repeatedly/1, repeat/1, from_list/1, transform/3, fold/3, run/1, to_list/1, step/1, take/2, drop/2, map/2, map2/3, append/2, flatten/1, concat/1, flat_map/2, filter/2, cycle/1, find/2, index/1, iterate/2, take_while/2, drop_while/2, scan/3, zip/2, chunk/2, sized_chunk/2, intersperse/2, any/2, all/2, group/2, reduce/2, last/1, empty/0, once/1, range/2, single/1, interleave/2, fold_until/3, try_fold/3, first/1, at/2, length/1, each/2, yield/2]). +-export_type([action/1, iterator/1, step/2, chunk/2, sized_chunk/1]). + +-type action(BSS) :: stop | {continue, BSS, fun(() -> action(BSS))}. + +-opaque iterator(BST) :: {iterator, fun(() -> action(BST))}. + +-type step(BSU, BSV) :: {next, BSU, BSV} | done. + +-type chunk(BSW, BSX) :: {another_by, + list(BSW), + BSX, + BSW, + fun(() -> action(BSW))} | + {last_by, list(BSW)}. + +-type sized_chunk(BSY) :: {another, list(BSY), fun(() -> action(BSY))} | + {last, list(BSY)} | + no_more. + +-spec stop() -> action(any()). +stop() -> + stop. + +-spec do_unfold(BTB, fun((BTB) -> step(BTC, BTB))) -> fun(() -> action(BTC)). +do_unfold(Initial, F) -> + fun() -> case F(Initial) of + {next, X, Acc} -> + {continue, X, do_unfold(Acc, F)}; + + done -> + stop + end end. + +-spec unfold(BTG, fun((BTG) -> step(BTH, BTG))) -> iterator(BTH). +unfold(Initial, F) -> + _pipe = Initial, + _pipe@1 = do_unfold(_pipe, F), + {iterator, _pipe@1}. + +-spec repeatedly(fun(() -> BTL)) -> iterator(BTL). +repeatedly(F) -> + unfold(nil, fun(_) -> {next, F(), nil} end). + +-spec repeat(BTN) -> iterator(BTN). +repeat(X) -> + repeatedly(fun() -> X end). + +-spec from_list(list(BTP)) -> iterator(BTP). +from_list(List) -> + Yield = fun(Acc) -> case Acc of + [] -> + done; + + [Head | Tail] -> + {next, Head, Tail} + end end, + unfold(List, Yield). + +-spec do_transform( + fun(() -> action(BTS)), + BTU, + fun((BTU, BTS) -> step(BTV, BTU)) +) -> fun(() -> action(BTV)). +do_transform(Continuation, State, F) -> + fun() -> case Continuation() of + stop -> + stop; + + {continue, El, Next} -> + case F(State, El) of + done -> + stop; + + {next, Yield, Next_state} -> + {continue, Yield, do_transform(Next, Next_state, F)} + end + end end. + +-spec transform(iterator(BTZ), BUB, fun((BUB, BTZ) -> step(BUC, BUB))) -> iterator(BUC). +transform(Iterator, Initial, F) -> + _pipe = do_transform(erlang:element(2, Iterator), Initial, F), + {iterator, _pipe}. + +-spec do_fold(fun(() -> action(BUG)), fun((BUI, BUG) -> BUI), BUI) -> BUI. +do_fold(Continuation, F, Accumulator) -> + case Continuation() of + {continue, Elem, Next} -> + do_fold(Next, F, F(Accumulator, Elem)); + + stop -> + Accumulator + end. + +-spec fold(iterator(BUJ), BUL, fun((BUL, BUJ) -> BUL)) -> BUL. +fold(Iterator, Initial, F) -> + _pipe = erlang:element(2, Iterator), + do_fold(_pipe, F, Initial). + +-spec run(iterator(any())) -> nil. +run(Iterator) -> + fold(Iterator, nil, fun(_, _) -> nil end). + +-spec to_list(iterator(BUO)) -> list(BUO). +to_list(Iterator) -> + _pipe = Iterator, + _pipe@1 = fold(_pipe, [], fun(Acc, E) -> [E | Acc] end), + gleam@list:reverse(_pipe@1). + +-spec step(iterator(BUR)) -> step(BUR, iterator(BUR)). +step(Iterator) -> + case (erlang:element(2, Iterator))() of + stop -> + done; + + {continue, E, A} -> + {next, E, {iterator, A}} + end. + +-spec do_take(fun(() -> action(BUW)), integer()) -> fun(() -> action(BUW)). +do_take(Continuation, Desired) -> + fun() -> case Desired > 0 of + false -> + stop; + + true -> + case Continuation() of + stop -> + stop; + + {continue, E, Next} -> + {continue, E, do_take(Next, Desired - 1)} + end + end end. + +-spec take(iterator(BUZ), integer()) -> iterator(BUZ). +take(Iterator, Desired) -> + _pipe = erlang:element(2, Iterator), + _pipe@1 = do_take(_pipe, Desired), + {iterator, _pipe@1}. + +-spec do_drop(fun(() -> action(BVC)), integer()) -> action(BVC). +do_drop(Continuation, Desired) -> + case Continuation() of + stop -> + stop; + + {continue, E, Next} -> + case Desired > 0 of + true -> + do_drop(Next, Desired - 1); + + false -> + {continue, E, Next} + end + end. + +-spec drop(iterator(BVF), integer()) -> iterator(BVF). +drop(Iterator, Desired) -> + _pipe = fun() -> do_drop(erlang:element(2, Iterator), Desired) end, + {iterator, _pipe}. + +-spec do_map(fun(() -> action(BVI)), fun((BVI) -> BVK)) -> fun(() -> action(BVK)). +do_map(Continuation, F) -> + fun() -> case Continuation() of + stop -> + stop; + + {continue, E, Continuation@1} -> + {continue, F(E), do_map(Continuation@1, F)} + end end. + +-spec map(iterator(BVM), fun((BVM) -> BVO)) -> iterator(BVO). +map(Iterator, F) -> + _pipe = erlang:element(2, Iterator), + _pipe@1 = do_map(_pipe, F), + {iterator, _pipe@1}. + +-spec do_map2( + fun(() -> action(BVQ)), + fun(() -> action(BVS)), + fun((BVQ, BVS) -> BVU) +) -> fun(() -> action(BVU)). +do_map2(Continuation1, Continuation2, Fun) -> + fun() -> case Continuation1() of + stop -> + stop; + + {continue, A, Next_a} -> + case Continuation2() of + stop -> + stop; + + {continue, B, Next_b} -> + {continue, Fun(A, B), do_map2(Next_a, Next_b, Fun)} + end + end end. + +-spec map2(iterator(BVW), iterator(BVY), fun((BVW, BVY) -> BWA)) -> iterator(BWA). +map2(Iterator1, Iterator2, Fun) -> + _pipe = do_map2( + erlang:element(2, Iterator1), + erlang:element(2, Iterator2), + Fun + ), + {iterator, _pipe}. + +-spec do_append(fun(() -> action(BWC)), fun(() -> action(BWC))) -> action(BWC). +do_append(First, Second) -> + case First() of + {continue, E, First@1} -> + {continue, E, fun() -> do_append(First@1, Second) end}; + + stop -> + Second() + end. + +-spec append(iterator(BWG), iterator(BWG)) -> iterator(BWG). +append(First, Second) -> + _pipe = fun() -> + do_append(erlang:element(2, First), erlang:element(2, Second)) + end, + {iterator, _pipe}. + +-spec do_flatten(fun(() -> action(iterator(BWK)))) -> action(BWK). +do_flatten(Flattened) -> + case Flattened() of + stop -> + stop; + + {continue, It, Next_iterator} -> + do_append( + erlang:element(2, It), + fun() -> do_flatten(Next_iterator) end + ) + end. + +-spec flatten(iterator(iterator(BWO))) -> iterator(BWO). +flatten(Iterator) -> + _pipe = fun() -> do_flatten(erlang:element(2, Iterator)) end, + {iterator, _pipe}. + +-spec concat(list(iterator(BWS))) -> iterator(BWS). +concat(Iterators) -> + flatten(from_list(Iterators)). + +-spec flat_map(iterator(BWW), fun((BWW) -> iterator(BWY))) -> iterator(BWY). +flat_map(Iterator, F) -> + _pipe = Iterator, + _pipe@1 = map(_pipe, F), + flatten(_pipe@1). + +-spec do_filter(fun(() -> action(BXB)), fun((BXB) -> boolean())) -> action(BXB). +do_filter(Continuation, Predicate) -> + case Continuation() of + stop -> + stop; + + {continue, E, Iterator} -> + case Predicate(E) of + true -> + {continue, E, fun() -> do_filter(Iterator, Predicate) end}; + + false -> + do_filter(Iterator, Predicate) + end + end. + +-spec filter(iterator(BXE), fun((BXE) -> boolean())) -> iterator(BXE). +filter(Iterator, Predicate) -> + _pipe = fun() -> do_filter(erlang:element(2, Iterator), Predicate) end, + {iterator, _pipe}. + +-spec cycle(iterator(BXH)) -> iterator(BXH). +cycle(Iterator) -> + _pipe = repeat(Iterator), + flatten(_pipe). + +-spec do_find(fun(() -> action(BXL)), fun((BXL) -> boolean())) -> {ok, BXL} | + {error, nil}. +do_find(Continuation, F) -> + case Continuation() of + stop -> + {error, nil}; + + {continue, E, Next} -> + case F(E) of + true -> + {ok, E}; + + false -> + do_find(Next, F) + end + end. + +-spec find(iterator(BXP), fun((BXP) -> boolean())) -> {ok, BXP} | {error, nil}. +find(Haystack, Is_desired) -> + _pipe = erlang:element(2, Haystack), + do_find(_pipe, Is_desired). + +-spec do_index(fun(() -> action(BXT)), integer()) -> fun(() -> action({BXT, + integer()})). +do_index(Continuation, Next) -> + fun() -> case Continuation() of + stop -> + stop; + + {continue, E, Continuation@1} -> + {continue, {E, Next}, do_index(Continuation@1, Next + 1)} + end end. + +-spec index(iterator(BXW)) -> iterator({BXW, integer()}). +index(Iterator) -> + _pipe = erlang:element(2, Iterator), + _pipe@1 = do_index(_pipe, 0), + {iterator, _pipe@1}. + +-spec iterate(BXZ, fun((BXZ) -> BXZ)) -> iterator(BXZ). +iterate(Initial, F) -> + unfold(Initial, fun(Element) -> {next, Element, F(Element)} end). + +-spec do_take_while(fun(() -> action(BYB)), fun((BYB) -> boolean())) -> fun(() -> action(BYB)). +do_take_while(Continuation, Predicate) -> + fun() -> case Continuation() of + stop -> + stop; + + {continue, E, Next} -> + case Predicate(E) of + false -> + stop; + + true -> + {continue, E, do_take_while(Next, Predicate)} + end + end end. + +-spec take_while(iterator(BYE), fun((BYE) -> boolean())) -> iterator(BYE). +take_while(Iterator, Predicate) -> + _pipe = erlang:element(2, Iterator), + _pipe@1 = do_take_while(_pipe, Predicate), + {iterator, _pipe@1}. + +-spec do_drop_while(fun(() -> action(BYH)), fun((BYH) -> boolean())) -> action(BYH). +do_drop_while(Continuation, Predicate) -> + case Continuation() of + stop -> + stop; + + {continue, E, Next} -> + case Predicate(E) of + false -> + {continue, E, Next}; + + true -> + do_drop_while(Next, Predicate) + end + end. + +-spec drop_while(iterator(BYK), fun((BYK) -> boolean())) -> iterator(BYK). +drop_while(Iterator, Predicate) -> + _pipe = fun() -> do_drop_while(erlang:element(2, Iterator), Predicate) end, + {iterator, _pipe}. + +-spec do_scan(fun(() -> action(BYN)), fun((BYP, BYN) -> BYP), BYP) -> fun(() -> action(BYP)). +do_scan(Continuation, F, Accumulator) -> + fun() -> case Continuation() of + stop -> + stop; + + {continue, El, Next} -> + Accumulated = F(Accumulator, El), + {continue, Accumulated, do_scan(Next, F, Accumulated)} + end end. + +-spec scan(iterator(BYR), BYT, fun((BYT, BYR) -> BYT)) -> iterator(BYT). +scan(Iterator, Initial, F) -> + _pipe = erlang:element(2, Iterator), + _pipe@1 = do_scan(_pipe, F, Initial), + {iterator, _pipe@1}. + +-spec do_zip(fun(() -> action(BYV)), fun(() -> action(BYX))) -> fun(() -> action({BYV, + BYX})). +do_zip(Left, Right) -> + fun() -> case Left() of + stop -> + stop; + + {continue, El_left, Next_left} -> + case Right() of + stop -> + stop; + + {continue, El_right, Next_right} -> + {continue, + {El_left, El_right}, + do_zip(Next_left, Next_right)} + end + end end. + +-spec zip(iterator(BZA), iterator(BZC)) -> iterator({BZA, BZC}). +zip(Left, Right) -> + _pipe = do_zip(erlang:element(2, Left), erlang:element(2, Right)), + {iterator, _pipe}. + +-spec next_chunk(fun(() -> action(BZF)), fun((BZF) -> BZH), BZH, list(BZF)) -> chunk(BZF, BZH). +next_chunk(Continuation, F, Previous_key, Current_chunk) -> + case Continuation() of + stop -> + {last_by, gleam@list:reverse(Current_chunk)}; + + {continue, E, Next} -> + Key = F(E), + case Key =:= Previous_key of + true -> + next_chunk(Next, F, Key, [E | Current_chunk]); + + false -> + {another_by, + gleam@list:reverse(Current_chunk), + Key, + E, + Next} + end + end. + +-spec do_chunk(fun(() -> action(BZL)), fun((BZL) -> BZN), BZN, BZL) -> action(list(BZL)). +do_chunk(Continuation, F, Previous_key, Previous_element) -> + case next_chunk(Continuation, F, Previous_key, [Previous_element]) of + {last_by, Chunk} -> + {continue, Chunk, fun stop/0}; + + {another_by, Chunk@1, Key, El, Next} -> + {continue, Chunk@1, fun() -> do_chunk(Next, F, Key, El) end} + end. + +-spec chunk(iterator(BZQ), fun((BZQ) -> any())) -> iterator(list(BZQ)). +chunk(Iterator, F) -> + _pipe = fun() -> case (erlang:element(2, Iterator))() of + stop -> + stop; + + {continue, E, Next} -> + do_chunk(Next, F, F(E), E) + end end, + {iterator, _pipe}. + +-spec next_sized_chunk(fun(() -> action(BZV)), integer(), list(BZV)) -> sized_chunk(BZV). +next_sized_chunk(Continuation, Left, Current_chunk) -> + case Continuation() of + stop -> + case Current_chunk of + [] -> + no_more; + + Remaining -> + {last, gleam@list:reverse(Remaining)} + end; + + {continue, E, Next} -> + Chunk = [E | Current_chunk], + case Left > 1 of + false -> + {another, gleam@list:reverse(Chunk), Next}; + + true -> + next_sized_chunk(Next, Left - 1, Chunk) + end + end. + +-spec do_sized_chunk(fun(() -> action(BZZ)), integer()) -> fun(() -> action(list(BZZ))). +do_sized_chunk(Continuation, Count) -> + fun() -> case next_sized_chunk(Continuation, Count, []) of + no_more -> + stop; + + {last, Chunk} -> + {continue, Chunk, fun stop/0}; + + {another, Chunk@1, Next_element} -> + {continue, Chunk@1, do_sized_chunk(Next_element, Count)} + end end. + +-spec sized_chunk(iterator(CAD), integer()) -> iterator(list(CAD)). +sized_chunk(Iterator, Count) -> + _pipe = erlang:element(2, Iterator), + _pipe@1 = do_sized_chunk(_pipe, Count), + {iterator, _pipe@1}. + +-spec do_intersperse(fun(() -> action(CAH)), CAH) -> action(CAH). +do_intersperse(Continuation, Separator) -> + case Continuation() of + stop -> + stop; + + {continue, E, Next} -> + Next_interspersed = fun() -> do_intersperse(Next, Separator) end, + {continue, Separator, fun() -> {continue, E, Next_interspersed} end} + end. + +-spec intersperse(iterator(CAK), CAK) -> iterator(CAK). +intersperse(Iterator, Elem) -> + _pipe = fun() -> case (erlang:element(2, Iterator))() of + stop -> + stop; + + {continue, E, Next} -> + {continue, E, fun() -> do_intersperse(Next, Elem) end} + end end, + {iterator, _pipe}. + +-spec do_any(fun(() -> action(CAN)), fun((CAN) -> boolean())) -> boolean(). +do_any(Continuation, Predicate) -> + case Continuation() of + stop -> + false; + + {continue, E, Next} -> + case Predicate(E) of + true -> + true; + + false -> + do_any(Next, Predicate) + end + end. + +-spec any(iterator(CAP), fun((CAP) -> boolean())) -> boolean(). +any(Iterator, Predicate) -> + _pipe = erlang:element(2, Iterator), + do_any(_pipe, Predicate). + +-spec do_all(fun(() -> action(CAR)), fun((CAR) -> boolean())) -> boolean(). +do_all(Continuation, Predicate) -> + case Continuation() of + stop -> + true; + + {continue, E, Next} -> + case Predicate(E) of + true -> + do_all(Next, Predicate); + + false -> + false + end + end. + +-spec all(iterator(CAT), fun((CAT) -> boolean())) -> boolean(). +all(Iterator, Predicate) -> + _pipe = erlang:element(2, Iterator), + do_all(_pipe, Predicate). + +-spec update_group_with(CAV) -> fun((gleam@option:option(list(CAV))) -> list(CAV)). +update_group_with(El) -> + fun(Maybe_group) -> case Maybe_group of + {some, Group} -> + [El | Group]; + + none -> + [El] + end end. + +-spec group_updater(fun((CAZ) -> CBA)) -> fun((gleam@dict:dict(CBA, list(CAZ)), CAZ) -> gleam@dict:dict(CBA, list(CAZ))). +group_updater(F) -> + fun(Groups, Elem) -> _pipe = Groups, + gleam@dict:update(_pipe, F(Elem), update_group_with(Elem)) end. + +-spec group(iterator(CBH), fun((CBH) -> CBJ)) -> gleam@dict:dict(CBJ, list(CBH)). +group(Iterator, Key) -> + _pipe = Iterator, + _pipe@1 = fold(_pipe, gleam@dict:new(), group_updater(Key)), + gleam@dict:map_values( + _pipe@1, + fun(_, Group) -> gleam@list:reverse(Group) end + ). + +-spec reduce(iterator(CBN), fun((CBN, CBN) -> CBN)) -> {ok, CBN} | {error, nil}. +reduce(Iterator, F) -> + case (erlang:element(2, Iterator))() of + stop -> + {error, nil}; + + {continue, E, Next} -> + _pipe = do_fold(Next, F, E), + {ok, _pipe} + end. + +-spec last(iterator(CBR)) -> {ok, CBR} | {error, nil}. +last(Iterator) -> + _pipe = Iterator, + reduce(_pipe, fun(_, Elem) -> Elem end). + +-spec empty() -> iterator(any()). +empty() -> + {iterator, fun stop/0}. + +-spec once(fun(() -> CBX)) -> iterator(CBX). +once(F) -> + _pipe = fun() -> {continue, F(), fun stop/0} end, + {iterator, _pipe}. + +-spec range(integer(), integer()) -> iterator(integer()). +range(Start, Stop) -> + case gleam@int:compare(Start, Stop) of + eq -> + once(fun() -> Start end); + + gt -> + unfold(Start, fun(Current) -> case Current < Stop of + false -> + {next, Current, Current - 1}; + + true -> + done + end end); + + lt -> + unfold(Start, fun(Current@1) -> case Current@1 > Stop of + false -> + {next, Current@1, Current@1 + 1}; + + true -> + done + end end) + end. + +-spec single(CBZ) -> iterator(CBZ). +single(Elem) -> + once(fun() -> Elem end). + +-spec do_interleave(fun(() -> action(CCB)), fun(() -> action(CCB))) -> action(CCB). +do_interleave(Current, Next) -> + case Current() of + stop -> + Next(); + + {continue, E, Next_other} -> + {continue, E, fun() -> do_interleave(Next, Next_other) end} + end. + +-spec interleave(iterator(CCF), iterator(CCF)) -> iterator(CCF). +interleave(Left, Right) -> + _pipe = fun() -> + do_interleave(erlang:element(2, Left), erlang:element(2, Right)) + end, + {iterator, _pipe}. + +-spec do_fold_until( + fun(() -> action(CCJ)), + fun((CCL, CCJ) -> gleam@list:continue_or_stop(CCL)), + CCL +) -> CCL. +do_fold_until(Continuation, F, Accumulator) -> + case Continuation() of + stop -> + Accumulator; + + {continue, Elem, Next} -> + case F(Accumulator, Elem) of + {continue, Accumulator@1} -> + do_fold_until(Next, F, Accumulator@1); + + {stop, Accumulator@2} -> + Accumulator@2 + end + end. + +-spec fold_until( + iterator(CCN), + CCP, + fun((CCP, CCN) -> gleam@list:continue_or_stop(CCP)) +) -> CCP. +fold_until(Iterator, Initial, F) -> + _pipe = erlang:element(2, Iterator), + do_fold_until(_pipe, F, Initial). + +-spec do_try_fold( + fun(() -> action(CCR)), + fun((CCT, CCR) -> {ok, CCT} | {error, CCU}), + CCT +) -> {ok, CCT} | {error, CCU}. +do_try_fold(Continuation, F, Accumulator) -> + case Continuation() of + stop -> + {ok, Accumulator}; + + {continue, Elem, Next} -> + gleam@result:'try'( + F(Accumulator, Elem), + fun(Accumulator@1) -> do_try_fold(Next, F, Accumulator@1) end + ) + end. + +-spec try_fold(iterator(CCZ), CDB, fun((CDB, CCZ) -> {ok, CDB} | {error, CDC})) -> {ok, + CDB} | + {error, CDC}. +try_fold(Iterator, Initial, F) -> + _pipe = erlang:element(2, Iterator), + do_try_fold(_pipe, F, Initial). + +-spec first(iterator(CDH)) -> {ok, CDH} | {error, nil}. +first(Iterator) -> + case (erlang:element(2, Iterator))() of + stop -> + {error, nil}; + + {continue, E, _} -> + {ok, E} + end. + +-spec at(iterator(CDL), integer()) -> {ok, CDL} | {error, nil}. +at(Iterator, Index) -> + _pipe = Iterator, + _pipe@1 = drop(_pipe, Index), + first(_pipe@1). + +-spec do_length(fun(() -> action(any())), integer()) -> integer(). +do_length(Continuation, Length) -> + case Continuation() of + stop -> + Length; + + {continue, _, Next} -> + do_length(Next, Length + 1) + end. + +-spec length(iterator(any())) -> integer(). +length(Iterator) -> + _pipe = erlang:element(2, Iterator), + do_length(_pipe, 0). + +-spec each(iterator(CDT), fun((CDT) -> any())) -> nil. +each(Iterator, F) -> + _pipe = Iterator, + _pipe@1 = map(_pipe, F), + run(_pipe@1). + +-spec yield(CDW, fun(() -> iterator(CDW))) -> iterator(CDW). +yield(Element, Next) -> + {iterator, fun() -> {continue, Element, erlang:element(2, Next())} end}. diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@list.erl b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@list.erl new file mode 100644 index 0000000..21f87e5 --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@list.erl @@ -0,0 +1,1136 @@ +-module(gleam@list). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). + +-export([length/1, reverse/1, is_empty/1, contains/2, first/1, rest/1, filter/2, filter_map/2, map/2, map2/3, index_map/2, try_map/2, drop/2, take/2, new/0, append/2, prepend/2, concat/1, flatten/1, flat_map/2, fold/3, group/2, map_fold/3, fold_right/3, index_fold/3, try_fold/3, fold_until/3, find/2, find_map/2, all/2, any/2, zip/2, strict_zip/2, unzip/1, intersperse/2, at/2, unique/1, sort/2, range/2, repeat/2, split/2, split_while/2, key_find/2, key_filter/2, pop/2, pop_map/2, key_pop/2, key_set/3, each/2, try_each/2, partition/2, permutations/1, window/2, window_by_2/1, drop_while/2, take_while/2, chunk/2, sized_chunk/2, reduce/2, scan/3, last/1, combinations/2, combination_pairs/1, transpose/1, interleave/1, shuffle/1]). +-export_type([length_mismatch/0, continue_or_stop/1]). + +-type length_mismatch() :: length_mismatch. + +-type continue_or_stop(XQ) :: {continue, XQ} | {stop, XQ}. + +-spec length(list(any())) -> integer(). +length(List) -> + erlang:length(List). + +-spec reverse(list(XV)) -> list(XV). +reverse(Xs) -> + lists:reverse(Xs). + +-spec is_empty(list(any())) -> boolean(). +is_empty(List) -> + List =:= []. + +-spec contains(list(YD), YD) -> boolean(). +contains(List, Elem) -> + case List of + [] -> + false; + + [First | _] when First =:= Elem -> + true; + + [_ | Rest] -> + contains(Rest, Elem) + end. + +-spec first(list(YF)) -> {ok, YF} | {error, nil}. +first(List) -> + case List of + [] -> + {error, nil}; + + [X | _] -> + {ok, X} + end. + +-spec rest(list(YJ)) -> {ok, list(YJ)} | {error, nil}. +rest(List) -> + case List of + [] -> + {error, nil}; + + [_ | Xs] -> + {ok, Xs} + end. + +-spec update_group(fun((YO) -> YP)) -> fun((gleam@dict:dict(YP, list(YO)), YO) -> gleam@dict:dict(YP, list(YO))). +update_group(F) -> + fun(Groups, Elem) -> case gleam@dict:get(Groups, F(Elem)) of + {ok, Existing} -> + gleam@dict:insert(Groups, F(Elem), [Elem | Existing]); + + {error, _} -> + gleam@dict:insert(Groups, F(Elem), [Elem]) + end end. + +-spec do_filter(list(AAC), fun((AAC) -> boolean()), list(AAC)) -> list(AAC). +do_filter(List, Fun, Acc) -> + case List of + [] -> + reverse(Acc); + + [X | Xs] -> + New_acc = case Fun(X) of + true -> + [X | Acc]; + + false -> + Acc + end, + do_filter(Xs, Fun, New_acc) + end. + +-spec filter(list(AAG), fun((AAG) -> boolean())) -> list(AAG). +filter(List, Predicate) -> + do_filter(List, Predicate, []). + +-spec do_filter_map( + list(AAJ), + fun((AAJ) -> {ok, AAL} | {error, any()}), + list(AAL) +) -> list(AAL). +do_filter_map(List, Fun, Acc) -> + case List of + [] -> + reverse(Acc); + + [X | Xs] -> + New_acc = case Fun(X) of + {ok, X@1} -> + [X@1 | Acc]; + + {error, _} -> + Acc + end, + do_filter_map(Xs, Fun, New_acc) + end. + +-spec filter_map(list(AAR), fun((AAR) -> {ok, AAT} | {error, any()})) -> list(AAT). +filter_map(List, Fun) -> + do_filter_map(List, Fun, []). + +-spec do_map(list(AAY), fun((AAY) -> ABA), list(ABA)) -> list(ABA). +do_map(List, Fun, Acc) -> + case List of + [] -> + reverse(Acc); + + [X | Xs] -> + do_map(Xs, Fun, [Fun(X) | Acc]) + end. + +-spec map(list(ABD), fun((ABD) -> ABF)) -> list(ABF). +map(List, Fun) -> + do_map(List, Fun, []). + +-spec do_map2(list(ABN), list(ABP), fun((ABN, ABP) -> ABR), list(ABR)) -> list(ABR). +do_map2(List1, List2, Fun, Acc) -> + case {List1, List2} of + {[], _} -> + reverse(Acc); + + {_, []} -> + reverse(Acc); + + {[A | As_], [B | Bs]} -> + do_map2(As_, Bs, Fun, [Fun(A, B) | Acc]) + end. + +-spec map2(list(ABH), list(ABJ), fun((ABH, ABJ) -> ABL)) -> list(ABL). +map2(List1, List2, Fun) -> + do_map2(List1, List2, Fun, []). + +-spec do_index_map( + list(ABZ), + fun((ABZ, integer()) -> ACB), + integer(), + list(ACB) +) -> list(ACB). +do_index_map(List, Fun, Index, Acc) -> + case List of + [] -> + reverse(Acc); + + [X | Xs] -> + Acc@1 = [Fun(X, Index) | Acc], + do_index_map(Xs, Fun, Index + 1, Acc@1) + end. + +-spec index_map(list(ACE), fun((ACE, integer()) -> ACG)) -> list(ACG). +index_map(List, Fun) -> + do_index_map(List, Fun, 0, []). + +-spec do_try_map(list(ACI), fun((ACI) -> {ok, ACK} | {error, ACL}), list(ACK)) -> {ok, + list(ACK)} | + {error, ACL}. +do_try_map(List, Fun, Acc) -> + case List of + [] -> + {ok, reverse(Acc)}; + + [X | Xs] -> + case Fun(X) of + {ok, Y} -> + do_try_map(Xs, Fun, [Y | Acc]); + + {error, Error} -> + {error, Error} + end + end. + +-spec try_map(list(ACS), fun((ACS) -> {ok, ACU} | {error, ACV})) -> {ok, + list(ACU)} | + {error, ACV}. +try_map(List, Fun) -> + do_try_map(List, Fun, []). + +-spec drop(list(ADB), integer()) -> list(ADB). +drop(List, N) -> + case N =< 0 of + true -> + List; + + false -> + case List of + [] -> + []; + + [_ | Xs] -> + drop(Xs, N - 1) + end + end. + +-spec do_take(list(ADE), integer(), list(ADE)) -> list(ADE). +do_take(List, N, Acc) -> + case N =< 0 of + true -> + reverse(Acc); + + false -> + case List of + [] -> + reverse(Acc); + + [X | Xs] -> + do_take(Xs, N - 1, [X | Acc]) + end + end. + +-spec take(list(ADI), integer()) -> list(ADI). +take(List, N) -> + do_take(List, N, []). + +-spec new() -> list(any()). +new() -> + []. + +-spec append(list(ADN), list(ADN)) -> list(ADN). +append(First, Second) -> + lists:append(First, Second). + +-spec prepend(list(ADV), ADV) -> list(ADV). +prepend(List, Item) -> + [Item | List]. + +-spec reverse_and_prepend(list(ADY), list(ADY)) -> list(ADY). +reverse_and_prepend(Prefix, Suffix) -> + case Prefix of + [] -> + Suffix; + + [First | Rest] -> + reverse_and_prepend(Rest, [First | Suffix]) + end. + +-spec do_concat(list(list(AEC)), list(AEC)) -> list(AEC). +do_concat(Lists, Acc) -> + case Lists of + [] -> + reverse(Acc); + + [List | Further_lists] -> + do_concat(Further_lists, reverse_and_prepend(List, Acc)) + end. + +-spec concat(list(list(AEH))) -> list(AEH). +concat(Lists) -> + do_concat(Lists, []). + +-spec flatten(list(list(AEL))) -> list(AEL). +flatten(Lists) -> + do_concat(Lists, []). + +-spec flat_map(list(AEP), fun((AEP) -> list(AER))) -> list(AER). +flat_map(List, Fun) -> + _pipe = map(List, Fun), + concat(_pipe). + +-spec fold(list(AEU), AEW, fun((AEW, AEU) -> AEW)) -> AEW. +fold(List, Initial, Fun) -> + case List of + [] -> + Initial; + + [X | Rest] -> + fold(Rest, Fun(Initial, X), Fun) + end. + +-spec group(list(YW), fun((YW) -> YY)) -> gleam@dict:dict(YY, list(YW)). +group(List, Key) -> + fold(List, gleam@dict:new(), update_group(Key)). + +-spec map_fold(list(ABU), ABW, fun((ABW, ABU) -> {ABW, ABX})) -> {ABW, + list(ABX)}. +map_fold(List, Acc, Fun) -> + _pipe = fold( + List, + {Acc, []}, + fun(Acc@1, Item) -> + {Current_acc, Items} = Acc@1, + {Next_acc, Next_item} = Fun(Current_acc, Item), + {Next_acc, [Next_item | Items]} + end + ), + gleam@pair:map_second(_pipe, fun reverse/1). + +-spec fold_right(list(AEX), AEZ, fun((AEZ, AEX) -> AEZ)) -> AEZ. +fold_right(List, Initial, Fun) -> + case List of + [] -> + Initial; + + [X | Rest] -> + Fun(fold_right(Rest, Initial, Fun), X) + end. + +-spec do_index_fold( + list(AFA), + AFC, + fun((AFC, AFA, integer()) -> AFC), + integer() +) -> AFC. +do_index_fold(Over, Acc, With, Index) -> + case Over of + [] -> + Acc; + + [First | Rest] -> + do_index_fold(Rest, With(Acc, First, Index), With, Index + 1) + end. + +-spec index_fold(list(AFD), AFF, fun((AFF, AFD, integer()) -> AFF)) -> AFF. +index_fold(Over, Initial, Fun) -> + do_index_fold(Over, Initial, Fun, 0). + +-spec try_fold(list(AFG), AFI, fun((AFI, AFG) -> {ok, AFI} | {error, AFJ})) -> {ok, + AFI} | + {error, AFJ}. +try_fold(Collection, Accumulator, Fun) -> + case Collection of + [] -> + {ok, Accumulator}; + + [First | Rest] -> + case Fun(Accumulator, First) of + {ok, Result} -> + try_fold(Rest, Result, Fun); + + {error, _} = Error -> + Error + end + end. + +-spec fold_until(list(AFO), AFQ, fun((AFQ, AFO) -> continue_or_stop(AFQ))) -> AFQ. +fold_until(Collection, Accumulator, Fun) -> + case Collection of + [] -> + Accumulator; + + [First | Rest] -> + case Fun(Accumulator, First) of + {continue, Next_accumulator} -> + fold_until(Rest, Next_accumulator, Fun); + + {stop, B} -> + B + end + end. + +-spec find(list(AFS), fun((AFS) -> boolean())) -> {ok, AFS} | {error, nil}. +find(Haystack, Is_desired) -> + case Haystack of + [] -> + {error, nil}; + + [X | Rest] -> + case Is_desired(X) of + true -> + {ok, X}; + + _ -> + find(Rest, Is_desired) + end + end. + +-spec find_map(list(AFW), fun((AFW) -> {ok, AFY} | {error, any()})) -> {ok, AFY} | + {error, nil}. +find_map(Haystack, Fun) -> + case Haystack of + [] -> + {error, nil}; + + [X | Rest] -> + case Fun(X) of + {ok, X@1} -> + {ok, X@1}; + + _ -> + find_map(Rest, Fun) + end + end. + +-spec all(list(AGE), fun((AGE) -> boolean())) -> boolean(). +all(List, Predicate) -> + case List of + [] -> + true; + + [First | Rest] -> + case Predicate(First) of + true -> + all(Rest, Predicate); + + false -> + false + end + end. + +-spec any(list(AGG), fun((AGG) -> boolean())) -> boolean(). +any(List, Predicate) -> + case List of + [] -> + false; + + [First | Rest] -> + case Predicate(First) of + true -> + true; + + false -> + any(Rest, Predicate) + end + end. + +-spec do_zip(list(AGI), list(AGK), list({AGI, AGK})) -> list({AGI, AGK}). +do_zip(Xs, Ys, Acc) -> + case {Xs, Ys} of + {[X | Xs@1], [Y | Ys@1]} -> + do_zip(Xs@1, Ys@1, [{X, Y} | Acc]); + + {_, _} -> + reverse(Acc) + end. + +-spec zip(list(AGO), list(AGQ)) -> list({AGO, AGQ}). +zip(List, Other) -> + do_zip(List, Other, []). + +-spec strict_zip(list(AGT), list(AGV)) -> {ok, list({AGT, AGV})} | + {error, length_mismatch()}. +strict_zip(List, Other) -> + case length(List) =:= length(Other) of + true -> + {ok, zip(List, Other)}; + + false -> + {error, length_mismatch} + end. + +-spec do_unzip(list({AWN, AWO}), list(AWN), list(AWO)) -> {list(AWN), list(AWO)}. +do_unzip(Input, Xs, Ys) -> + case Input of + [] -> + {reverse(Xs), reverse(Ys)}; + + [{X, Y} | Rest] -> + do_unzip(Rest, [X | Xs], [Y | Ys]) + end. + +-spec unzip(list({AHE, AHF})) -> {list(AHE), list(AHF)}. +unzip(Input) -> + do_unzip(Input, [], []). + +-spec do_intersperse(list(AHJ), AHJ, list(AHJ)) -> list(AHJ). +do_intersperse(List, Separator, Acc) -> + case List of + [] -> + reverse(Acc); + + [X | Rest] -> + do_intersperse(Rest, Separator, [X, Separator | Acc]) + end. + +-spec intersperse(list(AHN), AHN) -> list(AHN). +intersperse(List, Elem) -> + case List of + [] -> + List; + + [_] -> + List; + + [X | Rest] -> + do_intersperse(Rest, Elem, [X]) + end. + +-spec at(list(AHQ), integer()) -> {ok, AHQ} | {error, nil}. +at(List, Index) -> + case Index >= 0 of + true -> + _pipe = List, + _pipe@1 = drop(_pipe, Index), + first(_pipe@1); + + false -> + {error, nil} + end. + +-spec unique(list(AHU)) -> list(AHU). +unique(List) -> + case List of + [] -> + []; + + [X | Rest] -> + [X | unique(filter(Rest, fun(Y) -> Y /= X end))] + end. + +-spec merge_up( + integer(), + integer(), + list(AHX), + list(AHX), + list(AHX), + fun((AHX, AHX) -> gleam@order:order()) +) -> list(AHX). +merge_up(Na, Nb, A, B, Acc, Compare) -> + case {Na, Nb, A, B} of + {0, 0, _, _} -> + Acc; + + {_, 0, [Ax | Ar], _} -> + merge_up(Na - 1, Nb, Ar, B, [Ax | Acc], Compare); + + {0, _, _, [Bx | Br]} -> + merge_up(Na, Nb - 1, A, Br, [Bx | Acc], Compare); + + {_, _, [Ax@1 | Ar@1], [Bx@1 | Br@1]} -> + case Compare(Ax@1, Bx@1) of + gt -> + merge_up(Na, Nb - 1, A, Br@1, [Bx@1 | Acc], Compare); + + _ -> + merge_up(Na - 1, Nb, Ar@1, B, [Ax@1 | Acc], Compare) + end; + + {_, _, _, _} -> + Acc + end. + +-spec merge_down( + integer(), + integer(), + list(AIC), + list(AIC), + list(AIC), + fun((AIC, AIC) -> gleam@order:order()) +) -> list(AIC). +merge_down(Na, Nb, A, B, Acc, Compare) -> + case {Na, Nb, A, B} of + {0, 0, _, _} -> + Acc; + + {_, 0, [Ax | Ar], _} -> + merge_down(Na - 1, Nb, Ar, B, [Ax | Acc], Compare); + + {0, _, _, [Bx | Br]} -> + merge_down(Na, Nb - 1, A, Br, [Bx | Acc], Compare); + + {_, _, [Ax@1 | Ar@1], [Bx@1 | Br@1]} -> + case Compare(Bx@1, Ax@1) of + lt -> + merge_down(Na - 1, Nb, Ar@1, B, [Ax@1 | Acc], Compare); + + _ -> + merge_down(Na, Nb - 1, A, Br@1, [Bx@1 | Acc], Compare) + end; + + {_, _, _, _} -> + Acc + end. + +-spec merge_sort( + list(AIH), + integer(), + fun((AIH, AIH) -> gleam@order:order()), + boolean() +) -> list(AIH). +merge_sort(L, Ln, Compare, Down) -> + N = Ln div 2, + A = L, + B = drop(L, N), + case Ln < 3 of + true -> + case Down of + true -> + merge_down(N, Ln - N, A, B, [], Compare); + + false -> + merge_up(N, Ln - N, A, B, [], Compare) + end; + + false -> + case Down of + true -> + merge_down( + N, + Ln - N, + merge_sort(A, N, Compare, false), + merge_sort(B, Ln - N, Compare, false), + [], + Compare + ); + + false -> + merge_up( + N, + Ln - N, + merge_sort(A, N, Compare, true), + merge_sort(B, Ln - N, Compare, true), + [], + Compare + ) + end + end. + +-spec sort(list(AIK), fun((AIK, AIK) -> gleam@order:order())) -> list(AIK). +sort(List, Compare) -> + merge_sort(List, length(List), Compare, true). + +-spec tail_recursive_range(integer(), integer(), list(integer())) -> list(integer()). +tail_recursive_range(Start, Stop, Acc) -> + case gleam@int:compare(Start, Stop) of + eq -> + [Stop | Acc]; + + gt -> + tail_recursive_range(Start, Stop + 1, [Stop | Acc]); + + lt -> + tail_recursive_range(Start, Stop - 1, [Stop | Acc]) + end. + +-spec range(integer(), integer()) -> list(integer()). +range(Start, Stop) -> + tail_recursive_range(Start, Stop, []). + +-spec do_repeat(AIQ, integer(), list(AIQ)) -> list(AIQ). +do_repeat(A, Times, Acc) -> + case Times =< 0 of + true -> + Acc; + + false -> + do_repeat(A, Times - 1, [A | Acc]) + end. + +-spec repeat(AIT, integer()) -> list(AIT). +repeat(A, Times) -> + do_repeat(A, Times, []). + +-spec do_split(list(AIV), integer(), list(AIV)) -> {list(AIV), list(AIV)}. +do_split(List, N, Taken) -> + case N =< 0 of + true -> + {reverse(Taken), List}; + + false -> + case List of + [] -> + {reverse(Taken), []}; + + [X | Xs] -> + do_split(Xs, N - 1, [X | Taken]) + end + end. + +-spec split(list(AJA), integer()) -> {list(AJA), list(AJA)}. +split(List, Index) -> + do_split(List, Index, []). + +-spec do_split_while(list(AJE), fun((AJE) -> boolean()), list(AJE)) -> {list(AJE), + list(AJE)}. +do_split_while(List, F, Acc) -> + case List of + [] -> + {reverse(Acc), []}; + + [X | Xs] -> + case F(X) of + false -> + {reverse(Acc), List}; + + _ -> + do_split_while(Xs, F, [X | Acc]) + end + end. + +-spec split_while(list(AJJ), fun((AJJ) -> boolean())) -> {list(AJJ), list(AJJ)}. +split_while(List, Predicate) -> + do_split_while(List, Predicate, []). + +-spec key_find(list({AJN, AJO}), AJN) -> {ok, AJO} | {error, nil}. +key_find(Keyword_list, Desired_key) -> + find_map( + Keyword_list, + fun(Keyword) -> + {Key, Value} = Keyword, + case Key =:= Desired_key of + true -> + {ok, Value}; + + false -> + {error, nil} + end + end + ). + +-spec key_filter(list({AJS, AJT}), AJS) -> list(AJT). +key_filter(Keyword_list, Desired_key) -> + filter_map( + Keyword_list, + fun(Keyword) -> + {Key, Value} = Keyword, + case Key =:= Desired_key of + true -> + {ok, Value}; + + false -> + {error, nil} + end + end + ). + +-spec do_pop(list(BAG), fun((BAG) -> boolean()), list(BAG)) -> {ok, + {BAG, list(BAG)}} | + {error, nil}. +do_pop(Haystack, Predicate, Checked) -> + case Haystack of + [] -> + {error, nil}; + + [X | Rest] -> + case Predicate(X) of + true -> + {ok, {X, append(reverse(Checked), Rest)}}; + + false -> + do_pop(Rest, Predicate, [X | Checked]) + end + end. + +-spec pop(list(AKA), fun((AKA) -> boolean())) -> {ok, {AKA, list(AKA)}} | + {error, nil}. +pop(Haystack, Is_desired) -> + do_pop(Haystack, Is_desired, []). + +-spec do_pop_map(list(BAU), fun((BAU) -> {ok, BBH} | {error, any()}), list(BAU)) -> {ok, + {BBH, list(BAU)}} | + {error, nil}. +do_pop_map(Haystack, Mapper, Checked) -> + case Haystack of + [] -> + {error, nil}; + + [X | Rest] -> + case Mapper(X) of + {ok, Y} -> + {ok, {Y, append(reverse(Checked), Rest)}}; + + {error, _} -> + do_pop_map(Rest, Mapper, [X | Checked]) + end + end. + +-spec pop_map(list(AKJ), fun((AKJ) -> {ok, AKL} | {error, any()})) -> {ok, + {AKL, list(AKJ)}} | + {error, nil}. +pop_map(Haystack, Is_desired) -> + do_pop_map(Haystack, Is_desired, []). + +-spec key_pop(list({AKS, AKT}), AKS) -> {ok, {AKT, list({AKS, AKT})}} | + {error, nil}. +key_pop(Haystack, Key) -> + pop_map( + Haystack, + fun(Entry) -> + {K, V} = Entry, + case K of + K@1 when K@1 =:= Key -> + {ok, V}; + + _ -> + {error, nil} + end + end + ). + +-spec key_set(list({AKY, AKZ}), AKY, AKZ) -> list({AKY, AKZ}). +key_set(List, Key, Value) -> + case List of + [] -> + [{Key, Value}]; + + [{K, _} | Rest] when K =:= Key -> + [{Key, Value} | Rest]; + + [First | Rest@1] -> + [First | key_set(Rest@1, Key, Value)] + end. + +-spec each(list(ALC), fun((ALC) -> any())) -> nil. +each(List, F) -> + case List of + [] -> + nil; + + [X | Xs] -> + F(X), + each(Xs, F) + end. + +-spec try_each(list(ALF), fun((ALF) -> {ok, any()} | {error, ALI})) -> {ok, nil} | + {error, ALI}. +try_each(List, Fun) -> + case List of + [] -> + {ok, nil}; + + [X | Xs] -> + case Fun(X) of + {ok, _} -> + try_each(Xs, Fun); + + {error, E} -> + {error, E} + end + end. + +-spec do_partition(list(BCO), fun((BCO) -> boolean()), list(BCO), list(BCO)) -> {list(BCO), + list(BCO)}. +do_partition(List, Categorise, Trues, Falses) -> + case List of + [] -> + {reverse(Trues), reverse(Falses)}; + + [X | Xs] -> + case Categorise(X) of + true -> + do_partition(Xs, Categorise, [X | Trues], Falses); + + false -> + do_partition(Xs, Categorise, Trues, [X | Falses]) + end + end. + +-spec partition(list(ALS), fun((ALS) -> boolean())) -> {list(ALS), list(ALS)}. +partition(List, Categorise) -> + do_partition(List, Categorise, [], []). + +-spec permutations(list(ALW)) -> list(list(ALW)). +permutations(L) -> + case L of + [] -> + [[]]; + + _ -> + _pipe = L, + _pipe@5 = index_map(_pipe, fun(I, I_idx) -> _pipe@1 = L, + _pipe@2 = index_fold( + _pipe@1, + [], + fun(Acc, J, J_idx) -> case I_idx =:= J_idx of + true -> + Acc; + + false -> + [J | Acc] + end end + ), + _pipe@3 = reverse(_pipe@2), + _pipe@4 = permutations(_pipe@3), + map(_pipe@4, fun(Permutation) -> [I | Permutation] end) end), + concat(_pipe@5) + end. + +-spec do_window(list(list(AMA)), list(AMA), integer()) -> list(list(AMA)). +do_window(Acc, L, N) -> + Window = take(L, N), + case length(Window) =:= N of + true -> + do_window([Window | Acc], drop(L, 1), N); + + false -> + Acc + end. + +-spec window(list(AMG), integer()) -> list(list(AMG)). +window(L, N) -> + _pipe = do_window([], L, N), + reverse(_pipe). + +-spec window_by_2(list(AMK)) -> list({AMK, AMK}). +window_by_2(L) -> + zip(L, drop(L, 1)). + +-spec drop_while(list(AMN), fun((AMN) -> boolean())) -> list(AMN). +drop_while(List, Predicate) -> + case List of + [] -> + []; + + [X | Xs] -> + case Predicate(X) of + true -> + drop_while(Xs, Predicate); + + false -> + [X | Xs] + end + end. + +-spec do_take_while(list(AMQ), fun((AMQ) -> boolean()), list(AMQ)) -> list(AMQ). +do_take_while(List, Predicate, Acc) -> + case List of + [] -> + reverse(Acc); + + [First | Rest] -> + case Predicate(First) of + true -> + do_take_while(Rest, Predicate, [First | Acc]); + + false -> + reverse(Acc) + end + end. + +-spec take_while(list(AMU), fun((AMU) -> boolean())) -> list(AMU). +take_while(List, Predicate) -> + do_take_while(List, Predicate, []). + +-spec do_chunk(list(AMX), fun((AMX) -> AMZ), AMZ, list(AMX), list(list(AMX))) -> list(list(AMX)). +do_chunk(List, F, Previous_key, Current_chunk, Acc) -> + case List of + [First | Rest] -> + Key = F(First), + case Key =:= Previous_key of + false -> + New_acc = [reverse(Current_chunk) | Acc], + do_chunk(Rest, F, Key, [First], New_acc); + + _ -> + do_chunk(Rest, F, Key, [First | Current_chunk], Acc) + end; + + _ -> + reverse([reverse(Current_chunk) | Acc]) + end. + +-spec chunk(list(ANF), fun((ANF) -> any())) -> list(list(ANF)). +chunk(List, F) -> + case List of + [] -> + []; + + [First | Rest] -> + do_chunk(Rest, F, F(First), [First], []) + end. + +-spec do_sized_chunk( + list(ANK), + integer(), + integer(), + list(ANK), + list(list(ANK)) +) -> list(list(ANK)). +do_sized_chunk(List, Count, Left, Current_chunk, Acc) -> + case List of + [] -> + case Current_chunk of + [] -> + reverse(Acc); + + Remaining -> + reverse([reverse(Remaining) | Acc]) + end; + + [First | Rest] -> + Chunk = [First | Current_chunk], + case Left > 1 of + false -> + do_sized_chunk( + Rest, + Count, + Count, + [], + [reverse(Chunk) | Acc] + ); + + true -> + do_sized_chunk(Rest, Count, Left - 1, Chunk, Acc) + end + end. + +-spec sized_chunk(list(ANR), integer()) -> list(list(ANR)). +sized_chunk(List, Count) -> + do_sized_chunk(List, Count, Count, [], []). + +-spec reduce(list(ANV), fun((ANV, ANV) -> ANV)) -> {ok, ANV} | {error, nil}. +reduce(List, Fun) -> + case List of + [] -> + {error, nil}; + + [First | Rest] -> + {ok, fold(Rest, First, Fun)} + end. + +-spec do_scan(list(ANZ), AOB, list(AOB), fun((AOB, ANZ) -> AOB)) -> list(AOB). +do_scan(List, Accumulator, Accumulated, Fun) -> + case List of + [] -> + reverse(Accumulated); + + [X | Xs] -> + Next = Fun(Accumulator, X), + do_scan(Xs, Next, [Next | Accumulated], Fun) + end. + +-spec scan(list(AOE), AOG, fun((AOG, AOE) -> AOG)) -> list(AOG). +scan(List, Initial, Fun) -> + do_scan(List, Initial, [], Fun). + +-spec last(list(AOI)) -> {ok, AOI} | {error, nil}. +last(List) -> + _pipe = List, + reduce(_pipe, fun(_, Elem) -> Elem end). + +-spec combinations(list(AOM), integer()) -> list(list(AOM)). +combinations(Items, N) -> + case N of + 0 -> + [[]]; + + _ -> + case Items of + [] -> + []; + + [X | Xs] -> + First_combinations = begin + _pipe = map( + combinations(Xs, N - 1), + fun(Com) -> [X | Com] end + ), + reverse(_pipe) + end, + fold( + First_combinations, + combinations(Xs, N), + fun(Acc, C) -> [C | Acc] end + ) + end + end. + +-spec do_combination_pairs(list(AOQ)) -> list(list({AOQ, AOQ})). +do_combination_pairs(Items) -> + case Items of + [] -> + []; + + [X | Xs] -> + First_combinations = map(Xs, fun(Other) -> {X, Other} end), + [First_combinations | do_combination_pairs(Xs)] + end. + +-spec combination_pairs(list(AOU)) -> list({AOU, AOU}). +combination_pairs(Items) -> + _pipe = do_combination_pairs(Items), + concat(_pipe). + +-spec transpose(list(list(APB))) -> list(list(APB)). +transpose(List_of_list) -> + Take_first = fun(List) -> case List of + [] -> + []; + + [F] -> + [F]; + + [F@1 | _] -> + [F@1] + end end, + case List_of_list of + [] -> + []; + + [[] | Xss] -> + transpose(Xss); + + Rows -> + Firsts = begin + _pipe = Rows, + _pipe@1 = map(_pipe, Take_first), + concat(_pipe@1) + end, + Rest = transpose(map(Rows, fun(_capture) -> drop(_capture, 1) end)), + [Firsts | Rest] + end. + +-spec interleave(list(list(AOX))) -> list(AOX). +interleave(List) -> + _pipe = transpose(List), + concat(_pipe). + +-spec do_shuffle_pair_unwrap(list({float(), APG}), list(APG)) -> list(APG). +do_shuffle_pair_unwrap(List, Acc) -> + case List of + [] -> + Acc; + + [Elem_pair | Enumerable] -> + do_shuffle_pair_unwrap( + Enumerable, + [erlang:element(2, Elem_pair) | Acc] + ) + end. + +-spec do_shuffle_by_pair_indexes(list({float(), APK})) -> list({float(), APK}). +do_shuffle_by_pair_indexes(List_of_pairs) -> + sort( + List_of_pairs, + fun(A_pair, B_pair) -> + gleam@float:compare( + erlang:element(1, A_pair), + erlang:element(1, B_pair) + ) + end + ). + +-spec shuffle(list(APN)) -> list(APN). +shuffle(List) -> + _pipe = List, + _pipe@1 = fold(_pipe, [], fun(Acc, A) -> [{rand:uniform(), A} | Acc] end), + _pipe@2 = do_shuffle_by_pair_indexes(_pipe@1), + do_shuffle_pair_unwrap(_pipe@2, []). diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@map.erl b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@map.erl new file mode 100644 index 0000000..a1ed3fd --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@map.erl @@ -0,0 +1,76 @@ +-module(gleam@map). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). + +-export([size/1, to_list/1, from_list/1, has_key/2, new/0, get/2, insert/3, map_values/2, keys/1, values/1, filter/2, take/2, merge/2, delete/2, drop/2, update/3, fold/3]). + +-spec size(gleam@dict:dict(any(), any())) -> integer(). +size(Map) -> + maps:size(Map). + +-spec to_list(gleam@dict:dict(EWQ, EWR)) -> list({EWQ, EWR}). +to_list(Map) -> + maps:to_list(Map). + +-spec from_list(list({EWT, EWU})) -> gleam@dict:dict(EWT, EWU). +from_list(List) -> + maps:from_list(List). + +-spec has_key(gleam@dict:dict(EWY, any()), EWY) -> boolean(). +has_key(Map, Key) -> + gleam@dict:has_key(Map, Key). + +-spec new() -> gleam@dict:dict(any(), any()). +new() -> + gleam@dict:new(). + +-spec get(gleam@dict:dict(EXB, EXC), EXB) -> {ok, EXC} | {error, nil}. +get(From, Get) -> + gleam@dict:get(From, Get). + +-spec insert(gleam@dict:dict(EXG, EXH), EXG, EXH) -> gleam@dict:dict(EXG, EXH). +insert(Map, Key, Value) -> + gleam@dict:insert(Map, Key, Value). + +-spec map_values(gleam@dict:dict(EXK, EXL), fun((EXK, EXL) -> EXM)) -> gleam@dict:dict(EXK, EXM). +map_values(Map, Fun) -> + gleam@dict:map_values(Map, Fun). + +-spec keys(gleam@dict:dict(EXP, any())) -> list(EXP). +keys(Map) -> + gleam@dict:keys(Map). + +-spec values(gleam@dict:dict(any(), EXS)) -> list(EXS). +values(Map) -> + gleam@dict:values(Map). + +-spec filter(gleam@dict:dict(EXV, EXW), fun((EXV, EXW) -> boolean())) -> gleam@dict:dict(EXV, EXW). +filter(Map, Predicate) -> + gleam@dict:filter(Map, Predicate). + +-spec take(gleam@dict:dict(EXZ, EZT), list(EXZ)) -> gleam@dict:dict(EXZ, EZT). +take(Map, Desired_keys) -> + gleam@dict:take(Map, Desired_keys). + +-spec merge(gleam@dict:dict(EZU, EZV), gleam@dict:dict(EZU, EZV)) -> gleam@dict:dict(EZU, EZV). +merge(Map, New_entries) -> + gleam@dict:merge(Map, New_entries). + +-spec delete(gleam@dict:dict(EYG, EZX), EYG) -> gleam@dict:dict(EYG, EZX). +delete(Map, Key) -> + gleam@dict:delete(Map, Key). + +-spec drop(gleam@dict:dict(EYJ, EZZ), list(EYJ)) -> gleam@dict:dict(EYJ, EZZ). +drop(Map, Disallowed_keys) -> + gleam@dict:drop(Map, Disallowed_keys). + +-spec update( + gleam@dict:dict(EYN, EYO), + EYN, + fun((gleam@option:option(EYO)) -> EYO) +) -> gleam@dict:dict(EYN, EYO). +update(Map, Key, Fun) -> + gleam@dict:update(Map, Key, Fun). + +-spec fold(gleam@dict:dict(EYT, EYU), EYS, fun((EYS, EYT, EYU) -> EYS)) -> EYS. +fold(Map, Initial, Fun) -> + gleam@dict:fold(Map, Initial, Fun). diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@option.erl b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@option.erl new file mode 100644 index 0000000..6c9768c --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@option.erl @@ -0,0 +1,147 @@ +-module(gleam@option). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). + +-export([all/1, is_some/1, is_none/1, to_result/2, from_result/1, unwrap/2, lazy_unwrap/2, map/2, flatten/1, then/2, 'or'/2, lazy_or/2, values/1]). +-export_type([option/1]). + +-type option(GB) :: {some, GB} | none. + +-spec do_all(list(option(GC)), list(GC)) -> option(list(GC)). +do_all(List, Acc) -> + case List of + [] -> + {some, Acc}; + + [X | Rest] -> + Accumulate = fun(Acc@1, Item) -> case {Acc@1, Item} of + {{some, Values}, {some, Value}} -> + {some, [Value | Values]}; + + {_, _} -> + none + end end, + Accumulate(do_all(Rest, Acc), X) + end. + +-spec all(list(option(GI))) -> option(list(GI)). +all(List) -> + do_all(List, []). + +-spec is_some(option(any())) -> boolean(). +is_some(Option) -> + Option /= none. + +-spec is_none(option(any())) -> boolean(). +is_none(Option) -> + Option =:= none. + +-spec to_result(option(GR), GU) -> {ok, GR} | {error, GU}. +to_result(Option, E) -> + case Option of + {some, A} -> + {ok, A}; + + _ -> + {error, E} + end. + +-spec from_result({ok, GX} | {error, any()}) -> option(GX). +from_result(Result) -> + case Result of + {ok, A} -> + {some, A}; + + _ -> + none + end. + +-spec unwrap(option(HC), HC) -> HC. +unwrap(Option, Default) -> + case Option of + {some, X} -> + X; + + none -> + Default + end. + +-spec lazy_unwrap(option(HE), fun(() -> HE)) -> HE. +lazy_unwrap(Option, Default) -> + case Option of + {some, X} -> + X; + + none -> + Default() + end. + +-spec map(option(HG), fun((HG) -> HI)) -> option(HI). +map(Option, Fun) -> + case Option of + {some, X} -> + {some, Fun(X)}; + + none -> + none + end. + +-spec flatten(option(option(HK))) -> option(HK). +flatten(Option) -> + case Option of + {some, X} -> + X; + + none -> + none + end. + +-spec then(option(HO), fun((HO) -> option(HQ))) -> option(HQ). +then(Option, Fun) -> + case Option of + {some, X} -> + Fun(X); + + none -> + none + end. + +-spec 'or'(option(HT), option(HT)) -> option(HT). +'or'(First, Second) -> + case First of + {some, _} -> + First; + + none -> + Second + end. + +-spec lazy_or(option(HX), fun(() -> option(HX))) -> option(HX). +lazy_or(First, Second) -> + case First of + {some, _} -> + First; + + none -> + Second() + end. + +-spec do_values(list(option(IB)), list(IB)) -> list(IB). +do_values(List, Acc) -> + case List of + [] -> + Acc; + + [X | Xs] -> + Accumulate = fun(Acc@1, Item) -> case Item of + {some, Value} -> + [Value | Acc@1]; + + none -> + Acc@1 + end end, + Accumulate(do_values(Xs, Acc), X) + end. + +-spec values(list(option(IG))) -> list(IG). +values(Options) -> + do_values(Options, []). diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@order.erl b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@order.erl new file mode 100644 index 0000000..d4b225c --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@order.erl @@ -0,0 +1,79 @@ +-module(gleam@order). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). + +-export([negate/1, to_int/1, compare/2, max/2, min/2, reverse/1]). +-export_type([order/0]). + +-type order() :: lt | eq | gt. + +-spec negate(order()) -> order(). +negate(Order) -> + case Order of + lt -> + gt; + + eq -> + eq; + + gt -> + lt + end. + +-spec to_int(order()) -> integer(). +to_int(Order) -> + case Order of + lt -> + -1; + + eq -> + 0; + + gt -> + 1 + end. + +-spec compare(order(), order()) -> order(). +compare(A, B) -> + case {A, B} of + {X, Y} when X =:= Y -> + eq; + + {lt, _} -> + lt; + + {eq, gt} -> + lt; + + {_, _} -> + gt + end. + +-spec max(order(), order()) -> order(). +max(A, B) -> + case {A, B} of + {gt, _} -> + gt; + + {eq, lt} -> + eq; + + {_, _} -> + B + end. + +-spec min(order(), order()) -> order(). +min(A, B) -> + case {A, B} of + {lt, _} -> + lt; + + {eq, gt} -> + eq; + + {_, _} -> + B + end. + +-spec reverse(fun((I, I) -> order())) -> fun((I, I) -> order()). +reverse(Orderer) -> + fun(A, B) -> Orderer(B, A) end. diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@pair.erl b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@pair.erl new file mode 100644 index 0000000..8e5d10f --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@pair.erl @@ -0,0 +1,33 @@ +-module(gleam@pair). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). + +-export([first/1, second/1, swap/1, map_first/2, map_second/2, new/2]). + +-spec first({FM, any()}) -> FM. +first(Pair) -> + {A, _} = Pair, + A. + +-spec second({any(), FP}) -> FP. +second(Pair) -> + {_, A} = Pair, + A. + +-spec swap({FQ, FR}) -> {FR, FQ}. +swap(Pair) -> + {A, B} = Pair, + {B, A}. + +-spec map_first({FS, FT}, fun((FS) -> FU)) -> {FU, FT}. +map_first(Pair, Fun) -> + {A, B} = Pair, + {Fun(A), B}. + +-spec map_second({FV, FW}, fun((FW) -> FX)) -> {FV, FX}. +map_second(Pair, Fun) -> + {A, B} = Pair, + {A, Fun(B)}. + +-spec new(FY, FZ) -> {FY, FZ}. +new(First, Second) -> + {First, Second}. diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@queue.erl b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@queue.erl new file mode 100644 index 0000000..7c4fd02 --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@queue.erl @@ -0,0 +1,121 @@ +-module(gleam@queue). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). + +-export([new/0, from_list/1, to_list/1, is_empty/1, length/1, push_back/2, push_front/2, pop_back/1, pop_front/1, reverse/1, is_logically_equal/3, is_equal/2]). +-export_type([queue/1]). + +-opaque queue(DFL) :: {queue, list(DFL), list(DFL)}. + +-spec new() -> queue(any()). +new() -> + {queue, [], []}. + +-spec from_list(list(DFO)) -> queue(DFO). +from_list(List) -> + {queue, [], List}. + +-spec to_list(queue(DFR)) -> list(DFR). +to_list(Queue) -> + _pipe = erlang:element(3, Queue), + gleam@list:append(_pipe, gleam@list:reverse(erlang:element(2, Queue))). + +-spec is_empty(queue(any())) -> boolean(). +is_empty(Queue) -> + (erlang:element(2, Queue) =:= []) andalso (erlang:element(3, Queue) =:= []). + +-spec length(queue(any())) -> integer(). +length(Queue) -> + gleam@list:length(erlang:element(2, Queue)) + gleam@list:length( + erlang:element(3, Queue) + ). + +-spec push_back(queue(DFY), DFY) -> queue(DFY). +push_back(Queue, Item) -> + {queue, [Item | erlang:element(2, Queue)], erlang:element(3, Queue)}. + +-spec push_front(queue(DGB), DGB) -> queue(DGB). +push_front(Queue, Item) -> + {queue, erlang:element(2, Queue), [Item | erlang:element(3, Queue)]}. + +-spec pop_back(queue(DGE)) -> {ok, {DGE, queue(DGE)}} | {error, nil}. +pop_back(Queue) -> + case Queue of + {queue, [], []} -> + {error, nil}; + + {queue, [], Out} -> + pop_back({queue, gleam@list:reverse(Out), []}); + + {queue, [First | Rest], Out@1} -> + Queue@1 = {queue, Rest, Out@1}, + {ok, {First, Queue@1}} + end. + +-spec pop_front(queue(DGJ)) -> {ok, {DGJ, queue(DGJ)}} | {error, nil}. +pop_front(Queue) -> + case Queue of + {queue, [], []} -> + {error, nil}; + + {queue, In, []} -> + pop_front({queue, [], gleam@list:reverse(In)}); + + {queue, In@1, [First | Rest]} -> + Queue@1 = {queue, In@1, Rest}, + {ok, {First, Queue@1}} + end. + +-spec reverse(queue(DGO)) -> queue(DGO). +reverse(Queue) -> + {queue, erlang:element(3, Queue), erlang:element(2, Queue)}. + +-spec check_equal( + list(DGR), + list(DGR), + list(DGR), + list(DGR), + fun((DGR, DGR) -> boolean()) +) -> boolean(). +check_equal(Xs, X_tail, Ys, Y_tail, Eq) -> + case {Xs, X_tail, Ys, Y_tail} of + {[], [], [], []} -> + true; + + {[X | Xs@1], _, [Y | Ys@1], _} -> + case Eq(X, Y) of + false -> + false; + + true -> + check_equal(Xs@1, X_tail, Ys@1, Y_tail, Eq) + end; + + {[], [_ | _], _, _} -> + check_equal(gleam@list:reverse(X_tail), [], Ys, Y_tail, Eq); + + {_, _, [], [_ | _]} -> + check_equal(Xs, X_tail, gleam@list:reverse(Y_tail), [], Eq); + + {_, _, _, _} -> + false + end. + +-spec is_logically_equal(queue(DGW), queue(DGW), fun((DGW, DGW) -> boolean())) -> boolean(). +is_logically_equal(A, B, Element_is_equal) -> + check_equal( + erlang:element(3, A), + erlang:element(2, A), + erlang:element(3, B), + erlang:element(2, B), + Element_is_equal + ). + +-spec is_equal(queue(DGZ), queue(DGZ)) -> boolean(). +is_equal(A, B) -> + check_equal( + erlang:element(3, A), + erlang:element(2, A), + erlang:element(3, B), + erlang:element(2, B), + fun(A@1, B@1) -> A@1 =:= B@1 end + ). diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@regex.erl b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@regex.erl new file mode 100644 index 0000000..f49cc28 --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@regex.erl @@ -0,0 +1,33 @@ +-module(gleam@regex). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). + +-export([compile/2, from_string/1, check/2, split/2, scan/2]). +-export_type([regex/0, match/0, compile_error/0, options/0]). + +-type regex() :: any(). + +-type match() :: {match, binary(), list(gleam@option:option(binary()))}. + +-type compile_error() :: {compile_error, binary(), integer()}. + +-type options() :: {options, boolean(), boolean()}. + +-spec compile(binary(), options()) -> {ok, regex()} | {error, compile_error()}. +compile(Pattern, Options) -> + gleam_stdlib:compile_regex(Pattern, Options). + +-spec from_string(binary()) -> {ok, regex()} | {error, compile_error()}. +from_string(Pattern) -> + compile(Pattern, {options, false, false}). + +-spec check(regex(), binary()) -> boolean(). +check(Regex, Content) -> + gleam_stdlib:regex_check(Regex, Content). + +-spec split(regex(), binary()) -> list(binary()). +split(Regex, String) -> + gleam_stdlib:regex_split(Regex, String). + +-spec scan(regex(), binary()) -> list(match()). +scan(Regex, String) -> + gleam_stdlib:regex_scan(Regex, String). diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@result.erl b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@result.erl new file mode 100644 index 0000000..62dbca1 --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@result.erl @@ -0,0 +1,201 @@ +-module(gleam@result). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). + +-export([is_ok/1, is_error/1, map/2, map_error/2, flatten/1, 'try'/2, then/2, unwrap/2, lazy_unwrap/2, unwrap_error/2, unwrap_both/1, nil_error/1, 'or'/2, lazy_or/2, all/1, partition/1, replace/2, replace_error/2, values/1, try_recover/2]). + +-spec is_ok({ok, any()} | {error, any()}) -> boolean(). +is_ok(Result) -> + case Result of + {error, _} -> + false; + + {ok, _} -> + true + end. + +-spec is_error({ok, any()} | {error, any()}) -> boolean(). +is_error(Result) -> + case Result of + {ok, _} -> + false; + + {error, _} -> + true + end. + +-spec map({ok, BIZ} | {error, BJA}, fun((BIZ) -> BJD)) -> {ok, BJD} | + {error, BJA}. +map(Result, Fun) -> + case Result of + {ok, X} -> + {ok, Fun(X)}; + + {error, E} -> + {error, E} + end. + +-spec map_error({ok, BJG} | {error, BJH}, fun((BJH) -> BJK)) -> {ok, BJG} | + {error, BJK}. +map_error(Result, Fun) -> + case Result of + {ok, X} -> + {ok, X}; + + {error, Error} -> + {error, Fun(Error)} + end. + +-spec flatten({ok, {ok, BJN} | {error, BJO}} | {error, BJO}) -> {ok, BJN} | + {error, BJO}. +flatten(Result) -> + case Result of + {ok, X} -> + X; + + {error, Error} -> + {error, Error} + end. + +-spec 'try'({ok, BJV} | {error, BJW}, fun((BJV) -> {ok, BJZ} | {error, BJW})) -> {ok, + BJZ} | + {error, BJW}. +'try'(Result, Fun) -> + case Result of + {ok, X} -> + Fun(X); + + {error, E} -> + {error, E} + end. + +-spec then({ok, BKE} | {error, BKF}, fun((BKE) -> {ok, BKI} | {error, BKF})) -> {ok, + BKI} | + {error, BKF}. +then(Result, Fun) -> + 'try'(Result, Fun). + +-spec unwrap({ok, BKN} | {error, any()}, BKN) -> BKN. +unwrap(Result, Default) -> + case Result of + {ok, V} -> + V; + + {error, _} -> + Default + end. + +-spec lazy_unwrap({ok, BKR} | {error, any()}, fun(() -> BKR)) -> BKR. +lazy_unwrap(Result, Default) -> + case Result of + {ok, V} -> + V; + + {error, _} -> + Default() + end. + +-spec unwrap_error({ok, any()} | {error, BKW}, BKW) -> BKW. +unwrap_error(Result, Default) -> + case Result of + {ok, _} -> + Default; + + {error, E} -> + E + end. + +-spec unwrap_both({ok, BKZ} | {error, BKZ}) -> BKZ. +unwrap_both(Result) -> + case Result of + {ok, A} -> + A; + + {error, A@1} -> + A@1 + end. + +-spec nil_error({ok, BLC} | {error, any()}) -> {ok, BLC} | {error, nil}. +nil_error(Result) -> + map_error(Result, fun(_) -> nil end). + +-spec 'or'({ok, BLI} | {error, BLJ}, {ok, BLI} | {error, BLJ}) -> {ok, BLI} | + {error, BLJ}. +'or'(First, Second) -> + case First of + {ok, _} -> + First; + + {error, _} -> + Second + end. + +-spec lazy_or({ok, BLQ} | {error, BLR}, fun(() -> {ok, BLQ} | {error, BLR})) -> {ok, + BLQ} | + {error, BLR}. +lazy_or(First, Second) -> + case First of + {ok, _} -> + First; + + {error, _} -> + Second() + end. + +-spec all(list({ok, BLY} | {error, BLZ})) -> {ok, list(BLY)} | {error, BLZ}. +all(Results) -> + gleam@list:try_map(Results, fun(X) -> X end). + +-spec do_partition(list({ok, BMN} | {error, BMO}), list(BMN), list(BMO)) -> {list(BMN), + list(BMO)}. +do_partition(Results, Oks, Errors) -> + case Results of + [] -> + {Oks, Errors}; + + [{ok, A} | Rest] -> + do_partition(Rest, [A | Oks], Errors); + + [{error, E} | Rest@1] -> + do_partition(Rest@1, Oks, [E | Errors]) + end. + +-spec partition(list({ok, BMG} | {error, BMH})) -> {list(BMG), list(BMH)}. +partition(Results) -> + do_partition(Results, [], []). + +-spec replace({ok, any()} | {error, BMW}, BMZ) -> {ok, BMZ} | {error, BMW}. +replace(Result, Value) -> + case Result of + {ok, _} -> + {ok, Value}; + + {error, Error} -> + {error, Error} + end. + +-spec replace_error({ok, BNC} | {error, any()}, BNG) -> {ok, BNC} | {error, BNG}. +replace_error(Result, Error) -> + case Result of + {ok, X} -> + {ok, X}; + + {error, _} -> + {error, Error} + end. + +-spec values(list({ok, BNJ} | {error, any()})) -> list(BNJ). +values(Results) -> + gleam@list:filter_map(Results, fun(R) -> R end). + +-spec try_recover( + {ok, BNP} | {error, BNQ}, + fun((BNQ) -> {ok, BNP} | {error, BNT}) +) -> {ok, BNP} | {error, BNT}. +try_recover(Result, Fun) -> + case Result of + {ok, Value} -> + {ok, Value}; + + {error, Error} -> + Fun(Error) + end. diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@set.erl b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@set.erl new file mode 100644 index 0000000..035386c --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@set.erl @@ -0,0 +1,85 @@ +-module(gleam@set). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). + +-export([new/0, size/1, insert/2, contains/2, delete/2, to_list/1, from_list/1, fold/3, filter/2, drop/2, take/2, union/2, intersection/2]). +-export_type([set/1]). + +-opaque set(FAG) :: {set, gleam@dict:dict(FAG, list(nil))}. + +-spec new() -> set(any()). +new() -> + {set, gleam@dict:new()}. + +-spec size(set(any())) -> integer(). +size(Set) -> + maps:size(erlang:element(2, Set)). + +-spec insert(set(FAM), FAM) -> set(FAM). +insert(Set, Member) -> + {set, gleam@dict:insert(erlang:element(2, Set), Member, [])}. + +-spec contains(set(FAP), FAP) -> boolean(). +contains(Set, Member) -> + _pipe = erlang:element(2, Set), + _pipe@1 = gleam@dict:get(_pipe, Member), + gleam@result:is_ok(_pipe@1). + +-spec delete(set(FAR), FAR) -> set(FAR). +delete(Set, Member) -> + {set, gleam@dict:delete(erlang:element(2, Set), Member)}. + +-spec to_list(set(FAU)) -> list(FAU). +to_list(Set) -> + gleam@dict:keys(erlang:element(2, Set)). + +-spec from_list(list(FAX)) -> set(FAX). +from_list(Members) -> + Map = gleam@list:fold( + Members, + gleam@dict:new(), + fun(M, K) -> gleam@dict:insert(M, K, []) end + ), + {set, Map}. + +-spec fold(set(FBA), FBC, fun((FBC, FBA) -> FBC)) -> FBC. +fold(Set, Initial, Reducer) -> + gleam@dict:fold( + erlang:element(2, Set), + Initial, + fun(A, K, _) -> Reducer(A, K) end + ). + +-spec filter(set(FBD), fun((FBD) -> boolean())) -> set(FBD). +filter(Set, Predicate) -> + {set, + gleam@dict:filter(erlang:element(2, Set), fun(M, _) -> Predicate(M) end)}. + +-spec drop(set(FBG), list(FBG)) -> set(FBG). +drop(Set, Disallowed) -> + gleam@list:fold(Disallowed, Set, fun delete/2). + +-spec take(set(FBK), list(FBK)) -> set(FBK). +take(Set, Desired) -> + {set, gleam@dict:take(erlang:element(2, Set), Desired)}. + +-spec order(set(FBO), set(FBO)) -> {set(FBO), set(FBO)}. +order(First, Second) -> + case maps:size(erlang:element(2, First)) > maps:size( + erlang:element(2, Second) + ) of + true -> + {First, Second}; + + false -> + {Second, First} + end. + +-spec union(set(FBT), set(FBT)) -> set(FBT). +union(First, Second) -> + {Larger, Smaller} = order(First, Second), + fold(Smaller, Larger, fun insert/2). + +-spec intersection(set(FBX), set(FBX)) -> set(FBX). +intersection(First, Second) -> + {Larger, Smaller} = order(First, Second), + take(Larger, to_list(Smaller)). diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@string.erl b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@string.erl new file mode 100644 index 0000000..5f000af --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@string.erl @@ -0,0 +1,352 @@ +-module(gleam@string). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). + +-export([is_empty/1, length/1, reverse/1, replace/3, lowercase/1, uppercase/1, compare/2, crop/2, contains/2, starts_with/2, ends_with/2, split_once/2, append/2, concat/1, repeat/2, join/2, trim/1, trim_left/1, trim_right/1, pop_grapheme/1, to_graphemes/1, slice/3, drop_left/2, drop_right/2, split/2, pad_left/3, pad_right/3, to_utf_codepoints/1, from_utf_codepoints/1, utf_codepoint/1, utf_codepoint_to_int/1, to_option/1, first/1, last/1, capitalise/1, inspect/1, byte_size/1]). +-export_type([direction/0]). + +-type direction() :: leading | trailing | both. + +-spec is_empty(binary()) -> boolean(). +is_empty(Str) -> + Str =:= <<""/utf8>>. + +-spec length(binary()) -> integer(). +length(String) -> + string:length(String). + +-spec do_reverse(binary()) -> binary(). +do_reverse(String) -> + _pipe = String, + _pipe@1 = gleam@string_builder:from_string(_pipe), + _pipe@2 = gleam@string_builder:reverse(_pipe@1), + gleam@string_builder:to_string(_pipe@2). + +-spec reverse(binary()) -> binary(). +reverse(String) -> + do_reverse(String). + +-spec replace(binary(), binary(), binary()) -> binary(). +replace(String, Pattern, Substitute) -> + _pipe = String, + _pipe@1 = gleam@string_builder:from_string(_pipe), + _pipe@2 = gleam@string_builder:replace(_pipe@1, Pattern, Substitute), + gleam@string_builder:to_string(_pipe@2). + +-spec lowercase(binary()) -> binary(). +lowercase(String) -> + string:lowercase(String). + +-spec uppercase(binary()) -> binary(). +uppercase(String) -> + string:uppercase(String). + +-spec compare(binary(), binary()) -> gleam@order:order(). +compare(A, B) -> + case A =:= B of + true -> + eq; + + _ -> + case gleam_stdlib:less_than(A, B) of + true -> + lt; + + _ -> + gt + end + end. + +-spec crop(binary(), binary()) -> binary(). +crop(String, Substring) -> + gleam_stdlib:crop_string(String, Substring). + +-spec contains(binary(), binary()) -> boolean(). +contains(Haystack, Needle) -> + gleam_stdlib:contains_string(Haystack, Needle). + +-spec starts_with(binary(), binary()) -> boolean(). +starts_with(String, Prefix) -> + gleam_stdlib:string_starts_with(String, Prefix). + +-spec ends_with(binary(), binary()) -> boolean(). +ends_with(String, Suffix) -> + gleam_stdlib:string_ends_with(String, Suffix). + +-spec do_split_once(binary(), binary()) -> {ok, {binary(), binary()}} | + {error, nil}. +do_split_once(X, Substring) -> + case string:split(X, Substring) of + [First, Rest] -> + {ok, {First, Rest}}; + + _ -> + {error, nil} + end. + +-spec split_once(binary(), binary()) -> {ok, {binary(), binary()}} | + {error, nil}. +split_once(X, Substring) -> + do_split_once(X, Substring). + +-spec append(binary(), binary()) -> binary(). +append(First, Second) -> + _pipe = First, + _pipe@1 = gleam@string_builder:from_string(_pipe), + _pipe@2 = gleam@string_builder:append(_pipe@1, Second), + gleam@string_builder:to_string(_pipe@2). + +-spec concat(list(binary())) -> binary(). +concat(Strings) -> + _pipe = Strings, + _pipe@1 = gleam@string_builder:from_strings(_pipe), + gleam@string_builder:to_string(_pipe@1). + +-spec repeat(binary(), integer()) -> binary(). +repeat(String, Times) -> + _pipe = gleam@iterator:repeat(String), + _pipe@1 = gleam@iterator:take(_pipe, Times), + _pipe@2 = gleam@iterator:to_list(_pipe@1), + concat(_pipe@2). + +-spec do_join(list(binary()), binary()) -> binary(). +do_join(Strings, Separator) -> + _pipe = Strings, + _pipe@1 = gleam@list:intersperse(_pipe, Separator), + concat(_pipe@1). + +-spec join(list(binary()), binary()) -> binary(). +join(Strings, Separator) -> + do_join(Strings, Separator). + +-spec do_trim(binary()) -> binary(). +do_trim(String) -> + string:trim(String, both). + +-spec trim(binary()) -> binary(). +trim(String) -> + do_trim(String). + +-spec do_trim_left(binary()) -> binary(). +do_trim_left(String) -> + string:trim(String, leading). + +-spec trim_left(binary()) -> binary(). +trim_left(String) -> + do_trim_left(String). + +-spec do_trim_right(binary()) -> binary(). +do_trim_right(String) -> + string:trim(String, trailing). + +-spec trim_right(binary()) -> binary(). +trim_right(String) -> + do_trim_right(String). + +-spec pop_grapheme(binary()) -> {ok, {binary(), binary()}} | {error, nil}. +pop_grapheme(String) -> + gleam_stdlib:string_pop_grapheme(String). + +-spec do_to_graphemes(binary(), list(binary())) -> list(binary()). +do_to_graphemes(String, Acc) -> + case pop_grapheme(String) of + {ok, {Grapheme, Rest}} -> + do_to_graphemes(Rest, [Grapheme | Acc]); + + _ -> + Acc + end. + +-spec to_graphemes(binary()) -> list(binary()). +to_graphemes(String) -> + _pipe = do_to_graphemes(String, []), + gleam@list:reverse(_pipe). + +-spec slice(binary(), integer(), integer()) -> binary(). +slice(String, Idx, Len) -> + case Len < 0 of + true -> + <<""/utf8>>; + + false -> + case Idx < 0 of + true -> + Translated_idx = length(String) + Idx, + case Translated_idx < 0 of + true -> + <<""/utf8>>; + + false -> + string:slice(String, Translated_idx, Len) + end; + + false -> + string:slice(String, Idx, Len) + end + end. + +-spec drop_left(binary(), integer()) -> binary(). +drop_left(String, Num_graphemes) -> + case Num_graphemes < 0 of + true -> + String; + + false -> + slice(String, Num_graphemes, length(String) - Num_graphemes) + end. + +-spec drop_right(binary(), integer()) -> binary(). +drop_right(String, Num_graphemes) -> + case Num_graphemes < 0 of + true -> + String; + + false -> + slice(String, 0, length(String) - Num_graphemes) + end. + +-spec split(binary(), binary()) -> list(binary()). +split(X, Substring) -> + case Substring of + <<""/utf8>> -> + to_graphemes(X); + + _ -> + _pipe = X, + _pipe@1 = gleam@string_builder:from_string(_pipe), + _pipe@2 = gleam@string_builder:split(_pipe@1, Substring), + gleam@list:map(_pipe@2, fun gleam@string_builder:to_string/1) + end. + +-spec padding(integer(), binary()) -> gleam@iterator:iterator(binary()). +padding(Size, Pad_string) -> + Pad_length = length(Pad_string), + Num_pads = case Pad_length of + 0 -> 0; + Gleam@denominator -> Size div Gleam@denominator + end, + Extra = case Pad_length of + 0 -> 0; + Gleam@denominator@1 -> Size rem Gleam@denominator@1 + end, + _pipe = gleam@iterator:repeat(Pad_string), + _pipe@1 = gleam@iterator:take(_pipe, Num_pads), + gleam@iterator:append( + _pipe@1, + gleam@iterator:single(slice(Pad_string, 0, Extra)) + ). + +-spec pad_left(binary(), integer(), binary()) -> binary(). +pad_left(String, Desired_length, Pad_string) -> + Current_length = length(String), + To_pad_length = Desired_length - Current_length, + _pipe = padding(To_pad_length, Pad_string), + _pipe@1 = gleam@iterator:append(_pipe, gleam@iterator:single(String)), + _pipe@2 = gleam@iterator:to_list(_pipe@1), + concat(_pipe@2). + +-spec pad_right(binary(), integer(), binary()) -> binary(). +pad_right(String, Desired_length, Pad_string) -> + Current_length = length(String), + To_pad_length = Desired_length - Current_length, + _pipe = gleam@iterator:single(String), + _pipe@1 = gleam@iterator:append(_pipe, padding(To_pad_length, Pad_string)), + _pipe@2 = gleam@iterator:to_list(_pipe@1), + concat(_pipe@2). + +-spec do_to_utf_codepoints_impl(bitstring(), list(integer())) -> list(integer()). +do_to_utf_codepoints_impl(Bit_array, Acc) -> + case Bit_array of + <<First/utf8, Rest/binary>> -> + do_to_utf_codepoints_impl(Rest, [First | Acc]); + + _ -> + Acc + end. + +-spec do_to_utf_codepoints(binary()) -> list(integer()). +do_to_utf_codepoints(String) -> + _pipe = do_to_utf_codepoints_impl(<<String/binary>>, []), + gleam@list:reverse(_pipe). + +-spec to_utf_codepoints(binary()) -> list(integer()). +to_utf_codepoints(String) -> + do_to_utf_codepoints(String). + +-spec from_utf_codepoints(list(integer())) -> binary(). +from_utf_codepoints(Utf_codepoints) -> + gleam_stdlib:utf_codepoint_list_to_string(Utf_codepoints). + +-spec utf_codepoint(integer()) -> {ok, integer()} | {error, nil}. +utf_codepoint(Value) -> + case Value of + I when I > 1114111 -> + {error, nil}; + + 65534 -> + {error, nil}; + + 65535 -> + {error, nil}; + + I@1 when (I@1 >= 55296) andalso (I@1 =< 57343) -> + {error, nil}; + + I@2 -> + {ok, gleam_stdlib:identity(I@2)} + end. + +-spec utf_codepoint_to_int(integer()) -> integer(). +utf_codepoint_to_int(Cp) -> + gleam_stdlib:identity(Cp). + +-spec to_option(binary()) -> gleam@option:option(binary()). +to_option(S) -> + case S of + <<""/utf8>> -> + none; + + _ -> + {some, S} + end. + +-spec first(binary()) -> {ok, binary()} | {error, nil}. +first(S) -> + case pop_grapheme(S) of + {ok, {First, _}} -> + {ok, First}; + + {error, E} -> + {error, E} + end. + +-spec last(binary()) -> {ok, binary()} | {error, nil}. +last(S) -> + case pop_grapheme(S) of + {ok, {First, <<""/utf8>>}} -> + {ok, First}; + + {ok, {_, Rest}} -> + {ok, slice(Rest, -1, 1)}; + + {error, E} -> + {error, E} + end. + +-spec capitalise(binary()) -> binary(). +capitalise(S) -> + case pop_grapheme(S) of + {ok, {First, Rest}} -> + append(uppercase(First), lowercase(Rest)); + + _ -> + <<""/utf8>> + end. + +-spec inspect(any()) -> binary(). +inspect(Term) -> + _pipe = gleam_stdlib:inspect(Term), + gleam@string_builder:to_string(_pipe). + +-spec byte_size(binary()) -> integer(). +byte_size(String) -> + erlang:byte_size(String). diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@string_builder.erl b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@string_builder.erl new file mode 100644 index 0000000..068bcc1 --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@string_builder.erl @@ -0,0 +1,91 @@ +-module(gleam@string_builder). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). + +-export([prepend_builder/2, append_builder/2, new/0, from_strings/1, concat/1, from_string/1, prepend/2, append/2, to_string/1, byte_size/1, join/2, lowercase/1, uppercase/1, reverse/1, split/2, replace/3, is_equal/2, is_empty/1]). +-export_type([string_builder/0, direction/0]). + +-type string_builder() :: any(). + +-type direction() :: all. + +-spec prepend_builder(string_builder(), string_builder()) -> string_builder(). +prepend_builder(Builder, Prefix) -> + gleam_stdlib:iodata_append(Prefix, Builder). + +-spec append_builder(string_builder(), string_builder()) -> string_builder(). +append_builder(Builder, Suffix) -> + gleam_stdlib:iodata_append(Builder, Suffix). + +-spec new() -> string_builder(). +new() -> + gleam_stdlib:identity([]). + +-spec from_strings(list(binary())) -> string_builder(). +from_strings(Strings) -> + gleam_stdlib:identity(Strings). + +-spec concat(list(string_builder())) -> string_builder(). +concat(Builders) -> + gleam_stdlib:identity(Builders). + +-spec from_string(binary()) -> string_builder(). +from_string(String) -> + gleam_stdlib:identity(String). + +-spec prepend(string_builder(), binary()) -> string_builder(). +prepend(Builder, Prefix) -> + append_builder(from_string(Prefix), Builder). + +-spec append(string_builder(), binary()) -> string_builder(). +append(Builder, Second) -> + append_builder(Builder, from_string(Second)). + +-spec to_string(string_builder()) -> binary(). +to_string(Builder) -> + unicode:characters_to_binary(Builder). + +-spec byte_size(string_builder()) -> integer(). +byte_size(Builder) -> + erlang:iolist_size(Builder). + +-spec join(list(string_builder()), binary()) -> string_builder(). +join(Builders, Sep) -> + _pipe = Builders, + _pipe@1 = gleam@list:intersperse(_pipe, from_string(Sep)), + concat(_pipe@1). + +-spec lowercase(string_builder()) -> string_builder(). +lowercase(Builder) -> + string:lowercase(Builder). + +-spec uppercase(string_builder()) -> string_builder(). +uppercase(Builder) -> + string:uppercase(Builder). + +-spec reverse(string_builder()) -> string_builder(). +reverse(Builder) -> + string:reverse(Builder). + +-spec do_split(string_builder(), binary()) -> list(string_builder()). +do_split(Iodata, Pattern) -> + string:split(Iodata, Pattern, all). + +-spec split(string_builder(), binary()) -> list(string_builder()). +split(Iodata, Pattern) -> + do_split(Iodata, Pattern). + +-spec do_replace(string_builder(), binary(), binary()) -> string_builder(). +do_replace(Iodata, Pattern, Substitute) -> + string:replace(Iodata, Pattern, Substitute, all). + +-spec replace(string_builder(), binary(), binary()) -> string_builder(). +replace(Builder, Pattern, Substitute) -> + do_replace(Builder, Pattern, Substitute). + +-spec is_equal(string_builder(), string_builder()) -> boolean(). +is_equal(A, B) -> + string:equal(A, B). + +-spec is_empty(string_builder()) -> boolean(). +is_empty(Builder) -> + string:is_empty(Builder). diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@uri.erl b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@uri.erl new file mode 100644 index 0000000..3a067d6 --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam@uri.erl @@ -0,0 +1,252 @@ +-module(gleam@uri). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). + +-export([parse/1, parse_query/1, percent_encode/1, query_to_string/1, percent_decode/1, path_segments/1, to_string/1, origin/1, merge/2]). +-export_type([uri/0]). + +-type uri() :: {uri, + gleam@option:option(binary()), + gleam@option:option(binary()), + gleam@option:option(binary()), + gleam@option:option(integer()), + binary(), + gleam@option:option(binary()), + gleam@option:option(binary())}. + +-spec parse(binary()) -> {ok, uri()} | {error, nil}. +parse(Uri_string) -> + gleam_stdlib:uri_parse(Uri_string). + +-spec parse_query(binary()) -> {ok, list({binary(), binary()})} | {error, nil}. +parse_query(Query) -> + gleam_stdlib:parse_query(Query). + +-spec percent_encode(binary()) -> binary(). +percent_encode(Value) -> + gleam_stdlib:percent_encode(Value). + +-spec query_pair({binary(), binary()}) -> gleam@string_builder:string_builder(). +query_pair(Pair) -> + gleam@string_builder:from_strings( + [percent_encode(erlang:element(1, Pair)), + <<"="/utf8>>, + percent_encode(erlang:element(2, Pair))] + ). + +-spec query_to_string(list({binary(), binary()})) -> binary(). +query_to_string(Query) -> + _pipe = Query, + _pipe@1 = gleam@list:map(_pipe, fun query_pair/1), + _pipe@2 = gleam@list:intersperse( + _pipe@1, + gleam@string_builder:from_string(<<"&"/utf8>>) + ), + _pipe@3 = gleam@string_builder:concat(_pipe@2), + gleam@string_builder:to_string(_pipe@3). + +-spec percent_decode(binary()) -> {ok, binary()} | {error, nil}. +percent_decode(Value) -> + gleam_stdlib:percent_decode(Value). + +-spec do_remove_dot_segments(list(binary()), list(binary())) -> list(binary()). +do_remove_dot_segments(Input, Accumulator) -> + case Input of + [] -> + gleam@list:reverse(Accumulator); + + [Segment | Rest] -> + Accumulator@5 = case {Segment, Accumulator} of + {<<""/utf8>>, Accumulator@1} -> + Accumulator@1; + + {<<"."/utf8>>, Accumulator@2} -> + Accumulator@2; + + {<<".."/utf8>>, []} -> + []; + + {<<".."/utf8>>, [_ | Accumulator@3]} -> + Accumulator@3; + + {Segment@1, Accumulator@4} -> + [Segment@1 | Accumulator@4] + end, + do_remove_dot_segments(Rest, Accumulator@5) + end. + +-spec remove_dot_segments(list(binary())) -> list(binary()). +remove_dot_segments(Input) -> + do_remove_dot_segments(Input, []). + +-spec path_segments(binary()) -> list(binary()). +path_segments(Path) -> + remove_dot_segments(gleam@string:split(Path, <<"/"/utf8>>)). + +-spec to_string(uri()) -> binary(). +to_string(Uri) -> + Parts = case erlang:element(8, Uri) of + {some, Fragment} -> + [<<"#"/utf8>>, Fragment]; + + _ -> + [] + end, + Parts@1 = case erlang:element(7, Uri) of + {some, Query} -> + [<<"?"/utf8>>, Query | Parts]; + + _ -> + Parts + end, + Parts@2 = [erlang:element(6, Uri) | Parts@1], + Parts@3 = case {erlang:element(4, Uri), + gleam@string:starts_with(erlang:element(6, Uri), <<"/"/utf8>>)} of + {{some, Host}, false} when Host =/= <<""/utf8>> -> + [<<"/"/utf8>> | Parts@2]; + + {_, _} -> + Parts@2 + end, + Parts@4 = case {erlang:element(4, Uri), erlang:element(5, Uri)} of + {{some, _}, {some, Port}} -> + [<<":"/utf8>>, gleam@int:to_string(Port) | Parts@3]; + + {_, _} -> + Parts@3 + end, + Parts@5 = case {erlang:element(2, Uri), + erlang:element(3, Uri), + erlang:element(4, Uri)} of + {{some, S}, {some, U}, {some, H}} -> + [S, <<"://"/utf8>>, U, <<"@"/utf8>>, H | Parts@4]; + + {{some, S@1}, none, {some, H@1}} -> + [S@1, <<"://"/utf8>>, H@1 | Parts@4]; + + {{some, S@2}, {some, _}, none} -> + [S@2, <<":"/utf8>> | Parts@4]; + + {{some, S@2}, none, none} -> + [S@2, <<":"/utf8>> | Parts@4]; + + {none, none, {some, H@2}} -> + [<<"//"/utf8>>, H@2 | Parts@4]; + + {_, _, _} -> + Parts@4 + end, + gleam@string:concat(Parts@5). + +-spec origin(uri()) -> {ok, binary()} | {error, nil}. +origin(Uri) -> + {uri, Scheme, _, Host, Port, _, _, _} = Uri, + case Scheme of + {some, <<"https"/utf8>>} when Port =:= {some, 443} -> + Origin = {uri, Scheme, none, Host, none, <<""/utf8>>, none, none}, + {ok, to_string(Origin)}; + + {some, <<"http"/utf8>>} when Port =:= {some, 80} -> + Origin@1 = {uri, Scheme, none, Host, none, <<""/utf8>>, none, none}, + {ok, to_string(Origin@1)}; + + {some, S} when (S =:= <<"http"/utf8>>) orelse (S =:= <<"https"/utf8>>) -> + Origin@2 = {uri, Scheme, none, Host, Port, <<""/utf8>>, none, none}, + {ok, to_string(Origin@2)}; + + _ -> + {error, nil} + end. + +-spec drop_last(list(FFS)) -> list(FFS). +drop_last(Elements) -> + gleam@list:take(Elements, gleam@list:length(Elements) - 1). + +-spec join_segments(list(binary())) -> binary(). +join_segments(Segments) -> + gleam@string:join([<<""/utf8>> | Segments], <<"/"/utf8>>). + +-spec merge(uri(), uri()) -> {ok, uri()} | {error, nil}. +merge(Base, Relative) -> + case Base of + {uri, {some, _}, _, {some, _}, _, _, _, _} -> + case Relative of + {uri, _, _, {some, _}, _, _, _, _} -> + Path = begin + _pipe = gleam@string:split( + erlang:element(6, Relative), + <<"/"/utf8>> + ), + _pipe@1 = remove_dot_segments(_pipe), + join_segments(_pipe@1) + end, + Resolved = {uri, + gleam@option:'or'( + erlang:element(2, Relative), + erlang:element(2, Base) + ), + none, + erlang:element(4, Relative), + gleam@option:'or'( + erlang:element(5, Relative), + erlang:element(5, Base) + ), + Path, + erlang:element(7, Relative), + erlang:element(8, Relative)}, + {ok, Resolved}; + + _ -> + {New_path, New_query} = case erlang:element(6, Relative) of + <<""/utf8>> -> + {erlang:element(6, Base), + gleam@option:'or'( + erlang:element(7, Relative), + erlang:element(7, Base) + )}; + + _ -> + Path_segments = case gleam@string:starts_with( + erlang:element(6, Relative), + <<"/"/utf8>> + ) of + true -> + gleam@string:split( + erlang:element(6, Relative), + <<"/"/utf8>> + ); + + false -> + _pipe@2 = gleam@string:split( + erlang:element(6, Base), + <<"/"/utf8>> + ), + _pipe@3 = drop_last(_pipe@2), + gleam@list:append( + _pipe@3, + gleam@string:split( + erlang:element(6, Relative), + <<"/"/utf8>> + ) + ) + end, + Path@1 = begin + _pipe@4 = Path_segments, + _pipe@5 = remove_dot_segments(_pipe@4), + join_segments(_pipe@5) + end, + {Path@1, erlang:element(7, Relative)} + end, + Resolved@1 = {uri, + erlang:element(2, Base), + none, + erlang:element(4, Base), + erlang:element(5, Base), + New_path, + New_query, + erlang:element(8, Relative)}, + {ok, Resolved@1} + end; + + _ -> + {error, nil} + end. diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam_stdlib.app.src b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam_stdlib.app.src new file mode 100644 index 0000000..ddec67a --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam_stdlib.app.src @@ -0,0 +1,31 @@ +{application, gleam_stdlib, [ + {vsn, "0.34.0"}, + {applications, []}, + {description, "A standard library for the Gleam programming language"}, + {modules, [gleam@base, + gleam@bit_array, + gleam@bit_builder, + gleam@bit_string, + gleam@bool, + gleam@bytes_builder, + gleam@dict, + gleam@dynamic, + gleam@float, + gleam@function, + gleam@int, + gleam@io, + gleam@iterator, + gleam@list, + gleam@map, + gleam@option, + gleam@order, + gleam@pair, + gleam@queue, + gleam@regex, + gleam@result, + gleam@set, + gleam@string, + gleam@string_builder, + gleam@uri]}, + {registered, []} +]}. diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam_stdlib.erl b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam_stdlib.erl new file mode 100644 index 0000000..c6ea125 --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam_stdlib.erl @@ -0,0 +1,529 @@ +-module(gleam_stdlib). + +-export([ + map_get/2, iodata_append/2, identity/1, decode_int/1, decode_bool/1, + decode_float/1, decode_list/1, decode_option/2, decode_field/2, parse_int/1, + parse_float/1, less_than/2, string_pop_grapheme/1, string_starts_with/2, + wrap_list/1, string_ends_with/2, string_pad/4, decode_map/1, uri_parse/1, + bit_array_int_to_u32/1, bit_array_int_from_u32/1, decode_result/1, + bit_array_slice/3, decode_bit_array/1, compile_regex/2, regex_scan/2, + percent_encode/1, percent_decode/1, regex_check/2, regex_split/2, + base_decode64/1, parse_query/1, bit_array_concat/1, size_of_tuple/1, + decode_tuple/1, decode_tuple2/1, decode_tuple3/1, decode_tuple4/1, + decode_tuple5/1, decode_tuple6/1, tuple_get/2, classify_dynamic/1, print/1, + println/1, print_error/1, println_error/1, inspect/1, float_to_string/1, + int_from_base_string/2, utf_codepoint_list_to_string/1, contains_string/2, + crop_string/2, base16_decode/1 +]). + +%% Taken from OTP's uri_string module +-define(DEC2HEX(X), + if ((X) >= 0) andalso ((X) =< 9) -> (X) + $0; + ((X) >= 10) andalso ((X) =< 15) -> (X) + $A - 10 + end). + +%% Taken from OTP's uri_string module +-define(HEX2DEC(X), + if ((X) >= $0) andalso ((X) =< $9) -> (X) - $0; + ((X) >= $A) andalso ((X) =< $F) -> (X) - $A + 10; + ((X) >= $a) andalso ((X) =< $f) -> (X) - $a + 10 + end). + +-define(is_lowercase_char(X), (X > 96 andalso X < 123)). +-define(is_underscore_char(X), (X == 95)). +-define(is_digit_char(X), (X > 47 andalso X < 58)). + +uppercase(X) -> X - 32. + +map_get(Map, Key) -> + case maps:find(Key, Map) of + error -> {error, nil}; + OkFound -> OkFound + end. + +iodata_append(Iodata, String) -> [Iodata, String]. + +identity(X) -> X. + +decode_error_msg(Expected, Data) when is_binary(Expected) -> + decode_error(Expected, classify_dynamic(Data)). +decode_error(Expected, Got) when is_binary(Expected) andalso is_binary(Got) -> + {error, [{decode_error, Expected, Got, []}]}. + +classify_dynamic(nil) -> <<"Nil">>; +classify_dynamic(X) when is_atom(X) -> <<"Atom">>; +classify_dynamic(X) when is_binary(X) -> <<"String">>; +classify_dynamic(X) when is_bitstring(X) -> <<"BitArray">>; +classify_dynamic(X) when is_integer(X) -> <<"Int">>; +classify_dynamic(X) when is_float(X) -> <<"Float">>; +classify_dynamic(X) when is_list(X) -> <<"List">>; +classify_dynamic(X) when is_boolean(X) -> <<"Bool">>; +classify_dynamic(X) when is_map(X) -> <<"Map">>; +classify_dynamic(X) when is_tuple(X) -> + iolist_to_binary(["Tuple of ", integer_to_list(tuple_size(X)), " elements"]); +classify_dynamic(X) when + is_function(X, 0) orelse is_function(X, 1) orelse is_function(X, 2) orelse + is_function(X, 3) orelse is_function(X, 4) orelse is_function(X, 5) orelse + is_function(X, 6) orelse is_function(X, 7) orelse is_function(X, 8) orelse + is_function(X, 9) orelse is_function(X, 10) orelse is_function(X, 11) orelse + is_function(X, 12) -> <<"Function">>; +classify_dynamic(_) -> <<"Some other type">>. + +decode_map(Data) when is_map(Data) -> {ok, Data}; +decode_map(Data) -> decode_error_msg(<<"Map">>, Data). + +decode_bit_array(Data) when is_bitstring(Data) -> {ok, Data}; +decode_bit_array(Data) -> decode_error_msg(<<"BitArray">>, Data). + +decode_int(Data) when is_integer(Data) -> {ok, Data}; +decode_int(Data) -> decode_error_msg(<<"Int">>, Data). + +decode_float(Data) when is_float(Data) -> {ok, Data}; +decode_float(Data) -> decode_error_msg(<<"Float">>, Data). + +decode_bool(Data) when is_boolean(Data) -> {ok, Data}; +decode_bool(Data) -> decode_error_msg(<<"Bool">>, Data). + +decode_list(Data) when is_list(Data) -> {ok, Data}; +decode_list(Data) -> decode_error_msg(<<"List">>, Data). + +decode_field(Data, Key) when is_map(Data) -> + case Data of + #{Key := Value} -> {ok, {some, Value}}; + _ -> + {ok, none} + end; +decode_field(Data, _) -> + decode_error_msg(<<"Map">>, Data). + +size_of_tuple(Data) -> tuple_size(Data). + +tuple_get(_tup, Index) when Index < 0 -> {error, nil}; +tuple_get(Data, Index) when Index >= tuple_size(Data) -> {error, nil}; +tuple_get(Data, Index) -> {ok, element(Index + 1, Data)}. + +decode_tuple(Data) when is_tuple(Data) -> {ok, Data}; +decode_tuple(Data) -> decode_error_msg(<<"Tuple">>, Data). + +decode_tuple2({_,_} = A) -> {ok, A}; +decode_tuple2([A,B]) -> {ok, {A,B}}; +decode_tuple2(Data) -> decode_error_msg(<<"Tuple of 2 elements">>, Data). + +decode_tuple3({_,_,_} = A) -> {ok, A}; +decode_tuple3([A,B,C]) -> {ok, {A,B,C}}; +decode_tuple3(Data) -> decode_error_msg(<<"Tuple of 3 elements">>, Data). + +decode_tuple4({_,_,_,_} = A) -> {ok, A}; +decode_tuple4([A,B,C,D]) -> {ok, {A,B,C,D}}; +decode_tuple4(Data) -> decode_error_msg(<<"Tuple of 4 elements">>, Data). + +decode_tuple5({_,_,_,_,_} = A) -> {ok, A}; +decode_tuple5([A,B,C,D,E]) -> {ok, {A,B,C,D,E}}; +decode_tuple5(Data) -> decode_error_msg(<<"Tuple of 5 elements">>, Data). + +decode_tuple6({_,_,_,_,_,_} = A) -> {ok, A}; +decode_tuple6([A,B,C,D,E,F]) -> {ok, {A,B,C,D,E,F}}; +decode_tuple6(Data) -> decode_error_msg(<<"Tuple of 6 elements">>, Data). + +decode_option(Term, F) -> + Decode = fun(Inner) -> + case F(Inner) of + {ok, Decoded} -> {ok, {some, Decoded}}; + Error -> Error + end + end, + case Term of + undefined -> {ok, none}; + error -> {ok, none}; + null -> {ok, none}; + none -> {ok, none}; + nil -> {ok, none}; + {some, Inner} -> Decode(Inner); + _ -> Decode(Term) + end. + +decode_result(Term) -> + case Term of + {ok, Inner} -> {ok, {ok, Inner}}; + ok -> {ok, {ok, nil}}; + {error, Inner} -> {ok, {error, Inner}}; + error -> {ok, {error, nil}}; + _ -> decode_error_msg(<<"Result">>, Term) + end. + +int_from_base_string(String, Base) -> + case catch binary_to_integer(String, Base) of + Int when is_integer(Int) -> {ok, Int}; + _ -> {error, nil} + end. + +parse_int(String) -> + case catch binary_to_integer(String) of + Int when is_integer(Int) -> {ok, Int}; + _ -> {error, nil} + end. + +parse_float(String) -> + case catch binary_to_float(String) of + Float when is_float(Float) -> {ok, Float}; + _ -> {error, nil} + end. + +less_than(Lhs, Rhs) -> + Lhs < Rhs. + +string_starts_with(_, <<>>) -> true; +string_starts_with(String, Prefix) when byte_size(Prefix) > byte_size(String) -> false; +string_starts_with(String, Prefix) -> + PrefixSize = byte_size(Prefix), + Prefix == binary_part(String, 0, PrefixSize). + +string_ends_with(_, <<>>) -> true; +string_ends_with(String, Suffix) when byte_size(Suffix) > byte_size(String) -> false; +string_ends_with(String, Suffix) -> + SuffixSize = byte_size(Suffix), + Suffix == binary_part(String, byte_size(String) - SuffixSize, SuffixSize). + +string_pad(String, Length, Dir, PadString) -> + Chars = string:pad(String, Length, Dir, binary_to_list(PadString)), + case unicode:characters_to_binary(Chars) of + Bin when is_binary(Bin) -> Bin; + Error -> erlang:error({gleam_error, {string_invalid_utf8, Error}}) + end. + +string_pop_grapheme(String) -> + case string:next_grapheme(String) of + [ Next | Rest ] -> + {ok, {unicode:characters_to_binary([Next]), unicode:characters_to_binary(Rest)}}; + _ -> {error, nil} + end. + +bit_array_concat(BitArrays) -> + list_to_bitstring(BitArrays). + +bit_array_slice(Bin, Pos, Len) -> + try {ok, binary:part(Bin, Pos, Len)} + catch error:badarg -> {error, nil} + end. + +bit_array_int_to_u32(I) when 0 =< I, I < 4294967296 -> + {ok, <<I:32>>}; +bit_array_int_to_u32(_) -> + {error, nil}. + +bit_array_int_from_u32(<<I:32>>) -> + {ok, I}; +bit_array_int_from_u32(_) -> + {error, nil}. + +compile_regex(String, Options) -> + {options, Caseless, Multiline} = Options, + OptionsList = [ + unicode, + ucp, + Caseless andalso caseless, + Multiline andalso multiline + ], + FilteredOptions = [Option || Option <- OptionsList, Option /= false], + case re:compile(String, FilteredOptions) of + {ok, MP} -> {ok, MP}; + {error, {Str, Pos}} -> + {error, {compile_error, unicode:characters_to_binary(Str), Pos}} + end. + +regex_check(Regex, String) -> + re:run(String, Regex) /= nomatch. + +regex_split(Regex, String) -> + re:split(String, Regex). + +regex_submatches(_, {-1, 0}) -> none; +regex_submatches(String, {Start, Length}) -> + BinarySlice = binary:part(String, {Start, Length}), + case string:is_empty(binary_to_list(BinarySlice)) of + true -> none; + false -> {some, BinarySlice} + end. + +regex_matches(String, [{Start, Length} | Submatches]) -> + Submatches1 = lists:map(fun(X) -> regex_submatches(String, X) end, Submatches), + {match, binary:part(String, Start, Length), Submatches1}. + +regex_scan(Regex, String) -> + case re:run(String, Regex, [global]) of + {match, Captured} -> lists:map(fun(X) -> regex_matches(String, X) end, Captured); + nomatch -> [] + end. + +base_decode64(S) -> + try {ok, base64:decode(S)} + catch error:_ -> {error, nil} + end. + +wrap_list(X) when is_list(X) -> X; +wrap_list(X) -> [X]. + +parse_query(Query) -> + case uri_string:dissect_query(Query) of + {error, _, _} -> {error, nil}; + Pairs -> + Pairs1 = lists:map(fun + ({K, true}) -> {K, <<"">>}; + (Pair) -> Pair + end, Pairs), + {ok, Pairs1} + end. + +percent_encode(B) -> percent_encode(B, <<>>). +percent_encode(<<>>, Acc) -> + Acc; +percent_encode(<<H,T/binary>>, Acc) -> + case percent_ok(H) of + true -> + percent_encode(T, <<Acc/binary,H>>); + false -> + <<A:4,B:4>> = <<H>>, + percent_encode(T, <<Acc/binary,$%,(?DEC2HEX(A)),(?DEC2HEX(B))>>) + end. + +percent_decode(Cs) -> percent_decode(Cs, <<>>). +percent_decode(<<$%, C0, C1, Cs/binary>>, Acc) -> + case is_hex_digit(C0) andalso is_hex_digit(C1) of + true -> + B = ?HEX2DEC(C0)*16+?HEX2DEC(C1), + percent_decode(Cs, <<Acc/binary, B>>); + false -> + {error, nil} + end; +percent_decode(<<C,Cs/binary>>, Acc) -> + percent_decode(Cs, <<Acc/binary, C>>); +percent_decode(<<>>, Acc) -> + check_utf8(Acc). + +percent_ok($!) -> true; +percent_ok($$) -> true; +percent_ok($') -> true; +percent_ok($() -> true; +percent_ok($)) -> true; +percent_ok($*) -> true; +percent_ok($+) -> true; +percent_ok($-) -> true; +percent_ok($.) -> true; +percent_ok($_) -> true; +percent_ok($~) -> true; +percent_ok(C) when $0 =< C, C =< $9 -> true; +percent_ok(C) when $A =< C, C =< $Z -> true; +percent_ok(C) when $a =< C, C =< $z -> true; +percent_ok(_) -> false. + +is_hex_digit(C) -> + ($0 =< C andalso C =< $9) orelse ($a =< C andalso C =< $f) orelse ($A =< C andalso C =< $F). + +check_utf8(Cs) -> + case unicode:characters_to_list(Cs) of + {incomplete, _, _} -> {error, nil}; + {error, _, _} -> {error, nil}; + _ -> {ok, Cs} + end. + +uri_parse(String) -> + case uri_string:parse(String) of + {error, _, _} -> {error, nil}; + Uri -> + {ok, {uri, + maps_get_optional(Uri, scheme), + maps_get_optional(Uri, userinfo), + maps_get_optional(Uri, host), + maps_get_optional(Uri, port), + maps_get_or(Uri, path, <<>>), + maps_get_optional(Uri, query), + maps_get_optional(Uri, fragment) + }} + end. + +maps_get_optional(Map, Key) -> + try {some, maps:get(Key, Map)} + catch _:_ -> none + end. + +maps_get_or(Map, Key, Default) -> + try maps:get(Key, Map) + catch _:_ -> Default + end. + +print(String) -> + io:put_chars(String), + nil. + +println(String) -> + io:put_chars([String, $\n]), + nil. + +print_error(String) -> + io:put_chars(standard_error, String), + nil. + +println_error(String) -> + io:put_chars(standard_error, [String, $\n]), + nil. + +inspect(true) -> + "True"; +inspect(false) -> + "False"; +inspect(nil) -> + "Nil"; +inspect(Data) when is_map(Data) -> + Fields = [ + [<<"#(">>, inspect(Key), <<", ">>, inspect(Value), <<")">>] + || {Key, Value} <- maps:to_list(Data) + ], + ["dict.from_list([", lists:join(", ", Fields), "])"]; +inspect(Atom) when is_atom(Atom) -> + Binary = erlang:atom_to_binary(Atom), + case inspect_maybe_gleam_atom(Binary, none, <<>>) of + {ok, Inspected} -> Inspected; + {error, _} -> ["atom.create_from_string(\"", Binary, "\")"] + end; +inspect(Any) when is_integer(Any) -> + erlang:integer_to_list(Any); +inspect(Any) when is_float(Any) -> + io_lib_format:fwrite_g(Any); +inspect(Binary) when is_binary(Binary) -> + case inspect_maybe_utf8_string(Binary, <<>>) of + {ok, InspectedUtf8String} -> InspectedUtf8String; + {error, not_a_utf8_string} -> + Segments = [erlang:integer_to_list(X) || <<X>> <= Binary], + ["<<", lists:join(", ", Segments), ">>"] + end; +inspect(Bits) when is_bitstring(Bits) -> + inspect_bit_array(Bits); +inspect(List) when is_list(List) -> + case inspect_list(List) of + {proper, Elements} -> ["[", Elements, "]"]; + {improper, Elements} -> ["//erl([", Elements, "])"] + end; +inspect(Any) when is_tuple(Any) % Record constructors + andalso is_atom(element(1, Any)) + andalso element(1, Any) =/= false + andalso element(1, Any) =/= true + andalso element(1, Any) =/= nil +-> + [Atom | ArgsList] = erlang:tuple_to_list(Any), + Args = lists:join(<<", ">>, + lists:map(fun inspect/1, ArgsList) + ), + [inspect(Atom), "(", Args, ")"]; +inspect(Tuple) when is_tuple(Tuple) -> + Elements = lists:map(fun inspect/1, erlang:tuple_to_list(Tuple)), + ["#(", lists:join(", ", Elements), ")"]; +inspect(Any) when is_function(Any) -> + {arity, Arity} = erlang:fun_info(Any, arity), + ArgsAsciiCodes = lists:seq($a, $a + Arity - 1), + Args = lists:join(<<", ">>, + lists:map(fun(Arg) -> <<Arg>> end, ArgsAsciiCodes) + ), + ["//fn(", Args, ") { ... }"]; +inspect(Any) -> + ["//erl(", io_lib:format("~p", [Any]), ")"]. + + +inspect_maybe_gleam_atom(<<>>, none, _) -> + {error, nil}; +inspect_maybe_gleam_atom(<<First, _Rest/binary>>, none, _) when ?is_digit_char(First) -> + {error, nil}; +inspect_maybe_gleam_atom(<<"_", _Rest/binary>>, none, _) -> + {error, nil}; +inspect_maybe_gleam_atom(<<"_">>, _PrevChar, _Acc) -> + {error, nil}; +inspect_maybe_gleam_atom(<<"_", _Rest/binary>>, $_, _Acc) -> + {error, nil}; +inspect_maybe_gleam_atom(<<First, _Rest/binary>>, _PrevChar, _Acc) + when not (?is_lowercase_char(First) orelse ?is_underscore_char(First) orelse ?is_digit_char(First)) -> + {error, nil}; +inspect_maybe_gleam_atom(<<First, Rest/binary>>, none, Acc) -> + inspect_maybe_gleam_atom(Rest, First, <<Acc/binary, (uppercase(First))>>); +inspect_maybe_gleam_atom(<<"_", Rest/binary>>, _PrevChar, Acc) -> + inspect_maybe_gleam_atom(Rest, $_, Acc); +inspect_maybe_gleam_atom(<<First, Rest/binary>>, $_, Acc) -> + inspect_maybe_gleam_atom(Rest, First, <<Acc/binary, (uppercase(First))>>); +inspect_maybe_gleam_atom(<<First, Rest/binary>>, _PrevChar, Acc) -> + inspect_maybe_gleam_atom(Rest, First, <<Acc/binary, First>>); +inspect_maybe_gleam_atom(<<>>, _PrevChar, Acc) -> + {ok, Acc}; +inspect_maybe_gleam_atom(A, B, C) -> + erlang:display({A, B, C}), + throw({gleam_error, A, B, C}). + +inspect_list([]) -> + {proper, []}; +inspect_list([First]) -> + {proper, [inspect(First)]}; +inspect_list([First | Rest]) when is_list(Rest) -> + {Kind, Inspected} = inspect_list(Rest), + {Kind, [inspect(First), <<", ">> | Inspected]}; +inspect_list([First | ImproperTail]) -> + {improper, [inspect(First), <<" | ">>, inspect(ImproperTail)]}. + +inspect_bit_array(Bits) -> + Text = inspect_bit_array(Bits, <<"<<">>), + <<Text/binary, ">>">>. + +inspect_bit_array(<<>>, Acc) -> + Acc; +inspect_bit_array(<<X, Rest/bitstring>>, Acc) -> + inspect_bit_array(Rest, append_segment(Acc, erlang:integer_to_binary(X))); +inspect_bit_array(Rest, Acc) -> + Size = bit_size(Rest), + <<X:Size>> = Rest, + X1 = erlang:integer_to_binary(X), + Size1 = erlang:integer_to_binary(Size), + Segment = <<X1/binary, ":size(", Size1/binary, ")">>, + inspect_bit_array(<<>>, append_segment(Acc, Segment)). + +append_segment(<<"<<">>, Segment) -> + <<"<<", Segment/binary>>; +append_segment(Acc, Segment) -> + <<Acc/binary, ", ", Segment/binary>>. + + +inspect_maybe_utf8_string(Binary, Acc) -> + case Binary of + <<>> -> {ok, <<$", Acc/binary, $">>}; + <<First/utf8, Rest/binary>> -> + Escaped = case First of + $" -> <<$\\, $">>; + $\\ -> <<$\\, $\\>>; + $\r -> <<$\\, $r>>; + $\n -> <<$\\, $n>>; + $\t -> <<$\\, $t>>; + Other -> <<Other/utf8>> + end, + inspect_maybe_utf8_string(Rest, <<Acc/binary, Escaped/binary>>); + _ -> {error, not_a_utf8_string} + end. + +float_to_string(Float) when is_float(Float) -> + erlang:iolist_to_binary(io_lib_format:fwrite_g(Float)). + +utf_codepoint_list_to_string(List) -> + case unicode:characters_to_binary(List) of + {error, _} -> erlang:error({gleam_error, {string_invalid_utf8, List}}); + Binary -> Binary + end. + +crop_string(String, Prefix) -> + case string:find(String, Prefix) of + nomatch -> String; + New -> New + end. + +contains_string(String, Substring) -> + is_bitstring(string:find(String, Substring)). + +base16_decode(String) -> + try + {ok, binary:decode_hex(String)} + catch + _:_ -> {error, nil} + end. diff --git a/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam_stdlib.mjs b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam_stdlib.mjs new file mode 100644 index 0000000..45c28cf --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleam_stdlib/src/gleam_stdlib.mjs @@ -0,0 +1,878 @@ +import { + BitArray, + Error, + List, + Ok, + Result, + UtfCodepoint, + stringBits, + toBitArray, + NonEmpty, + CustomType, +} from "./gleam.mjs"; +import { + CompileError as RegexCompileError, + Match as RegexMatch, +} from "./gleam/regex.mjs"; +import { DecodeError } from "./gleam/dynamic.mjs"; +import { Some, None } from "./gleam/option.mjs"; +import Dict from "./dict.mjs"; + +const Nil = undefined; +const NOT_FOUND = {}; + +export function identity(x) { + return x; +} + +export function parse_int(value) { + if (/^[-+]?(\d+)$/.test(value)) { + return new Ok(parseInt(value)); + } else { + return new Error(Nil); + } +} + +export function parse_float(value) { + if (/^[-+]?(\d+)\.(\d+)$/.test(value)) { + return new Ok(parseFloat(value)); + } else { + return new Error(Nil); + } +} + +export function to_string(term) { + return term.toString(); +} + +export function float_to_string(float) { + const string = float.toString(); + if (string.indexOf(".") >= 0) { + return string; + } else { + return string + ".0"; + } +} + +export function int_to_base_string(int, base) { + return int.toString(base).toUpperCase(); +} + +const int_base_patterns = { + 2: /[^0-1]/, + 3: /[^0-2]/, + 4: /[^0-3]/, + 5: /[^0-4]/, + 6: /[^0-5]/, + 7: /[^0-6]/, + 8: /[^0-7]/, + 9: /[^0-8]/, + 10: /[^0-9]/, + 11: /[^0-9a]/, + 12: /[^0-9a-b]/, + 13: /[^0-9a-c]/, + 14: /[^0-9a-d]/, + 15: /[^0-9a-e]/, + 16: /[^0-9a-f]/, + 17: /[^0-9a-g]/, + 18: /[^0-9a-h]/, + 19: /[^0-9a-i]/, + 20: /[^0-9a-j]/, + 21: /[^0-9a-k]/, + 22: /[^0-9a-l]/, + 23: /[^0-9a-m]/, + 24: /[^0-9a-n]/, + 25: /[^0-9a-o]/, + 26: /[^0-9a-p]/, + 27: /[^0-9a-q]/, + 28: /[^0-9a-r]/, + 29: /[^0-9a-s]/, + 30: /[^0-9a-t]/, + 31: /[^0-9a-u]/, + 32: /[^0-9a-v]/, + 33: /[^0-9a-w]/, + 34: /[^0-9a-x]/, + 35: /[^0-9a-y]/, + 36: /[^0-9a-z]/, +}; + +export function int_from_base_string(string, base) { + if (int_base_patterns[base].test(string.replace(/^-/, "").toLowerCase())) { + return new Error(Nil); + } + + const result = parseInt(string, base); + + if (isNaN(result)) { + return new Error(Nil); + } + + return new Ok(result); +} + +export function string_replace(string, target, substitute) { + if (typeof string.replaceAll !== "undefined") { + return string.replaceAll(target, substitute); + } + // Fallback for older Node.js versions: + // 1. <https://stackoverflow.com/a/1144788> + // 2. <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping> + // TODO: This fallback could be remove once Node.js 14 is EOL + // aka <https://nodejs.org/en/about/releases/> on or after 2024-04-30 + return string.replace( + // $& means the whole matched string + new RegExp(target.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g"), + substitute + ); +} + +export function string_reverse(string) { + return [...string].reverse().join(""); +} + +export function string_length(string) { + if (string === "") { + return 0; + } + const iterator = graphemes_iterator(string); + if (iterator) { + let i = 0; + for (const _ of iterator) { + i++; + } + return i; + } else { + return string.match(/./gsu).length; + } +} + +export function graphemes(string) { + const iterator = graphemes_iterator(string); + if (iterator) { + return List.fromArray(Array.from(iterator).map((item) => item.segment)); + } else { + return List.fromArray(string.match(/./gsu)); + } +} + +function graphemes_iterator(string) { + if (Intl && Intl.Segmenter) { + return new Intl.Segmenter().segment(string)[Symbol.iterator](); + } +} + +export function pop_grapheme(string) { + let first; + const iterator = graphemes_iterator(string); + if (iterator) { + first = iterator.next().value?.segment; + } else { + first = string.match(/./su)?.[0]; + } + if (first) { + return new Ok([first, string.slice(first.length)]); + } else { + return new Error(Nil); + } +} + +export function lowercase(string) { + return string.toLowerCase(); +} + +export function uppercase(string) { + return string.toUpperCase(); +} + +export function less_than(a, b) { + return a < b; +} + +export function add(a, b) { + return a + b; +} + +export function equal(a, b) { + return a === b; +} + +export function split(xs, pattern) { + return List.fromArray(xs.split(pattern)); +} + +export function join(xs, separator) { + const iterator = xs[Symbol.iterator](); + let result = iterator.next().value || ""; + let current = iterator.next(); + while (!current.done) { + result = result + separator + current.value; + current = iterator.next(); + } + return result; +} + +export function concat(xs) { + let result = ""; + for (const x of xs) { + result = result + x; + } + return result; +} + +export function length(data) { + return data.length; +} + +export function crop_string(string, substring) { + return string.substring(string.indexOf(substring)); +} + +export function contains_string(haystack, needle) { + return haystack.indexOf(needle) >= 0; +} + +export function starts_with(haystack, needle) { + return haystack.startsWith(needle); +} + +export function ends_with(haystack, needle) { + return haystack.endsWith(needle); +} + +export function split_once(haystack, needle) { + const index = haystack.indexOf(needle); + if (index >= 0) { + const before = haystack.slice(0, index); + const after = haystack.slice(index + needle.length); + return new Ok([before, after]); + } else { + return new Error(Nil); + } +} + +export function trim(string) { + return string.trim(); +} + +export function trim_left(string) { + return string.trimLeft(); +} + +export function trim_right(string) { + return string.trimRight(); +} + +export function bit_array_from_string(string) { + return toBitArray([stringBits(string)]); +} + +export function bit_array_concat(bit_arrays) { + return toBitArray(bit_arrays.toArray().map((b) => b.buffer)); +} + +export function console_log(term) { + console.log(term); +} + +export function console_error(term) { + console.error(term); +} + +export function crash(message) { + throw new globalThis.Error(message); +} + +export function bit_array_to_string(bit_array) { + try { + const decoder = new TextDecoder("utf-8", { fatal: true }); + return new Ok(decoder.decode(bit_array.buffer)); + } catch (_error) { + return new Error(Nil); + } +} + +export function print(string) { + if (typeof process === "object") { + process.stdout.write(string); // We can write without a trailing newline + } else if (typeof Deno === "object") { + Deno.stdout.writeSync(new TextEncoder().encode(string)); // We can write without a trailing newline + } else { + console.log(string); // We're in a browser. Newlines are mandated + } +} + +export function print_error(string) { + if (typeof process === "object" && process.stderr?.write) { + process.stderr.write(string); // We can write without a trailing newline + } else if (typeof Deno === "object") { + Deno.stderr.writeSync(new TextEncoder().encode(string)); // We can write without a trailing newline + } else { + console.error(string); // We're in a browser. Newlines are mandated + } +} + +export function print_debug(string) { + if (typeof process === "object" && process.stderr?.write) { + process.stderr.write(string + "\n"); // If we're in Node.js, use `stderr` + } else if (typeof Deno === "object") { + Deno.stderr.writeSync(new TextEncoder().encode(string + "\n")); // If we're in Deno, use `stderr` + } else { + console.log(string); // Otherwise, use `console.log` (so that it doesn't look like an error) + } +} + +export function ceiling(float) { + return Math.ceil(float); +} + +export function floor(float) { + return Math.floor(float); +} + +export function round(float) { + return Math.round(float); +} + +export function truncate(float) { + return Math.trunc(float); +} + +export function power(base, exponent) { + // It is checked in Gleam that: + // - The base is non-negative and that the exponent is not fractional. + // - The base is non-zero and the exponent is non-negative (otherwise + // the result will essentially be division by zero). + // It can thus be assumed that valid input is passed to the Math.pow + // function and a NaN or Infinity value will not be produced. + return Math.pow(base, exponent); +} + +export function random_uniform() { + const random_uniform_result = Math.random(); + // With round-to-nearest-even behavior, the ranges claimed for the functions below + // (excluding the one for Math.random() itself) aren't exact. + // If extremely large bounds are chosen (2^53 or higher), + // it's possible in extremely rare cases to calculate the usually-excluded upper bound. + // Note that as numbers in JavaScript are IEEE 754 floating point numbers + // See: <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random> + // Because of this, we just loop 'until' we get a valid result where 0.0 <= x < 1.0: + if (random_uniform_result === 1.0) { + return random_uniform(); + } + return random_uniform_result; +} + +export function bit_array_slice(bits, position, length) { + const start = Math.min(position, position + length); + const end = Math.max(position, position + length); + if (start < 0 || end > bits.length) return new Error(Nil); + const buffer = new Uint8Array(bits.buffer.buffer, start, Math.abs(length)); + return new Ok(new BitArray(buffer)); +} + +export function codepoint(int) { + return new UtfCodepoint(int); +} + +export function string_to_codepoint_integer_list(string) { + return List.fromArray(Array.from(string).map((item) => item.codePointAt(0))); +} + +export function utf_codepoint_list_to_string(utf_codepoint_integer_list) { + return utf_codepoint_integer_list + .toArray() + .map((x) => String.fromCodePoint(x.value)) + .join(""); +} + +export function utf_codepoint_to_int(utf_codepoint) { + return utf_codepoint.value; +} + +export function regex_check(regex, string) { + regex.lastIndex = 0; + return regex.test(string); +} + +export function compile_regex(pattern, options) { + try { + let flags = "gu"; + if (options.case_insensitive) flags += "i"; + if (options.multi_line) flags += "m"; + return new Ok(new RegExp(pattern, flags)); + } catch (error) { + const number = (error.columnNumber || 0) | 0; + return new Error(new RegexCompileError(error.message, number)); + } +} + +export function regex_scan(regex, string) { + const matches = Array.from(string.matchAll(regex)).map((match) => { + const content = match[0]; + const submatches = []; + for (let n = match.length - 1; n > 0; n--) { + if (match[n]) { + submatches[n - 1] = new Some(match[n]); + continue; + } + if (submatches.length > 0) { + submatches[n - 1] = new None(); + } + } + return new RegexMatch(content, List.fromArray(submatches)); + }); + return List.fromArray(matches); +} + +export function new_map() { + return Dict.new(); +} + +export function map_size(map) { + return map.size; +} + +export function map_to_list(map) { + return List.fromArray(map.entries()); +} + +export function map_remove(key, map) { + return map.delete(key); +} + +export function map_get(map, key) { + const value = map.get(key, NOT_FOUND); + if (value === NOT_FOUND) { + return new Error(Nil); + } + return new Ok(value); +} + +export function map_insert(key, value, map) { + return map.set(key, value); +} + +function unsafe_percent_decode(string) { + return decodeURIComponent((string || "").replace("+", " ")); +} + +export function percent_decode(string) { + try { + return new Ok(unsafe_percent_decode(string)); + } catch (_error) { + return new Error(Nil); + } +} + +export function percent_encode(string) { + return encodeURIComponent(string); +} + +export function parse_query(query) { + try { + const pairs = []; + for (const section of query.split("&")) { + const [key, value] = section.split("="); + if (!key) continue; + pairs.push([unsafe_percent_decode(key), unsafe_percent_decode(value)]); + } + return new Ok(List.fromArray(pairs)); + } catch (_error) { + return new Error(Nil); + } +} + +// From https://developer.mozilla.org/en-US/docs/Glossary/Base64#Solution_2_%E2%80%93_rewrite_the_DOMs_atob()_and_btoa()_using_JavaScript's_TypedArrays_and_UTF-8 +export function encode64(bit_array) { + const aBytes = bit_array.buffer; + let nMod3 = 2; + let sB64Enc = ""; + + for (let nLen = aBytes.length, nUint24 = 0, nIdx = 0; nIdx < nLen; nIdx++) { + nMod3 = nIdx % 3; + if (nIdx > 0 && ((nIdx * 4) / 3) % 76 === 0) { + sB64Enc += "\r\n"; + } + nUint24 |= aBytes[nIdx] << ((16 >>> nMod3) & 24); + if (nMod3 === 2 || aBytes.length - nIdx === 1) { + sB64Enc += String.fromCharCode( + uint6ToB64((nUint24 >>> 18) & 63), + uint6ToB64((nUint24 >>> 12) & 63), + uint6ToB64((nUint24 >>> 6) & 63), + uint6ToB64(nUint24 & 63) + ); + nUint24 = 0; + } + } + + return ( + sB64Enc.substr(0, sB64Enc.length - 2 + nMod3) + + (nMod3 === 2 ? "" : nMod3 === 1 ? "=" : "==") + ); +} + +// From https://developer.mozilla.org/en-US/docs/Glossary/Base64#Solution_2_%E2%80%93_rewrite_the_DOMs_atob()_and_btoa()_using_JavaScript's_TypedArrays_and_UTF-8 +function uint6ToB64(nUint6) { + return nUint6 < 26 + ? nUint6 + 65 + : nUint6 < 52 + ? nUint6 + 71 + : nUint6 < 62 + ? nUint6 - 4 + : nUint6 === 62 + ? 43 + : nUint6 === 63 + ? 47 + : 65; +} + +// From https://developer.mozilla.org/en-US/docs/Glossary/Base64#Solution_2_%E2%80%93_rewrite_the_DOMs_atob()_and_btoa()_using_JavaScript's_TypedArrays_and_UTF-8 +function b64ToUint6(nChr) { + return nChr > 64 && nChr < 91 + ? nChr - 65 + : nChr > 96 && nChr < 123 + ? nChr - 71 + : nChr > 47 && nChr < 58 + ? nChr + 4 + : nChr === 43 + ? 62 + : nChr === 47 + ? 63 + : 0; +} + +// From https://developer.mozilla.org/en-US/docs/Glossary/Base64#Solution_2_%E2%80%93_rewrite_the_DOMs_atob()_and_btoa()_using_JavaScript's_TypedArrays_and_UTF-8 +export function decode64(sBase64) { + if (sBase64.match(/[^A-Za-z0-9\+\/=]/g)) return new Error(Nil); + const sB64Enc = sBase64.replace(/=/g, ""); + const nInLen = sB64Enc.length; + const nOutLen = (nInLen * 3 + 1) >> 2; + const taBytes = new Uint8Array(nOutLen); + + for ( + let nMod3, nMod4, nUint24 = 0, nOutIdx = 0, nInIdx = 0; + nInIdx < nInLen; + nInIdx++ + ) { + nMod4 = nInIdx & 3; + nUint24 |= b64ToUint6(sB64Enc.charCodeAt(nInIdx)) << (6 * (3 - nMod4)); + if (nMod4 === 3 || nInLen - nInIdx === 1) { + for (nMod3 = 0; nMod3 < 3 && nOutIdx < nOutLen; nMod3++, nOutIdx++) { + taBytes[nOutIdx] = (nUint24 >>> ((16 >>> nMod3) & 24)) & 255; + } + nUint24 = 0; + } + } + + return new Ok(new BitArray(taBytes)); +} + +export function classify_dynamic(data) { + if (typeof data === "string") { + return "String"; + } else if (data instanceof Result) { + return "Result"; + } else if (data instanceof List) { + return "List"; + } else if (data instanceof BitArray) { + return "BitArray"; + } else if (data instanceof Dict) { + return "Map"; + } else if (Number.isInteger(data)) { + return "Int"; + } else if (Array.isArray(data)) { + return `Tuple of ${data.length} elements`; + } else if (typeof data === "number") { + return "Float"; + } else if (data === null) { + return "Null"; + } else if (data === undefined) { + return "Nil"; + } else { + const type = typeof data; + return type.charAt(0).toUpperCase() + type.slice(1); + } +} + +function decoder_error(expected, got) { + return decoder_error_no_classify(expected, classify_dynamic(got)); +} + +function decoder_error_no_classify(expected, got) { + return new Error( + List.fromArray([new DecodeError(expected, got, List.fromArray([]))]) + ); +} + +export function decode_string(data) { + return typeof data === "string" + ? new Ok(data) + : decoder_error("String", data); +} + +export function decode_int(data) { + return Number.isInteger(data) ? new Ok(data) : decoder_error("Int", data); +} + +export function decode_float(data) { + return typeof data === "number" ? new Ok(data) : decoder_error("Float", data); +} + +export function decode_bool(data) { + return typeof data === "boolean" ? new Ok(data) : decoder_error("Bool", data); +} + +export function decode_bit_array(data) { + if (data instanceof BitArray) { + return new Ok(data); + } + if (data instanceof Uint8Array) { + return new Ok(new BitArray(data)); + } + return decoder_error("BitArray", data); +} + +export function decode_tuple(data) { + return Array.isArray(data) ? new Ok(data) : decoder_error("Tuple", data); +} + +export function decode_tuple2(data) { + return decode_tupleN(data, 2); +} + +export function decode_tuple3(data) { + return decode_tupleN(data, 3); +} + +export function decode_tuple4(data) { + return decode_tupleN(data, 4); +} + +export function decode_tuple5(data) { + return decode_tupleN(data, 5); +} + +export function decode_tuple6(data) { + return decode_tupleN(data, 6); +} + +function decode_tupleN(data, n) { + if (Array.isArray(data) && data.length == n) { + return new Ok(data); + } + + const list = decode_exact_length_list(data, n); + if (list) return new Ok(list); + + return decoder_error(`Tuple of ${n} elements`, data); +} + +function decode_exact_length_list(data, n) { + if (!(data instanceof List)) return; + + const elements = []; + let current = data; + + for (let i = 0; i < n; i++) { + if (!(current instanceof NonEmpty)) break; + elements.push(current.head); + current = current.tail; + } + + if (elements.length === n && !(current instanceof NonEmpty)) return elements; +} + +export function tuple_get(data, index) { + return index >= 0 && data.length > index + ? new Ok(data[index]) + : new Error(Nil); +} + +export function decode_list(data) { + if (Array.isArray(data)) { + return new Ok(List.fromArray(data)); + } + return data instanceof List ? new Ok(data) : decoder_error("List", data); +} + +export function decode_result(data) { + return data instanceof Result ? new Ok(data) : decoder_error("Result", data); +} + +export function decode_map(data) { + if (data instanceof Dict) { + return new Ok(Dict.fromMap(data)); + } + if (data == null) { + return decoder_error("Map", data); + } + if (typeof data !== "object") { + return decoder_error("Map", data); + } + const proto = Object.getPrototypeOf(data); + if (proto === Object.prototype || proto === null) { + return new Ok(Dict.fromObject(data)); + } + return decoder_error("Map", data); +} + +export function decode_option(data, decoder) { + if (data === null || data === undefined || data instanceof None) + return new Ok(new None()); + if (data instanceof Some) data = data[0]; + const result = decoder(data); + if (result.isOk()) { + return new Ok(new Some(result[0])); + } else { + return result; + } +} + +export function decode_field(value, name) { + const not_a_map_error = () => decoder_error("Map", value); + + if ( + value instanceof Dict || + value instanceof WeakMap || + value instanceof Map + ) { + const entry = map_get(value, name); + return new Ok(entry.isOk() ? new Some(entry[0]) : new None()); + } else if (Object.getPrototypeOf(value) == Object.prototype) { + return try_get_field(value, name, () => new Ok(new None())); + } else { + return try_get_field(value, name, not_a_map_error); + } +} + +function try_get_field(value, field, or_else) { + try { + return field in value ? new Ok(new Some(value[field])) : or_else(); + } catch { + return or_else(); + } +} + +export function byte_size(string) { + return new TextEncoder().encode(string).length; +} + +// In Javascript bitwise operations convert numbers to a sequence of 32 bits +// while Erlang uses arbitrary precision. +// To get around this problem and get consistent results use BigInt and then +// downcast the value back to a Number value. + +export function bitwise_and(x, y) { + return Number(BigInt(x) & BigInt(y)); +} + +export function bitwise_not(x) { + return Number(~BigInt(x)); +} + +export function bitwise_or(x, y) { + return Number(BigInt(x) | BigInt(y)); +} + +export function bitwise_exclusive_or(x, y) { + return Number(BigInt(x) ^ BigInt(y)); +} + +export function bitwise_shift_left(x, y) { + return Number(BigInt(x) << BigInt(y)); +} + +export function bitwise_shift_right(x, y) { + return Number(BigInt(x) >> BigInt(y)); +} + +export function inspect(v) { + const t = typeof v; + if (v === true) return "True"; + if (v === false) return "False"; + if (v === null) return "//js(null)"; + if (v === undefined) return "Nil"; + if (t === "string") return JSON.stringify(v); + if (t === "bigint" || t === "number") return v.toString(); + if (Array.isArray(v)) return `#(${v.map(inspect).join(", ")})`; + if (v instanceof List) return inspectList(v); + if (v instanceof UtfCodepoint) return inspectUtfCodepoint(v); + if (v instanceof BitArray) return inspectBitArray(v); + if (v instanceof CustomType) return inspectCustomType(v); + if (v instanceof Dict) return inspectDict(v); + if (v instanceof Set) return `//js(Set(${[...v].map(inspect).join(", ")}))`; + if (v instanceof RegExp) return `//js(${v})`; + if (v instanceof Date) return `//js(Date("${v.toISOString()}"))`; + if (v instanceof Function) { + const args = []; + for (const i of Array(v.length).keys()) + args.push(String.fromCharCode(i + 97)); + return `//fn(${args.join(", ")}) { ... }`; + } + return inspectObject(v); +} + +function inspectDict(map) { + let body = "dict.from_list(["; + let first = true; + map.forEach((value, key) => { + if (!first) body = body + ", "; + body = body + "#(" + inspect(key) + ", " + inspect(value) + ")"; + first = false; + }); + return body + "])"; +} + +function inspectObject(v) { + const name = Object.getPrototypeOf(v)?.constructor?.name || "Object"; + const props = []; + for (const k of Object.keys(v)) { + props.push(`${inspect(k)}: ${inspect(v[k])}`); + } + const body = props.length ? " " + props.join(", ") + " " : ""; + const head = name === "Object" ? "" : name + " "; + return `//js(${head}{${body}})`; +} + +function inspectCustomType(record) { + const props = Object.keys(record) + .map((label) => { + const value = inspect(record[label]); + return isNaN(parseInt(label)) ? `${label}: ${value}` : value; + }) + .join(", "); + return props + ? `${record.constructor.name}(${props})` + : record.constructor.name; +} + +export function inspectList(list) { + return `[${list.toArray().map(inspect).join(", ")}]`; +} + +export function inspectBitArray(bits) { + return `<<${Array.from(bits.buffer).join(", ")}>>`; +} + +export function inspectUtfCodepoint(codepoint) { + return `//utfcodepoint(${String.fromCodePoint(codepoint.value)})`; +} + +export function base16_encode(bit_array) { + let result = ""; + for (const byte of bit_array.buffer) { + result += byte.toString(16).padStart(2, "0").toUpperCase(); + } + return result; +} + +export function base16_decode(string) { + const bytes = new Uint8Array(string.length / 2); + for (let i = 0; i < string.length; i += 2) { + const a = parseInt(string[i], 16); + const b = parseInt(string[i + 1], 16); + if (isNaN(a) || isNaN(b)) return new Error(Nil); + bytes[i / 2] = a * 16 + b; + } + return new Ok(new BitArray(bytes)); +} diff --git a/aoc2023/build/packages/tom/build/packages/gleeunit/LICENCE b/aoc2023/build/packages/tom/build/packages/gleeunit/LICENCE new file mode 100644 index 0000000..c7967c3 --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleeunit/LICENCE @@ -0,0 +1,191 @@ + 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 + + Copyright 2021, Louis Pilfold <louis@lpil.uk>. + + 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/aoc2023/build/packages/tom/build/packages/gleeunit/README.md b/aoc2023/build/packages/tom/build/packages/gleeunit/README.md new file mode 100644 index 0000000..3ca1c63 --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleeunit/README.md @@ -0,0 +1,52 @@ +# gleeunit + +Gleam bindings to the Erlang EUnit test framework. + +A custom test runner is included for when compiled to JavaScript running on +either NodeJS or Deno. + +Documentation is available on [HexDocs](https://hexdocs.pm/gleeunit/index.html). + +## Usage + +Add this package to your Gleam project. + +```sh +gleam add gleeunit --dev +``` + +And then call the `gleeunit.main` function from your test main function. + +```gleam +// In test/yourapp_test.gleam +import gleeunit + +pub fn main() { + gleeunit.main() +} +``` + +Now any public function with a name ending in `_test` in the `test` directory +will be found and run as a test. + +```gleam +pub fn the_universe_test() { + let assert 1 = 1 +} +``` + +Run the tests by entering `gleam test` in the command line. + +### Deno + +If using the Deno JavaScript runtime, you will need to add the following to your +`gleam.toml`. + +```toml +[javascript.deno] +allow_read = [ + "gleam.toml", + "test", + "build", +] +``` diff --git a/aoc2023/build/packages/tom/build/packages/gleeunit/gleam.toml b/aoc2023/build/packages/tom/build/packages/gleeunit/gleam.toml new file mode 100644 index 0000000..c5be79b --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleeunit/gleam.toml @@ -0,0 +1,17 @@ +name = "gleeunit" +version = "1.0.2" +licences = ["Apache-2.0"] +description = "Gleam bindings to Erlang's EUnit test framework" +gleam = ">= 0.33.0" + +[javascript.deno] +allow_read = [ + "gleam.toml", + "test", + "build", +] + +[dependencies] +gleam_stdlib = "~> 0.32" + +[dev-dependencies] diff --git a/aoc2023/build/packages/tom/build/packages/gleeunit/src/gleeunit.app.src b/aoc2023/build/packages/tom/build/packages/gleeunit/src/gleeunit.app.src new file mode 100644 index 0000000..eb6365f --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleeunit/src/gleeunit.app.src @@ -0,0 +1,8 @@ +{application, gleeunit, [ + {vsn, "1.0.2"}, + {applications, [gleam_stdlib]}, + {description, "Gleam bindings to Erlang's EUnit test framework"}, + {modules, [gleeunit, + gleeunit@should]}, + {registered, []} +]}. diff --git a/aoc2023/build/packages/tom/build/packages/gleeunit/src/gleeunit.erl b/aoc2023/build/packages/tom/build/packages/gleeunit/src/gleeunit.erl new file mode 100644 index 0000000..22e1e7f --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleeunit/src/gleeunit.erl @@ -0,0 +1,59 @@ +-module(gleeunit). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). + +-export([main/0]). +-export_type([atom_/0, encoding/0, report_module_name/0, gleeunit_progress_option/0, eunit_option/0]). + +-type atom_() :: any(). + +-type encoding() :: utf8. + +-type report_module_name() :: gleeunit_progress. + +-type gleeunit_progress_option() :: {colored, boolean()}. + +-type eunit_option() :: verbose | + no_tty | + {report, {report_module_name(), list(gleeunit_progress_option())}}. + +-spec gleam_to_erlang_module_name(binary()) -> binary(). +gleam_to_erlang_module_name(Path) -> + _pipe = Path, + _pipe@1 = gleam@string:replace(_pipe, <<".gleam"/utf8>>, <<""/utf8>>), + _pipe@2 = gleam@string:replace(_pipe@1, <<".erl"/utf8>>, <<""/utf8>>), + gleam@string:replace(_pipe@2, <<"/"/utf8>>, <<"@"/utf8>>). + +-spec do_main() -> nil. +do_main() -> + Options = [verbose, + no_tty, + {report, {gleeunit_progress, [{colored, true}]}}], + Result = begin + _pipe = gleeunit_ffi:find_files( + <<"**/*.{erl,gleam}"/utf8>>, + <<"test"/utf8>> + ), + _pipe@1 = gleam@list:map(_pipe, fun gleam_to_erlang_module_name/1), + _pipe@2 = gleam@list:map( + _pipe@1, + fun(_capture) -> erlang:binary_to_atom(_capture, utf8) end + ), + _pipe@3 = eunit:test(_pipe@2, Options), + _pipe@4 = (gleam@dynamic:result( + fun gleam@dynamic:dynamic/1, + fun gleam@dynamic:dynamic/1 + ))(_pipe@3), + gleam@result:unwrap(_pipe@4, {error, gleam@dynamic:from(nil)}) + end, + Code = case Result of + {ok, _} -> + 0; + + {error, _} -> + 1 + end, + erlang:halt(Code). + +-spec main() -> nil. +main() -> + do_main(). diff --git a/aoc2023/build/packages/tom/build/packages/gleeunit/src/gleeunit.gleam b/aoc2023/build/packages/tom/build/packages/gleeunit/src/gleeunit.gleam new file mode 100644 index 0000000..884c3fa --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleeunit/src/gleeunit.gleam @@ -0,0 +1,92 @@ +/// Find and run all test functions for the current project using Erlang's EUnit +/// test framework. +/// +/// Any Erlang or Gleam function in the `test` directory with a name editing in +/// `_test` is considered a test function and will be run. +/// +/// If running on JavaScript tests will be run with a custom test runner. +/// +pub fn main() -> Nil { + do_main() +} + +@target(javascript) +@external(javascript, "./gleeunit_ffi.mjs", "main") +fn do_main() -> Nil + +@target(erlang) +import gleam/list +@target(erlang) +import gleam/result +@target(erlang) +import gleam/string +@target(erlang) +import gleam/dynamic.{type Dynamic} + +@target(erlang) +fn do_main() -> Nil { + let options = [Verbose, NoTty, Report(#(GleeunitProgress, [Colored(True)]))] + + let result = + find_files(matching: "**/*.{erl,gleam}", in: "test") + |> list.map(gleam_to_erlang_module_name) + |> list.map(dangerously_convert_string_to_atom(_, Utf8)) + |> run_eunit(options) + |> dynamic.result(dynamic.dynamic, dynamic.dynamic) + |> result.unwrap(Error(dynamic.from(Nil))) + + let code = case result { + Ok(_) -> 0 + Error(_) -> 1 + } + halt(code) +} + +@target(erlang) +@external(erlang, "erlang", "halt") +fn halt(a: Int) -> Nil + +@target(erlang) +fn gleam_to_erlang_module_name(path: String) -> String { + path + |> string.replace(".gleam", "") + |> string.replace(".erl", "") + |> string.replace("/", "@") +} + +@target(erlang) +@external(erlang, "gleeunit_ffi", "find_files") +fn find_files(matching matching: String, in in: String) -> List(String) + +@target(erlang) +type Atom + +@target(erlang) +type Encoding { + Utf8 +} + +@target(erlang) +@external(erlang, "erlang", "binary_to_atom") +fn dangerously_convert_string_to_atom(a: String, b: Encoding) -> Atom + +@target(erlang) +type ReportModuleName { + GleeunitProgress +} + +@target(erlang) +type GleeunitProgressOption { + Colored(Bool) +} + +@target(erlang) +type EunitOption { + Verbose + NoTty + Report(#(ReportModuleName, List(GleeunitProgressOption))) +} + +@target(erlang) +@external(erlang, "eunit", "test") +fn run_eunit(a: List(Atom), b: List(EunitOption)) -> Dynamic diff --git a/aoc2023/build/packages/tom/build/packages/gleeunit/src/gleeunit/should.gleam b/aoc2023/build/packages/tom/build/packages/gleeunit/src/gleeunit/should.gleam new file mode 100644 index 0000000..da484e3 --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleeunit/src/gleeunit/should.gleam @@ -0,0 +1,65 @@ +//// A module for testing your Gleam code. The functions found here are +//// compatible with the Erlang eunit test framework. +//// +//// More information on running eunit can be found in [the rebar3 +//// documentation](https://rebar3.org/docs/testing/eunit/). + +import gleam/string + +@external(erlang, "gleeunit_ffi", "should_equal") +pub fn equal(a: t, b: t) -> Nil { + case a == b { + True -> Nil + _ -> + panic as string.concat([ + "\n\t", + string.inspect(a), + "\n\tshould equal \n\t", + string.inspect(b), + ]) + } +} + +@external(erlang, "gleeunit_ffi", "should_not_equal") +pub fn not_equal(a: t, b: t) -> Nil { + case a != b { + True -> Nil + _ -> + panic as string.concat([ + "\n", + string.inspect(a), + "\nshould not equal \n", + string.inspect(b), + ]) + } +} + +@external(erlang, "gleeunit_ffi", "should_be_ok") +pub fn be_ok(a: Result(a, e)) -> a { + case a { + Ok(value) -> value + _ -> panic as string.concat(["\n", string.inspect(a), "\nshould be ok"]) + } +} + +@external(erlang, "gleeunit_ffi", "should_be_error") +pub fn be_error(a: Result(a, e)) -> e { + case a { + Error(error) -> error + _ -> panic as string.concat(["\n", string.inspect(a), "\nshould be error"]) + } +} + +pub fn be_true(actual: Bool) -> Nil { + actual + |> equal(True) +} + +pub fn be_false(actual: Bool) -> Nil { + actual + |> equal(False) +} + +pub fn fail() -> Nil { + be_true(False) +} diff --git a/aoc2023/build/packages/tom/build/packages/gleeunit/src/gleeunit@should.erl b/aoc2023/build/packages/tom/build/packages/gleeunit/src/gleeunit@should.erl new file mode 100644 index 0000000..bfb45f5 --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleeunit/src/gleeunit@should.erl @@ -0,0 +1,34 @@ +-module(gleeunit@should). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). + +-export([equal/2, not_equal/2, be_ok/1, be_error/1, be_true/1, be_false/1, fail/0]). + +-spec equal(FMN, FMN) -> nil. +equal(A, B) -> + gleeunit_ffi:should_equal(A, B). + +-spec not_equal(FMO, FMO) -> nil. +not_equal(A, B) -> + gleeunit_ffi:should_not_equal(A, B). + +-spec be_ok({ok, FMP} | {error, any()}) -> FMP. +be_ok(A) -> + gleeunit_ffi:should_be_ok(A). + +-spec be_error({ok, any()} | {error, FMU}) -> FMU. +be_error(A) -> + gleeunit_ffi:should_be_error(A). + +-spec be_true(boolean()) -> nil. +be_true(Actual) -> + _pipe = Actual, + gleeunit_ffi:should_equal(_pipe, true). + +-spec be_false(boolean()) -> nil. +be_false(Actual) -> + _pipe = Actual, + gleeunit_ffi:should_equal(_pipe, false). + +-spec fail() -> nil. +fail() -> + be_true(false). diff --git a/aoc2023/build/packages/tom/build/packages/gleeunit/src/gleeunit_ffi.erl b/aoc2023/build/packages/tom/build/packages/gleeunit/src/gleeunit_ffi.erl new file mode 100644 index 0000000..31f9ef9 --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleeunit/src/gleeunit_ffi.erl @@ -0,0 +1,24 @@ +-module(gleeunit_ffi). + +-export([find_files/2, should_equal/2, should_not_equal/2, should_be_ok/1, + should_be_error/1]). + +-include_lib("eunit/include/eunit.hrl"). + +find_files(Pattern, In) -> + Results = filelib:wildcard(binary_to_list(Pattern), binary_to_list(In)), + lists:map(fun list_to_binary/1, Results). + + +should_equal(Actual, Expected) -> + ?assertEqual(Expected, Actual), + nil. +should_not_equal(Actual, Expected) -> + ?assertNotEqual(Expected, Actual), + nil. +should_be_ok(A) -> + ?assertMatch({ok, _}, A), + element(2, A). +should_be_error(A) -> + ?assertMatch({error, _}, A), + element(2, A). diff --git a/aoc2023/build/packages/tom/build/packages/gleeunit/src/gleeunit_ffi.mjs b/aoc2023/build/packages/tom/build/packages/gleeunit/src/gleeunit_ffi.mjs new file mode 100644 index 0000000..339a843 --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleeunit/src/gleeunit_ffi.mjs @@ -0,0 +1,101 @@ +async function* gleamFiles(directory) { + for (let entry of await read_dir(directory)) { + let path = join_path(directory, entry); + if (path.endsWith(".gleam")) { + yield path; + } else { + try { + yield* gleamFiles(path); + } catch (error) { + // Could not read directory, assume it's a file + } + } + } +} + +async function readRootPackageName() { + let toml = await read_file("gleam.toml", "utf-8"); + for (let line of toml.split("\n")) { + let matches = line.match(/\s*name\s*=\s*"([a-z][a-z0-9_]*)"/); // Match regexp in compiler-cli/src/new.rs in validate_name() + if (matches) return matches[1]; + } + throw new Error("Could not determine package name from gleam.toml"); +} + +export async function main() { + let passes = 0; + let failures = 0; + + let packageName = await readRootPackageName(); + let dist = `../${packageName}/`; + + for await (let path of await gleamFiles("test")) { + let js_path = path.slice("test/".length).replace(".gleam", ".mjs"); + let module = await import(join_path(dist, js_path)); + for (let fnName of Object.keys(module)) { + if (!fnName.endsWith("_test")) continue; + try { + await module[fnName](); + write(`\u001b[32m.\u001b[0m`); + passes++; + } catch (error) { + let moduleName = "\n" + js_path.slice(0, -4); + let line = error.line ? `:${error.line}` : ""; + write(`\n❌ ${moduleName}.${fnName}${line}: ${error}\n`); + failures++; + } + } + } + + console.log(` +${passes + failures} tests, ${failures} failures`); + exit(failures ? 1 : 0); +} + +export function crash(message) { + throw new Error(message); +} + +function write(message) { + if (globalThis.Deno) { + Deno.stdout.writeSync(new TextEncoder().encode(message)); + } else { + process.stdout.write(message); + } +} + +function exit(code) { + if (globalThis.Deno) { + Deno.exit(code); + } else { + process.exit(code); + } +} + +async function read_dir(path) { + if (globalThis.Deno) { + let items = []; + for await (let item of Deno.readDir(path, { withFileTypes: true })) { + items.push(item.name); + } + return items; + } else { + let { readdir } = await import("fs/promises"); + return readdir(path); + } +} + +function join_path(a, b) { + if (a.endsWith("/")) return a + b; + return a + "/" + b; +} + +async function read_file(path) { + if (globalThis.Deno) { + return Deno.readTextFile(path); + } else { + let { readFile } = await import("fs/promises"); + let contents = await readFile(path); + return contents.toString(); + } +} diff --git a/aoc2023/build/packages/tom/build/packages/gleeunit/src/gleeunit_progress.erl b/aoc2023/build/packages/tom/build/packages/gleeunit/src/gleeunit_progress.erl new file mode 100644 index 0000000..1f68eb9 --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/gleeunit/src/gleeunit_progress.erl @@ -0,0 +1,607 @@ +%% A formatter adapted from Sean Cribb's https://github.com/seancribbs/eunit_formatters + +%% @doc A listener/reporter for eunit that prints '.' for each +%% success, 'F' for each failure, and 'E' for each error. It can also +%% optionally summarize the failures at the end. +-compile({nowarn_unused_function, [insert/2, to_list/1, to_list/2, size/1]}). +-module(gleeunit_progress). +-behaviour(eunit_listener). +-define(NOTEST, true). +-include_lib("eunit/include/eunit.hrl"). + +-define(RED, "\e[0;31m"). +-define(GREEN, "\e[0;32m"). +-define(YELLOW, "\e[0;33m"). +-define(WHITE, "\e[0;37m"). +-define(CYAN, "\e[0;36m"). +-define(RESET, "\e[0m"). + +-record(node,{ + rank = 0 :: non_neg_integer(), + key :: term(), + value :: term(), + children = new() :: binomial_heap() + }). + +-export_type([binomial_heap/0, heap_node/0]). +-type binomial_heap() :: [ heap_node() ]. +-type heap_node() :: #node{}. + +%% eunit_listener callbacks +-export([ + init/1, + handle_begin/3, + handle_end/3, + handle_cancel/3, + terminate/2, + start/0, + start/1 + ]). + +%% -- binomial_heap.erl content start -- + +-record(state, { + status = dict:new() :: euf_dict(), + failures = [] :: [[pos_integer()]], + skips = [] :: [[pos_integer()]], + timings = new() :: binomial_heap(), + colored = true :: boolean(), + profile = false :: boolean() + }). + +-type euf_dict() :: dict:dict(). + +-spec new() -> binomial_heap(). +new() -> + []. + +% Inserts a new pair into the heap (or creates a new heap) +-spec insert(term(), term()) -> binomial_heap(). +insert(Key,Value) -> + insert(Key,Value,[]). + +-spec insert(term(), term(), binomial_heap()) -> binomial_heap(). +insert(Key,Value,Forest) -> + insTree(#node{key=Key,value=Value},Forest). + +% Merges two heaps +-spec merge(binomial_heap(), binomial_heap()) -> binomial_heap(). +merge(TS1,[]) when is_list(TS1) -> TS1; +merge([],TS2) when is_list(TS2) -> TS2; +merge([#node{rank=R1}=T1|TS1]=F1,[#node{rank=R2}=T2|TS2]=F2) -> + if + R1 < R2 -> + [T1 | merge(TS1,F2)]; + R2 < R1 -> + [T2 | merge(F1, TS2)]; + true -> + insTree(link(T1,T2),merge(TS1,TS2)) + end. + +% Deletes the top entry from the heap and returns it +-spec delete(binomial_heap()) -> {{term(), term()}, binomial_heap()}. +delete(TS) -> + {#node{key=Key,value=Value,children=TS1},TS2} = getMin(TS), + {{Key,Value},merge(lists:reverse(TS1),TS2)}. + +% Turns the heap into list in heap order +-spec to_list(binomial_heap()) -> [{term(), term()}]. +to_list([]) -> []; +to_list(List) when is_list(List) -> + to_list([],List). +to_list(Acc, []) -> + lists:reverse(Acc); +to_list(Acc,Forest) -> + {Next, Trees} = delete(Forest), + to_list([Next|Acc], Trees). + +% Take N elements from the top of the heap +-spec take(non_neg_integer(), binomial_heap()) -> [{term(), term()}]. +take(N,Trees) when is_integer(N), is_list(Trees) -> + take(N,Trees,[]). +take(0,_Trees,Acc) -> + lists:reverse(Acc); +take(_N,[],Acc)-> + lists:reverse(Acc); +take(N,Trees,Acc) -> + {Top,T2} = delete(Trees), + take(N-1,T2,[Top|Acc]). + +% Get an estimate of the size based on the binomial property +-spec size(binomial_heap()) -> non_neg_integer(). +size(Forest) -> + erlang:trunc(lists:sum([math:pow(2,R) || #node{rank=R} <- Forest])). + +%% Private API +-spec link(heap_node(), heap_node()) -> heap_node(). +link(#node{rank=R,key=X1,children=C1}=T1,#node{key=X2,children=C2}=T2) -> + case X1 < X2 of + true -> + T1#node{rank=R+1,children=[T2|C1]}; + _ -> + T2#node{rank=R+1,children=[T1|C2]} + end. + +insTree(Tree, []) -> + [Tree]; +insTree(#node{rank=R1}=T1, [#node{rank=R2}=T2|Rest] = TS) -> + case R1 < R2 of + true -> + [T1|TS]; + _ -> + insTree(link(T1,T2),Rest) + end. + +getMin([T]) -> + {T,[]}; +getMin([#node{key=K} = T|TS]) -> + {#node{key=K1} = T1,TS1} = getMin(TS), + case K < K1 of + true -> {T,TS}; + _ -> {T1,[T|TS1]} + end. + +%% -- binomial_heap.erl content end -- + +%% Startup +start() -> + start([]). + +start(Options) -> + eunit_listener:start(?MODULE, Options). + +%%------------------------------------------ +%% eunit_listener callbacks +%%------------------------------------------ +init(Options) -> + #state{colored=proplists:get_bool(colored, Options), + profile=proplists:get_bool(profile, Options)}. + +handle_begin(group, Data, St) -> + GID = proplists:get_value(id, Data), + Dict = St#state.status, + St#state{status=dict:store(GID, orddict:from_list([{type, group}|Data]), Dict)}; +handle_begin(test, Data, St) -> + TID = proplists:get_value(id, Data), + Dict = St#state.status, + St#state{status=dict:store(TID, orddict:from_list([{type, test}|Data]), Dict)}. + +handle_end(group, Data, St) -> + St#state{status=merge_on_end(Data, St#state.status)}; +handle_end(test, Data, St) -> + NewStatus = merge_on_end(Data, St#state.status), + St1 = print_progress(Data, St), + St2 = record_timing(Data, St1), + St2#state{status=NewStatus}. + +handle_cancel(_, Data, #state{status=Status, skips=Skips}=St) -> + Status1 = merge_on_end(Data, Status), + ID = proplists:get_value(id, Data), + St#state{status=Status1, skips=[ID|Skips]}. + +terminate({ok, Data}, St) -> + print_failures(St), + print_pending(St), + print_profile(St), + print_timing(St), + print_results(Data, St); +terminate({error, Reason}, St) -> + io:nl(), io:nl(), + print_colored(io_lib:format("Eunit failed: ~25p~n", [Reason]), ?RED, St), + sync_end(error). + +sync_end(Result) -> + receive + {stop, Reference, ReplyTo} -> + ReplyTo ! {result, Reference, Result}, + ok + end. + +%%------------------------------------------ +%% Print and collect information during run +%%------------------------------------------ +print_progress(Data, St) -> + TID = proplists:get_value(id, Data), + case proplists:get_value(status, Data) of + ok -> + print_progress_success(St), + St; + {skipped, _Reason} -> + print_progress_skipped(St), + St#state{skips=[TID|St#state.skips]}; + {error, Exception} -> + print_progress_failed(Exception, St), + St#state{failures=[TID|St#state.failures]} + end. + +record_timing(Data, State=#state{timings=T, profile=true}) -> + TID = proplists:get_value(id, Data), + case lists:keyfind(time, 1, Data) of + {time, Int} -> + %% It's a min-heap, so we insert negative numbers instead + %% of the actuals and normalize when we report on them. + T1 = insert(-Int, TID, T), + State#state{timings=T1}; + false -> + State + end; +record_timing(_Data, State) -> + State. + +print_progress_success(St) -> + print_colored(".", ?GREEN, St). + +print_progress_skipped(St) -> + print_colored("*", ?YELLOW, St). + +print_progress_failed(_Exc, St) -> + print_colored("F", ?RED, St). + +merge_on_end(Data, Dict) -> + ID = proplists:get_value(id, Data), + dict:update(ID, + fun(Old) -> + orddict:merge(fun merge_data/3, Old, orddict:from_list(Data)) + end, Dict). + +merge_data(_K, undefined, X) -> X; +merge_data(_K, X, undefined) -> X; +merge_data(_K, _, X) -> X. + +%%------------------------------------------ +%% Print information at end of run +%%------------------------------------------ +print_failures(#state{failures=[]}) -> + ok; +print_failures(#state{failures=Fails}=State) -> + io:nl(), + io:fwrite("Failures:~n",[]), + lists:foldr(print_failure_fun(State), 1, Fails), + ok. + +print_failure_fun(#state{status=Status}=State) -> + fun(Key, Count) -> + TestData = dict:fetch(Key, Status), + TestId = format_test_identifier(TestData), + io:fwrite("~n ~p) ~ts~n", [Count, TestId]), + print_failure_reason(proplists:get_value(status, TestData), + proplists:get_value(output, TestData), + State), + io:nl(), + Count + 1 + end. + +print_gleam_location(#{function := Function, line := Line, module := Module }, State) -> + X = indent(5, "location: ~s.~s:~p~n", [Module, Function, Line]), + print_colored(X, ?CYAN, State); +print_gleam_location(_, _) -> + ok. + +inspect(X) -> + gleam@string:inspect(X). + +print_gleam_failure_reason( + #{gleam_error := assert, message := Message, value := Value}, + State +) -> + print_colored(indent(5, "~s~n", [Message]), ?RED, State), + print_colored(indent(5, " value: ", []), ?RED, State), + print_colored(indent(0, "~ts~n", [inspect(Value)]), ?RESET, State); +print_gleam_failure_reason( + #{gleam_error := todo, message := Message}, + State +) -> + print_colored(indent(5, "todo expression run~n", []), ?RED, State), + print_colored(indent(5, " message: ", []), ?RED, State), + print_colored(indent(0, "~s~n", [Message]), ?RESET, State); +print_gleam_failure_reason(Error, State) -> + print_colored(indent(5, "~p~n", [Error]), ?RED, State). + +% New Gleeunit specific formatters +print_failure_reason( + {error, {error, #{gleam_error := _} = Error, Stack}}, Output, State +) when is_list(Stack) -> + print_gleam_failure_reason(Error, State), + print_gleam_location(Error, State), + print_stack(Stack, State), + print_failure_output(5, Output, State); +print_failure_reason({error, {error, {case_clause, Value}, Stack}}, Output, State) when is_list(Stack) -> + print_colored(indent(5, "No case clause matched~n", []), ?RED, State), + print_colored(indent(5, "Value: ", []), ?CYAN, State), + print_colored(indent(0, "~ts~n", [inspect(Value)]), ?RESET, State), + print_stack(Stack, State), + print_failure_output(5, Output, State); +% From the original Erlang version +print_failure_reason({skipped, Reason}, _Output, State) -> + print_colored(io_lib:format(" ~ts~n", [format_pending_reason(Reason)]), + ?RED, State); +print_failure_reason({error, {_Class, Term, _}}, Output, State) when + is_tuple(Term), tuple_size(Term) == 2, is_list(element(2, Term)) -> + print_assertion_failure(Term, State), + print_failure_output(5, Output, State); +print_failure_reason({error, {error, Error, Stack}}, Output, State) when is_list(Stack) -> + print_colored(indent(5, "Failure: ~p~n", [Error]), ?RED, State), + print_stack(Stack, State), + print_failure_output(5, Output, State); +print_failure_reason({error, Reason}, Output, State) -> + print_colored(indent(5, "Failure: ~p~n", [Reason]), ?RED, State), + print_failure_output(5, Output, State). + +gleam_format_module_name(Module) -> + string:replace(atom_to_list(Module), "@", "/", all). + +print_stack(Stack, State) -> + print_colored(indent(5, "stacktrace:~n", []), ?CYAN, State), + print_stackframes(Stack, State). +print_stackframes([{eunit_test, _, _, _} | Stack], State) -> + print_stackframes(Stack, State); +print_stackframes([{eunit_proc, _, _, _} | Stack], State) -> + print_stackframes(Stack, State); +print_stackframes([{Module, Function, _Arity, _Location} | Stack], State) -> + GleamModule = gleam_format_module_name(Module), + print_colored(indent(7, "~s.~p~n", [GleamModule, Function]), ?CYAN, State), + print_stackframes(Stack, State); +print_stackframes([], _State) -> + ok. + + +print_failure_output(_, <<>>, _) -> ok; +print_failure_output(_, undefined, _) -> ok; +print_failure_output(Indent, Output, State) -> + print_colored(indent(Indent, "output: ~ts", [Output]), ?CYAN, State). + +print_assertion_failure({Type, Props}, State) -> + FailureDesc = format_assertion_failure(Type, Props, 5), + print_colored(FailureDesc, ?RED, State), + io:nl(). + +print_pending(#state{skips=[]}) -> + ok; +print_pending(#state{status=Status, skips=Skips}=State) -> + io:nl(), + io:fwrite("Pending:~n", []), + lists:foreach(fun(ID) -> + Info = dict:fetch(ID, Status), + case proplists:get_value(reason, Info) of + undefined -> + ok; + Reason -> + print_pending_reason(Reason, Info, State) + end + end, lists:reverse(Skips)), + io:nl(). + +print_pending_reason(Reason0, Data, State) -> + Text = case proplists:get_value(type, Data) of + group -> + io_lib:format(" ~ts~n", [proplists:get_value(desc, Data)]); + test -> + io_lib:format(" ~ts~n", [format_test_identifier(Data)]) + end, + Reason = io_lib:format(" %% ~ts~n", [format_pending_reason(Reason0)]), + print_colored(Text, ?YELLOW, State), + print_colored(Reason, ?CYAN, State). + +print_profile(#state{timings=T, status=Status, profile=true}=State) -> + TopN = take(10, T), + TopNTime = abs(lists:sum([ Time || {Time, _} <- TopN ])), + TLG = dict:fetch([], Status), + TotalTime = proplists:get_value(time, TLG), + if TotalTime =/= undefined andalso TotalTime > 0 andalso TopN =/= [] -> + TopNPct = (TopNTime / TotalTime) * 100, + io:nl(), io:nl(), + io:fwrite("Top ~p slowest tests (~ts, ~.1f% of total time):", [length(TopN), format_time(TopNTime), TopNPct]), + lists:foreach(print_timing_fun(State), TopN), + io:nl(); + true -> ok + end; +print_profile(#state{profile=false}) -> + ok. + +print_timing(#state{status=Status}) -> + TLG = dict:fetch([], Status), + Time = proplists:get_value(time, TLG), + io:nl(), + io:fwrite("Finished in ~ts~n", [format_time(Time)]), + ok. + +print_results(Data, State) -> + Pass = proplists:get_value(pass, Data, 0), + Fail = proplists:get_value(fail, Data, 0), + Skip = proplists:get_value(skip, Data, 0), + Cancel = proplists:get_value(cancel, Data, 0), + Total = Pass + Fail + Skip + Cancel, + {Color, Result} = if Fail > 0 -> {?RED, error}; + Skip > 0; Cancel > 0 -> {?YELLOW, error}; + Pass =:= 0 -> {?YELLOW, ok}; + true -> {?GREEN, ok} + end, + print_results(Color, Total, Fail, Skip, Cancel, State), + sync_end(Result). + +print_results(Color, 0, _, _, _, State) -> + print_colored(Color, "0 tests\n", State); +print_results(Color, Total, Fail, Skip, Cancel, State) -> + SkipText = format_optional_result(Skip, "skipped"), + CancelText = format_optional_result(Cancel, "cancelled"), + Text = io_lib:format("~p tests, ~p failures~ts~ts~n", [Total, Fail, SkipText, CancelText]), + print_colored(Text, Color, State). + +print_timing_fun(#state{status=Status}=State) -> + fun({Time, Key}) -> + TestData = dict:fetch(Key, Status), + TestId = format_test_identifier(TestData), + io:nl(), + io:fwrite(" ~ts~n", [TestId]), + print_colored([" "|format_time(abs(Time))], ?CYAN, State) + end. + +%%------------------------------------------ +%% Print to the console with the given color +%% if enabled. +%%------------------------------------------ +print_colored(Text, Color, #state{colored=true}) -> + io:fwrite("~s~ts~s", [Color, Text, ?RESET]); +print_colored(Text, _Color, #state{colored=false}) -> + io:fwrite("~ts", [Text]). + +%%------------------------------------------ +%% Generic data formatters +%%------------------------------------------ +format_function_name(M, F) -> + M1 = gleam_format_module_name(M), + io_lib:format("~ts.~ts", [M1, F]). + +format_optional_result(0, _) -> + []; +format_optional_result(Count, Text) -> + io_lib:format(", ~p ~ts", [Count, Text]). + +format_test_identifier(Data) -> + {Mod, Fun, _} = proplists:get_value(source, Data), + Line = case proplists:get_value(line, Data) of + 0 -> ""; + L -> io_lib:format(":~p", [L]) + end, + Desc = case proplists:get_value(desc, Data) of + undefined -> ""; + DescText -> io_lib:format(": ~ts", [DescText]) + end, + io_lib:format("~ts~ts~ts", [format_function_name(Mod, Fun), Line, Desc]). + +format_time(undefined) -> + "? seconds"; +format_time(Time) -> + io_lib:format("~.3f seconds", [Time / 1000]). + +format_pending_reason({module_not_found, M}) -> + M1 = gleam_format_module_name(M), + io_lib:format("Module '~ts' missing", [M1]); +format_pending_reason({no_such_function, {M,F,_}}) -> + M1 = gleam_format_module_name(M), + io_lib:format("Function ~ts undefined", [format_function_name(M1,F)]); +format_pending_reason({exit, Reason}) -> + io_lib:format("Related process exited with reason: ~p", [Reason]); +format_pending_reason(Reason) -> + io_lib:format("Unknown error: ~p", [Reason]). + +%% @doc Formats all the known eunit assertions, you're on your own if +%% you make an assertion yourself. +format_assertion_failure(Type, Props, I) when Type =:= assertion_failed + ; Type =:= assert -> + Keys = proplists:get_keys(Props), + HasEUnitProps = ([expression, value] -- Keys) =:= [], + HasHamcrestProps = ([expected, actual, matcher] -- Keys) =:= [], + if + HasEUnitProps -> + [indent(I, "Failure: ?assert(~ts)~n", [proplists:get_value(expression, Props)]), + indent(I, " expected: true~n", []), + case proplists:get_value(value, Props) of + false -> + indent(I, " got: false", []); + {not_a_boolean, V} -> + indent(I, " got: ~p", [V]) + end]; + HasHamcrestProps -> + [indent(I, "Failure: ?assertThat(~p)~n", [proplists:get_value(matcher, Props)]), + indent(I, " expected: ~ts~n", [inspect(proplists:get_value(expected, Props))]), + indent(I, " got: ~ts", [inspect(proplists:get_value(actual, Props))])]; + true -> + [indent(I, "Failure: unknown assert: ~p", [Props])] + end; + +format_assertion_failure(Type, Props, I) when Type =:= assertMatch_failed + ; Type =:= assertMatch -> + Expr = proplists:get_value(expression, Props), + Pattern = proplists:get_value(pattern, Props), + Value = proplists:get_value(value, Props), + [indent(I, "Failure: ?assertMatch(~ts, ~ts)~n", [Pattern, Expr]), + indent(I, " expected: = ~ts~n", [Pattern]), + indent(I, " got: ~p", [Value])]; + +format_assertion_failure(Type, Props, I) when Type =:= assertNotMatch_failed + ; Type =:= assertNotMatch -> + Expr = proplists:get_value(expression, Props), + Pattern = proplists:get_value(pattern, Props), + Value = proplists:get_value(value, Props), + [indent(I, "Failure: ?assertNotMatch(~ts, ~ts)~n", [Pattern, Expr]), + indent(I, " expected not: = ~ts~n", [Pattern]), + indent(I, " got: ~p", [Value])]; + +format_assertion_failure(Type, Props, I) when Type =:= assertEqual_failed + ; Type =:= assertEqual -> + Expected = inspect(proplists:get_value(expected, Props)), + Value = inspect(proplists:get_value(value, Props)), + [indent(I, "Values were not equal~n", []), + indent(I, "expected: ~ts~n", [Expected]), + indent(I, " got: ~ts", [Value])]; + +format_assertion_failure(Type, Props, I) when Type =:= assertNotEqual_failed + ; Type =:= assertNotEqual -> + Value = inspect(proplists:get_value(value, Props)), + [indent(I, "Values were equal~n", []), + indent(I, "expected: not ~ts~n,", [Value]), + indent(I, " got: ~ts", [Value])]; + +format_assertion_failure(Type, Props, I) when Type =:= assertException_failed + ; Type =:= assertException -> + Expr = proplists:get_value(expression, Props), + Pattern = proplists:get_value(pattern, Props), + {Class, Term} = extract_exception_pattern(Pattern), % I hate that we have to do this, why not just give DATA + [indent(I, "Failure: ?assertException(~ts, ~ts, ~ts)~n", [Class, Term, Expr]), + case proplists:is_defined(unexpected_success, Props) of + true -> + [indent(I, " expected: exception ~ts but nothing was raised~n", [Pattern]), + indent(I, " got: value ~p", [proplists:get_value(unexpected_success, Props)])]; + false -> + Ex = proplists:get_value(unexpected_exception, Props), + [indent(I, " expected: exception ~ts~n", [Pattern]), + indent(I, " got: exception ~p", [Ex])] + end]; + +format_assertion_failure(Type, Props, I) when Type =:= assertNotException_failed + ; Type =:= assertNotException -> + Expr = proplists:get_value(expression, Props), + Pattern = proplists:get_value(pattern, Props), + {Class, Term} = extract_exception_pattern(Pattern), % I hate that we have to do this, why not just give DAT + Ex = proplists:get_value(unexpected_exception, Props), + [indent(I, "Failure: ?assertNotException(~ts, ~ts, ~ts)~n", [Class, Term, Expr]), + indent(I, " expected not: exception ~ts~n", [Pattern]), + indent(I, " got: exception ~p", [Ex])]; + +format_assertion_failure(Type, Props, I) when Type =:= command_failed + ; Type =:= command -> + Cmd = proplists:get_value(command, Props), + Expected = proplists:get_value(expected_status, Props), + Status = proplists:get_value(status, Props), + [indent(I, "Failure: ?cmdStatus(~p, ~p)~n", [Expected, Cmd]), + indent(I, " expected: status ~p~n", [Expected]), + indent(I, " got: status ~p", [Status])]; + +format_assertion_failure(Type, Props, I) when Type =:= assertCmd_failed + ; Type =:= assertCmd -> + Cmd = proplists:get_value(command, Props), + Expected = proplists:get_value(expected_status, Props), + Status = proplists:get_value(status, Props), + [indent(I, "Failure: ?assertCmdStatus(~p, ~p)~n", [Expected, Cmd]), + indent(I, " expected: status ~p~n", [Expected]), + indent(I, " got: status ~p", [Status])]; + +format_assertion_failure(Type, Props, I) when Type =:= assertCmdOutput_failed + ; Type =:= assertCmdOutput -> + Cmd = proplists:get_value(command, Props), + Expected = proplists:get_value(expected_output, Props), + Output = proplists:get_value(output, Props), + [indent(I, "Failure: ?assertCmdOutput(~p, ~p)~n", [Expected, Cmd]), + indent(I, " expected: ~p~n", [Expected]), + indent(I, " got: ~p", [Output])]; + +format_assertion_failure(Type, Props, I) -> + indent(I, "~p", [{Type, Props}]). + +indent(I, Fmt, Args) -> + io_lib:format("~" ++ integer_to_list(I) ++ "s" ++ Fmt, [" "|Args]). + +extract_exception_pattern(Str) -> + ["{", Class, Term|_] = re:split(Str, "[, ]{1,2}", [unicode,{return,list}]), + {Class, Term}. diff --git a/aoc2023/build/packages/tom/build/packages/packages.toml b/aoc2023/build/packages/tom/build/packages/packages.toml new file mode 100644 index 0000000..b7ab86d --- /dev/null +++ b/aoc2023/build/packages/tom/build/packages/packages.toml @@ -0,0 +1,3 @@ +[packages] +gleam_stdlib = "0.34.0" +gleeunit = "1.0.2" diff --git a/aoc2023/build/packages/tom/build/prod/erlang/gleam.lock b/aoc2023/build/packages/tom/build/prod/erlang/gleam.lock new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/aoc2023/build/packages/tom/build/prod/erlang/gleam.lock diff --git a/aoc2023/build/packages/tom/build/prod/javascript/gleam.lock b/aoc2023/build/packages/tom/build/prod/javascript/gleam.lock new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/aoc2023/build/packages/tom/build/prod/javascript/gleam.lock diff --git a/aoc2023/build/packages/tom/gleam.toml b/aoc2023/build/packages/tom/gleam.toml new file mode 100644 index 0000000..f131d09 --- /dev/null +++ b/aoc2023/build/packages/tom/gleam.toml @@ -0,0 +1,13 @@ +name = "tom" +version = "0.2.1" + +description = "A pure Gleam TOML parser!" +licences = ["Apache-2.0"] +repository = { type = "github", user = "lpil", repo = "tom" } +links = [{ title = "TOML website", href = "https://toml.io/en/" }] + +[dependencies] +gleam_stdlib = "~> 0.32" + +[dev-dependencies] +gleeunit = "~> 1.0" diff --git a/aoc2023/build/packages/tom/include/tom_DateTimeValue.hrl b/aoc2023/build/packages/tom/include/tom_DateTimeValue.hrl new file mode 100644 index 0000000..3b1e660 --- /dev/null +++ b/aoc2023/build/packages/tom/include/tom_DateTimeValue.hrl @@ -0,0 +1,5 @@ +-record(date_time_value, { + date :: tom:date(), + time :: tom:time(), + offset :: tom:offset() +}). diff --git a/aoc2023/build/packages/tom/include/tom_DateValue.hrl b/aoc2023/build/packages/tom/include/tom_DateValue.hrl new file mode 100644 index 0000000..c41f901 --- /dev/null +++ b/aoc2023/build/packages/tom/include/tom_DateValue.hrl @@ -0,0 +1 @@ +-record(date_value, {year :: integer(), month :: integer(), day :: integer()}). diff --git a/aoc2023/build/packages/tom/include/tom_KeyAlreadyInUse.hrl b/aoc2023/build/packages/tom/include/tom_KeyAlreadyInUse.hrl new file mode 100644 index 0000000..930df26 --- /dev/null +++ b/aoc2023/build/packages/tom/include/tom_KeyAlreadyInUse.hrl @@ -0,0 +1 @@ +-record(key_already_in_use, {key :: list(binary())}). diff --git a/aoc2023/build/packages/tom/include/tom_NotFound.hrl b/aoc2023/build/packages/tom/include/tom_NotFound.hrl new file mode 100644 index 0000000..19c9a17 --- /dev/null +++ b/aoc2023/build/packages/tom/include/tom_NotFound.hrl @@ -0,0 +1 @@ +-record(not_found, {key :: list(binary())}). diff --git a/aoc2023/build/packages/tom/include/tom_Offset.hrl b/aoc2023/build/packages/tom/include/tom_Offset.hrl new file mode 100644 index 0000000..a58a8e1 --- /dev/null +++ b/aoc2023/build/packages/tom/include/tom_Offset.hrl @@ -0,0 +1,5 @@ +-record(offset, { + direction :: tom:sign(), + hours :: integer(), + minutes :: integer() +}). diff --git a/aoc2023/build/packages/tom/include/tom_TimeValue.hrl b/aoc2023/build/packages/tom/include/tom_TimeValue.hrl new file mode 100644 index 0000000..e1275de --- /dev/null +++ b/aoc2023/build/packages/tom/include/tom_TimeValue.hrl @@ -0,0 +1,6 @@ +-record(time_value, { + hour :: integer(), + minute :: integer(), + second :: integer(), + millisecond :: integer() +}). diff --git a/aoc2023/build/packages/tom/include/tom_Unexpected.hrl b/aoc2023/build/packages/tom/include/tom_Unexpected.hrl new file mode 100644 index 0000000..ab1091c --- /dev/null +++ b/aoc2023/build/packages/tom/include/tom_Unexpected.hrl @@ -0,0 +1 @@ +-record(unexpected, {got :: binary(), expected :: binary()}). diff --git a/aoc2023/build/packages/tom/include/tom_WrongType.hrl b/aoc2023/build/packages/tom/include/tom_WrongType.hrl new file mode 100644 index 0000000..ae57352 --- /dev/null +++ b/aoc2023/build/packages/tom/include/tom_WrongType.hrl @@ -0,0 +1,5 @@ +-record(wrong_type, { + key :: list(binary()), + expected :: binary(), + got :: binary() +}). diff --git a/aoc2023/build/packages/tom/manifest.toml b/aoc2023/build/packages/tom/manifest.toml new file mode 100644 index 0000000..e1aa028 --- /dev/null +++ b/aoc2023/build/packages/tom/manifest.toml @@ -0,0 +1,11 @@ +# This file was generated by Gleam +# You typically do not need to edit this file + +packages = [ + { name = "gleam_stdlib", version = "0.34.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "1FB8454D2991E9B4C0C804544D8A9AD0F6184725E20D63C3155F0AEB4230B016" }, + { name = "gleeunit", version = "1.0.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "D364C87AFEB26BDB4FB8A5ABDE67D635DC9FA52D6AB68416044C35B096C6882D" }, +] + +[requirements] +gleam_stdlib = { version = "~> 0.32" } +gleeunit = { version = "~> 1.0" } diff --git a/aoc2023/build/packages/tom/src/tom.app.src b/aoc2023/build/packages/tom/src/tom.app.src new file mode 100644 index 0000000..051649c --- /dev/null +++ b/aoc2023/build/packages/tom/src/tom.app.src @@ -0,0 +1,8 @@ +{application, tom, [ + {vsn, "0.2.1"}, + {applications, [gleam_stdlib, + gleeunit]}, + {description, "A pure Gleam TOML parser!"}, + {modules, [tom]}, + {registered, []} +]}. diff --git a/aoc2023/build/packages/tom/src/tom.erl b/aoc2023/build/packages/tom/src/tom.erl new file mode 100644 index 0000000..4f5c071 --- /dev/null +++ b/aoc2023/build/packages/tom/src/tom.erl @@ -0,0 +1,2140 @@ +-module(tom). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function]). + +-export([get/2, get_int/2, get_float/2, get_bool/2, get_string/2, get_date/2, get_time/2, get_date_time/2, get_array/2, get_table/2, get_number/2, parse/1]). +-export_type([toml/0, date_time/0, date/0, time/0, offset/0, sign/0, parse_error/0, number_/0, get_error/0]). + +-type toml() :: {int, integer()} | + {float, float()} | + {infinity, sign()} | + {nan, sign()} | + {bool, boolean()} | + {string, binary()} | + {date, date()} | + {time, time()} | + {date_time, date_time()} | + {array, list(toml())} | + {array_of_tables, list(gleam@map:map_(binary(), toml()))} | + {table, gleam@map:map_(binary(), toml())} | + {inline_table, gleam@map:map_(binary(), toml())}. + +-type date_time() :: {date_time_value, date(), time(), offset()}. + +-type date() :: {date_value, integer(), integer(), integer()}. + +-type time() :: {time_value, integer(), integer(), integer(), integer()}. + +-type offset() :: local | {offset, sign(), integer(), integer()}. + +-type sign() :: positive | negative. + +-type parse_error() :: {unexpected, binary(), binary()} | + {key_already_in_use, list(binary())}. + +-type number_() :: {number_int, integer()} | + {number_float, float()} | + {number_infinity, sign()} | + {number_nan, sign()}. + +-type get_error() :: {not_found, list(binary())} | + {wrong_type, list(binary()), binary(), binary()}. + +-spec classify(toml()) -> binary(). +classify(Toml) -> + case Toml of + {int, _} -> + <<"Int"/utf8>>; + + {float, _} -> + <<"Float"/utf8>>; + + {nan, positive} -> + <<"NaN"/utf8>>; + + {nan, negative} -> + <<"Negative NaN"/utf8>>; + + {infinity, positive} -> + <<"Infinity"/utf8>>; + + {infinity, negative} -> + <<"Negative Infinity"/utf8>>; + + {bool, _} -> + <<"Bool"/utf8>>; + + {string, _} -> + <<"String"/utf8>>; + + {date, _} -> + <<"Date"/utf8>>; + + {time, _} -> + <<"Time"/utf8>>; + + {date_time, _} -> + <<"DateTime"/utf8>>; + + {array, _} -> + <<"Array"/utf8>>; + + {array_of_tables, _} -> + <<"Array"/utf8>>; + + {table, _} -> + <<"Table"/utf8>>; + + {inline_table, _} -> + <<"Table"/utf8>> + end. + +-spec push_key({ok, FIU} | {error, get_error()}, binary()) -> {ok, FIU} | + {error, get_error()}. +push_key(Result, Key) -> + case Result of + {ok, T} -> + {ok, T}; + + {error, {not_found, Path}} -> + {error, {not_found, [Key | Path]}}; + + {error, {wrong_type, Path@1, Expected, Got}} -> + {error, {wrong_type, [Key | Path@1], Expected, Got}} + end. + +-spec get(gleam@map:map_(binary(), toml()), list(binary())) -> {ok, toml()} | + {error, get_error()}. +get(Toml, Key) -> + case Key of + [] -> + {error, {not_found, []}}; + + [K] -> + gleam@result:replace_error(gleam@map:get(Toml, K), {not_found, [K]}); + + [K@1 | Key@1] -> + case gleam@map:get(Toml, K@1) of + {ok, {table, T}} -> + push_key(get(T, Key@1), K@1); + + {ok, Other} -> + {error, + {wrong_type, [K@1], <<"Table"/utf8>>, classify(Other)}}; + + {error, _} -> + {error, {not_found, [K@1]}} + end + end. + +-spec get_int(gleam@map:map_(binary(), toml()), list(binary())) -> {ok, + integer()} | + {error, get_error()}. +get_int(Toml, Key) -> + case get(Toml, Key) of + {ok, {int, I}} -> + {ok, I}; + + {ok, Other} -> + {error, {wrong_type, Key, <<"Int"/utf8>>, classify(Other)}}; + + {error, E} -> + {error, E} + end. + +-spec get_float(gleam@map:map_(binary(), toml()), list(binary())) -> {ok, + float()} | + {error, get_error()}. +get_float(Toml, Key) -> + case get(Toml, Key) of + {ok, {float, I}} -> + {ok, I}; + + {ok, Other} -> + {error, {wrong_type, Key, <<"Float"/utf8>>, classify(Other)}}; + + {error, E} -> + {error, E} + end. + +-spec get_bool(gleam@map:map_(binary(), toml()), list(binary())) -> {ok, + boolean()} | + {error, get_error()}. +get_bool(Toml, Key) -> + case get(Toml, Key) of + {ok, {bool, I}} -> + {ok, I}; + + {ok, Other} -> + {error, {wrong_type, Key, <<"Bool"/utf8>>, classify(Other)}}; + + {error, E} -> + {error, E} + end. + +-spec get_string(gleam@map:map_(binary(), toml()), list(binary())) -> {ok, + binary()} | + {error, get_error()}. +get_string(Toml, Key) -> + case get(Toml, Key) of + {ok, {string, I}} -> + {ok, I}; + + {ok, Other} -> + {error, {wrong_type, Key, <<"String"/utf8>>, classify(Other)}}; + + {error, E} -> + {error, E} + end. + +-spec get_date(gleam@map:map_(binary(), toml()), list(binary())) -> {ok, date()} | + {error, get_error()}. +get_date(Toml, Key) -> + case get(Toml, Key) of + {ok, {date, I}} -> + {ok, I}; + + {ok, Other} -> + {error, {wrong_type, Key, <<"Date"/utf8>>, classify(Other)}}; + + {error, E} -> + {error, E} + end. + +-spec get_time(gleam@map:map_(binary(), toml()), list(binary())) -> {ok, time()} | + {error, get_error()}. +get_time(Toml, Key) -> + case get(Toml, Key) of + {ok, {time, I}} -> + {ok, I}; + + {ok, Other} -> + {error, {wrong_type, Key, <<"Time"/utf8>>, classify(Other)}}; + + {error, E} -> + {error, E} + end. + +-spec get_date_time(gleam@map:map_(binary(), toml()), list(binary())) -> {ok, + date_time()} | + {error, get_error()}. +get_date_time(Toml, Key) -> + case get(Toml, Key) of + {ok, {date_time, I}} -> + {ok, I}; + + {ok, Other} -> + {error, {wrong_type, Key, <<"DateTime"/utf8>>, classify(Other)}}; + + {error, E} -> + {error, E} + end. + +-spec get_array(gleam@map:map_(binary(), toml()), list(binary())) -> {ok, + list(toml())} | + {error, get_error()}. +get_array(Toml, Key) -> + case get(Toml, Key) of + {ok, {array, I}} -> + {ok, I}; + + {ok, {array_of_tables, I@1}} -> + {ok, gleam@list:map(I@1, fun(Field@0) -> {table, Field@0} end)}; + + {ok, Other} -> + {error, {wrong_type, Key, <<"Array"/utf8>>, classify(Other)}}; + + {error, E} -> + {error, E} + end. + +-spec get_table(gleam@map:map_(binary(), toml()), list(binary())) -> {ok, + gleam@map:map_(binary(), toml())} | + {error, get_error()}. +get_table(Toml, Key) -> + case get(Toml, Key) of + {ok, {table, I}} -> + {ok, I}; + + {ok, {inline_table, I@1}} -> + {ok, I@1}; + + {ok, Other} -> + {error, {wrong_type, Key, <<"Table"/utf8>>, classify(Other)}}; + + {error, E} -> + {error, E} + end. + +-spec get_number(gleam@map:map_(binary(), toml()), list(binary())) -> {ok, + number_()} | + {error, get_error()}. +get_number(Toml, Key) -> + case get(Toml, Key) of + {ok, {int, X}} -> + {ok, {number_int, X}}; + + {ok, {float, X@1}} -> + {ok, {number_float, X@1}}; + + {ok, {nan, X@2}} -> + {ok, {number_nan, X@2}}; + + {ok, {infinity, X@3}} -> + {ok, {number_infinity, X@3}}; + + {ok, Other} -> + {error, {wrong_type, Key, <<"Number"/utf8>>, classify(Other)}}; + + {error, E} -> + {error, E} + end. + +-spec merge(gleam@map:map_(binary(), toml()), binary(), toml(), toml()) -> {ok, + gleam@map:map_(binary(), toml())} | + {error, list(binary())}. +merge(Table, Key, Old, New) -> + case {Old, New} of + {{array_of_tables, Tables}, {array_of_tables, New@1}} -> + {ok, + gleam@map:insert( + Table, + Key, + {array_of_tables, gleam@list:append(New@1, Tables)} + )}; + + {_, _} -> + {error, [Key]} + end. + +-spec insert_loop(gleam@map:map_(binary(), toml()), list(binary()), toml()) -> {ok, + gleam@map:map_(binary(), toml())} | + {error, list(binary())}. +insert_loop(Table, Key, Value) -> + case Key of + [] -> + erlang:error(#{gleam_error => panic, + message => <<"unreachable"/utf8>>, + module => <<"tom"/utf8>>, + function => <<"insert_loop"/utf8>>, + line => 511}); + + [K] -> + case gleam@map:get(Table, K) of + {error, nil} -> + {ok, gleam@map:insert(Table, K, Value)}; + + {ok, Old} -> + merge(Table, K, Old, Value) + end; + + [K@1 | Key@1] -> + case gleam@map:get(Table, K@1) of + {error, nil} -> + case insert_loop(gleam@map:new(), Key@1, Value) of + {ok, Inner} -> + {ok, gleam@map:insert(Table, K@1, {table, Inner})}; + + {error, Path} -> + {error, [K@1 | Path]} + end; + + {ok, {array_of_tables, [Inner@1 | Rest]}} -> + case insert_loop(Inner@1, Key@1, Value) of + {ok, Inner@2} -> + {ok, + gleam@map:insert( + Table, + K@1, + {array_of_tables, [Inner@2 | Rest]} + )}; + + {error, Path@1} -> + {error, [K@1 | Path@1]} + end; + + {ok, {table, Inner@3}} -> + case insert_loop(Inner@3, Key@1, Value) of + {ok, Inner@4} -> + {ok, gleam@map:insert(Table, K@1, {table, Inner@4})}; + + {error, Path@2} -> + {error, [K@1 | Path@2]} + end; + + {ok, _} -> + {error, [K@1]} + end + end. + +-spec insert(gleam@map:map_(binary(), toml()), list(binary()), toml()) -> {ok, + gleam@map:map_(binary(), toml())} | + {error, parse_error()}. +insert(Table, Key, Value) -> + case insert_loop(Table, Key, Value) of + {ok, Table@1} -> + {ok, Table@1}; + + {error, Path} -> + {error, {key_already_in_use, Path}} + end. + +-spec expect_end_of_line( + list(binary()), + fun((list(binary())) -> {ok, {FKZ, list(binary())}} | {error, parse_error()}) +) -> {ok, {FKZ, list(binary())}} | {error, parse_error()}. +expect_end_of_line(Input, Next) -> + case Input of + [<<"\n"/utf8>> | Input@1] -> + Next(Input@1); + + [<<"\r\n"/utf8>> | Input@2] -> + Next(Input@2); + + [G | _] -> + {error, {unexpected, G, <<"\n"/utf8>>}}; + + [] -> + {error, {unexpected, <<"EOF"/utf8>>, <<"\n"/utf8>>}} + end. + +-spec parse_key_quoted(list(binary()), binary(), binary()) -> {ok, + {binary(), list(binary())}} | + {error, parse_error()}. +parse_key_quoted(Input, Close, Name) -> + case Input of + [G | Input@1] when G =:= Close -> + {ok, {Name, Input@1}}; + + [G@1 | Input@2] -> + parse_key_quoted(Input@2, Close, <<Name/binary, G@1/binary>>); + + [] -> + {error, {unexpected, <<"EOF"/utf8>>, Close}} + end. + +-spec parse_key_bare(list(binary()), binary()) -> {ok, + {binary(), list(binary())}} | + {error, parse_error()}. +parse_key_bare(Input, Name) -> + case Input of + [<<" "/utf8>> | Input@1] when Name =/= <<""/utf8>> -> + {ok, {Name, Input@1}}; + + [<<"="/utf8>> | _] when Name =/= <<""/utf8>> -> + {ok, {Name, Input}}; + + [<<"."/utf8>> | _] when Name =/= <<""/utf8>> -> + {ok, {Name, Input}}; + + [<<"]"/utf8>> | _] when Name =/= <<""/utf8>> -> + {ok, {Name, Input}}; + + [<<","/utf8>> | _] when Name =/= <<""/utf8>> -> + {error, {unexpected, <<","/utf8>>, <<"="/utf8>>}}; + + [<<"\n"/utf8>> | _] when Name =/= <<""/utf8>> -> + {error, {unexpected, <<"\n"/utf8>>, <<"="/utf8>>}}; + + [<<"\r\n"/utf8>> | _] when Name =/= <<""/utf8>> -> + {error, {unexpected, <<"\r\n"/utf8>>, <<"="/utf8>>}}; + + [<<"\n"/utf8>> | _] -> + {error, {unexpected, <<"\n"/utf8>>, <<"key"/utf8>>}}; + + [<<"\r\n"/utf8>> | _] -> + {error, {unexpected, <<"\r\n"/utf8>>, <<"key"/utf8>>}}; + + [<<"]"/utf8>> | _] -> + {error, {unexpected, <<"]"/utf8>>, <<"key"/utf8>>}}; + + [<<","/utf8>> | _] -> + {error, {unexpected, <<","/utf8>>, <<"key"/utf8>>}}; + + [G | Input@2] -> + parse_key_bare(Input@2, <<Name/binary, G/binary>>); + + [] -> + {error, {unexpected, <<"EOF"/utf8>>, <<"key"/utf8>>}} + end. + +-spec skip_line_whitespace(list(binary())) -> list(binary()). +skip_line_whitespace(Input) -> + gleam@list:drop_while( + Input, + fun(G) -> (G =:= <<" "/utf8>>) orelse (G =:= <<"\t"/utf8>>) end + ). + +-spec parse_key_segment(list(binary())) -> {ok, {binary(), list(binary())}} | + {error, parse_error()}. +parse_key_segment(Input) -> + Input@1 = skip_line_whitespace(Input), + case Input@1 of + [<<"="/utf8>> | _] -> + {error, {unexpected, <<"="/utf8>>, <<"Key"/utf8>>}}; + + [<<"\n"/utf8>> | _] -> + {error, {unexpected, <<"\n"/utf8>>, <<"Key"/utf8>>}}; + + [<<"\r\n"/utf8>> | _] -> + {error, {unexpected, <<"\r\n"/utf8>>, <<"Key"/utf8>>}}; + + [<<"["/utf8>> | _] -> + {error, {unexpected, <<"["/utf8>>, <<"Key"/utf8>>}}; + + [<<"\""/utf8>> | Input@2] -> + parse_key_quoted(Input@2, <<"\""/utf8>>, <<""/utf8>>); + + [<<"'"/utf8>> | Input@3] -> + parse_key_quoted(Input@3, <<"'"/utf8>>, <<""/utf8>>); + + _ -> + parse_key_bare(Input@1, <<""/utf8>>) + end. + +-spec skip_whitespace(list(binary())) -> list(binary()). +skip_whitespace(Input) -> + case Input of + [<<" "/utf8>> | Input@1] -> + skip_whitespace(Input@1); + + [<<"\t"/utf8>> | Input@2] -> + skip_whitespace(Input@2); + + [<<"\n"/utf8>> | Input@3] -> + skip_whitespace(Input@3); + + [<<"\r\n"/utf8>> | Input@4] -> + skip_whitespace(Input@4); + + Input@5 -> + Input@5 + end. + +-spec drop_comments(list(binary()), list(binary())) -> list(binary()). +drop_comments(Input, Acc) -> + case Input of + [<<"#"/utf8>> | Input@1] -> + _pipe = Input@1, + _pipe@1 = gleam@list:drop_while( + _pipe, + fun(G) -> G /= <<"\n"/utf8>> end + ), + drop_comments(_pipe@1, Acc); + + [G@1 | Input@2] -> + drop_comments(Input@2, [G@1 | Acc]); + + [] -> + gleam@list:reverse(Acc) + end. + +-spec do( + {ok, {FLK, list(binary())}} | {error, parse_error()}, + fun((FLK, list(binary())) -> {ok, FLN} | {error, parse_error()}) +) -> {ok, FLN} | {error, parse_error()}. +do(Result, Next) -> + case Result of + {ok, {A, Input}} -> + Next(A, Input); + + {error, E} -> + {error, E} + end. + +-spec parse_key(list(binary()), list(binary())) -> {ok, + {list(binary()), list(binary())}} | + {error, parse_error()}. +parse_key(Input, Segments) -> + do( + parse_key_segment(Input), + fun(Segment, Input@1) -> + Segments@1 = [Segment | Segments], + Input@2 = skip_line_whitespace(Input@1), + case Input@2 of + [<<"."/utf8>> | Input@3] -> + parse_key(Input@3, Segments@1); + + _ -> + {ok, {gleam@list:reverse(Segments@1), Input@2}} + end + end + ). + +-spec expect( + list(binary()), + binary(), + fun((list(binary())) -> {ok, {FLS, list(binary())}} | {error, parse_error()}) +) -> {ok, {FLS, list(binary())}} | {error, parse_error()}. +expect(Input, Expected, Next) -> + case Input of + [G | Input@1] when G =:= Expected -> + Next(Input@1); + + [G@1 | _] -> + {error, {unexpected, G@1, Expected}}; + + [] -> + {error, {unexpected, <<"EOF"/utf8>>, Expected}} + end. + +-spec parse_table_header(list(binary())) -> {ok, + {list(binary()), list(binary())}} | + {error, parse_error()}. +parse_table_header(Input) -> + Input@1 = skip_line_whitespace(Input), + do( + parse_key(Input@1, []), + fun(Key, Input@2) -> + expect( + Input@2, + <<"]"/utf8>>, + fun(Input@3) -> + Input@4 = skip_line_whitespace(Input@3), + expect_end_of_line( + Input@4, + fun(Input@5) -> {ok, {Key, Input@5}} end + ) + end + ) + end + ). + +-spec parse_hex(list(binary()), integer(), sign()) -> {ok, + {toml(), list(binary())}} | + {error, parse_error()}. +parse_hex(Input, Number, Sign) -> + case Input of + [<<"_"/utf8>> | Input@1] -> + parse_hex(Input@1, Number, Sign); + + [<<"0"/utf8>> | Input@2] -> + parse_hex(Input@2, (Number * 16) + 0, Sign); + + [<<"1"/utf8>> | Input@3] -> + parse_hex(Input@3, (Number * 16) + 1, Sign); + + [<<"2"/utf8>> | Input@4] -> + parse_hex(Input@4, (Number * 16) + 2, Sign); + + [<<"3"/utf8>> | Input@5] -> + parse_hex(Input@5, (Number * 16) + 3, Sign); + + [<<"4"/utf8>> | Input@6] -> + parse_hex(Input@6, (Number * 16) + 4, Sign); + + [<<"5"/utf8>> | Input@7] -> + parse_hex(Input@7, (Number * 16) + 5, Sign); + + [<<"6"/utf8>> | Input@8] -> + parse_hex(Input@8, (Number * 16) + 6, Sign); + + [<<"7"/utf8>> | Input@9] -> + parse_hex(Input@9, (Number * 16) + 7, Sign); + + [<<"8"/utf8>> | Input@10] -> + parse_hex(Input@10, (Number * 16) + 8, Sign); + + [<<"9"/utf8>> | Input@11] -> + parse_hex(Input@11, (Number * 16) + 9, Sign); + + [<<"a"/utf8>> | Input@12] -> + parse_hex(Input@12, (Number * 16) + 10, Sign); + + [<<"b"/utf8>> | Input@13] -> + parse_hex(Input@13, (Number * 16) + 11, Sign); + + [<<"c"/utf8>> | Input@14] -> + parse_hex(Input@14, (Number * 16) + 12, Sign); + + [<<"d"/utf8>> | Input@15] -> + parse_hex(Input@15, (Number * 16) + 13, Sign); + + [<<"e"/utf8>> | Input@16] -> + parse_hex(Input@16, (Number * 16) + 14, Sign); + + [<<"f"/utf8>> | Input@17] -> + parse_hex(Input@17, (Number * 16) + 15, Sign); + + [<<"A"/utf8>> | Input@18] -> + parse_hex(Input@18, (Number * 16) + 10, Sign); + + [<<"B"/utf8>> | Input@19] -> + parse_hex(Input@19, (Number * 16) + 11, Sign); + + [<<"C"/utf8>> | Input@20] -> + parse_hex(Input@20, (Number * 16) + 12, Sign); + + [<<"D"/utf8>> | Input@21] -> + parse_hex(Input@21, (Number * 16) + 13, Sign); + + [<<"E"/utf8>> | Input@22] -> + parse_hex(Input@22, (Number * 16) + 14, Sign); + + [<<"F"/utf8>> | Input@23] -> + parse_hex(Input@23, (Number * 16) + 15, Sign); + + Input@24 -> + Number@1 = case Sign of + positive -> + Number; + + negative -> + - Number + end, + {ok, {{int, Number@1}, Input@24}} + end. + +-spec parse_octal(list(binary()), integer(), sign()) -> {ok, + {toml(), list(binary())}} | + {error, parse_error()}. +parse_octal(Input, Number, Sign) -> + case Input of + [<<"_"/utf8>> | Input@1] -> + parse_octal(Input@1, Number, Sign); + + [<<"0"/utf8>> | Input@2] -> + parse_octal(Input@2, (Number * 8) + 0, Sign); + + [<<"1"/utf8>> | Input@3] -> + parse_octal(Input@3, (Number * 8) + 1, Sign); + + [<<"2"/utf8>> | Input@4] -> + parse_octal(Input@4, (Number * 8) + 2, Sign); + + [<<"3"/utf8>> | Input@5] -> + parse_octal(Input@5, (Number * 8) + 3, Sign); + + [<<"4"/utf8>> | Input@6] -> + parse_octal(Input@6, (Number * 8) + 4, Sign); + + [<<"5"/utf8>> | Input@7] -> + parse_octal(Input@7, (Number * 8) + 5, Sign); + + [<<"6"/utf8>> | Input@8] -> + parse_octal(Input@8, (Number * 8) + 6, Sign); + + [<<"7"/utf8>> | Input@9] -> + parse_octal(Input@9, (Number * 8) + 7, Sign); + + Input@10 -> + Number@1 = case Sign of + positive -> + Number; + + negative -> + - Number + end, + {ok, {{int, Number@1}, Input@10}} + end. + +-spec parse_binary(list(binary()), integer(), sign()) -> {ok, + {toml(), list(binary())}} | + {error, parse_error()}. +parse_binary(Input, Number, Sign) -> + case Input of + [<<"_"/utf8>> | Input@1] -> + parse_binary(Input@1, Number, Sign); + + [<<"0"/utf8>> | Input@2] -> + parse_binary(Input@2, (Number * 2) + 0, Sign); + + [<<"1"/utf8>> | Input@3] -> + parse_binary(Input@3, (Number * 2) + 1, Sign); + + Input@4 -> + Number@1 = case Sign of + positive -> + Number; + + negative -> + - Number + end, + {ok, {{int, Number@1}, Input@4}} + end. + +-spec parse_exponent(list(binary()), float(), sign(), integer(), sign()) -> {ok, + {toml(), list(binary())}} | + {error, parse_error()}. +parse_exponent(Input, N, N_sign, Ex, Ex_sign) -> + case Input of + [<<"_"/utf8>> | Input@1] -> + parse_exponent(Input@1, N, N_sign, Ex, Ex_sign); + + [<<"0"/utf8>> | Input@2] -> + parse_exponent(Input@2, N, N_sign, Ex * 10, Ex_sign); + + [<<"1"/utf8>> | Input@3] -> + parse_exponent(Input@3, N, N_sign, (Ex * 10) + 1, Ex_sign); + + [<<"2"/utf8>> | Input@4] -> + parse_exponent(Input@4, N, N_sign, (Ex * 10) + 2, Ex_sign); + + [<<"3"/utf8>> | Input@5] -> + parse_exponent(Input@5, N, N_sign, (Ex * 10) + 3, Ex_sign); + + [<<"4"/utf8>> | Input@6] -> + parse_exponent(Input@6, N, N_sign, (Ex * 10) + 4, Ex_sign); + + [<<"5"/utf8>> | Input@7] -> + parse_exponent(Input@7, N, N_sign, (Ex * 10) + 5, Ex_sign); + + [<<"6"/utf8>> | Input@8] -> + parse_exponent(Input@8, N, N_sign, (Ex * 10) + 6, Ex_sign); + + [<<"7"/utf8>> | Input@9] -> + parse_exponent(Input@9, N, N_sign, (Ex * 10) + 7, Ex_sign); + + [<<"8"/utf8>> | Input@10] -> + parse_exponent(Input@10, N, N_sign, (Ex * 10) + 8, Ex_sign); + + [<<"9"/utf8>> | Input@11] -> + parse_exponent(Input@11, N, N_sign, (Ex * 10) + 9, Ex_sign); + + Input@12 -> + Number = case N_sign of + positive -> + N; + + negative -> + N * -1.0 + end, + Exponent = gleam@int:to_float(case Ex_sign of + positive -> + Ex; + + negative -> + - Ex + end), + Multiplier@1 = case gleam@float:power(10.0, Exponent) of + {ok, Multiplier} -> + Multiplier; + + {error, _} -> + 1.0 + end, + {ok, {{float, Number * Multiplier@1}, Input@12}} + end. + +-spec parse_float(list(binary()), float(), sign(), float()) -> {ok, + {toml(), list(binary())}} | + {error, parse_error()}. +parse_float(Input, Number, Sign, Unit) -> + case Input of + [<<"_"/utf8>> | Input@1] -> + parse_float(Input@1, Number, Sign, Unit); + + [<<"0"/utf8>> | Input@2] -> + parse_float(Input@2, Number, Sign, Unit * 0.1); + + [<<"1"/utf8>> | Input@3] -> + parse_float(Input@3, Number + (1.0 * Unit), Sign, Unit * 0.1); + + [<<"2"/utf8>> | Input@4] -> + parse_float(Input@4, Number + (2.0 * Unit), Sign, Unit * 0.1); + + [<<"3"/utf8>> | Input@5] -> + parse_float(Input@5, Number + (3.0 * Unit), Sign, Unit * 0.1); + + [<<"4"/utf8>> | Input@6] -> + parse_float(Input@6, Number + (4.0 * Unit), Sign, Unit * 0.1); + + [<<"5"/utf8>> | Input@7] -> + parse_float(Input@7, Number + (5.0 * Unit), Sign, Unit * 0.1); + + [<<"6"/utf8>> | Input@8] -> + parse_float(Input@8, Number + (6.0 * Unit), Sign, Unit * 0.1); + + [<<"7"/utf8>> | Input@9] -> + parse_float(Input@9, Number + (7.0 * Unit), Sign, Unit * 0.1); + + [<<"8"/utf8>> | Input@10] -> + parse_float(Input@10, Number + (8.0 * Unit), Sign, Unit * 0.1); + + [<<"9"/utf8>> | Input@11] -> + parse_float(Input@11, Number + (9.0 * Unit), Sign, Unit * 0.1); + + [<<"e"/utf8>>, <<"+"/utf8>> | Input@12] -> + parse_exponent(Input@12, Number, Sign, 0, positive); + + [<<"e"/utf8>>, <<"-"/utf8>> | Input@13] -> + parse_exponent(Input@13, Number, Sign, 0, negative); + + [<<"e"/utf8>> | Input@14] -> + parse_exponent(Input@14, Number, Sign, 0, positive); + + [<<"E"/utf8>>, <<"+"/utf8>> | Input@15] -> + parse_exponent(Input@15, Number, Sign, 0, positive); + + [<<"E"/utf8>>, <<"-"/utf8>> | Input@16] -> + parse_exponent(Input@16, Number, Sign, 0, negative); + + [<<"E"/utf8>> | Input@17] -> + parse_exponent(Input@17, Number, Sign, 0, positive); + + Input@18 -> + Number@1 = case Sign of + positive -> + Number; + + negative -> + Number * -1.0 + end, + {ok, {{float, Number@1}, Input@18}} + end. + +-spec parse_string(list(binary()), binary()) -> {ok, {toml(), list(binary())}} | + {error, parse_error()}. +parse_string(Input, String) -> + case Input of + [<<"\""/utf8>> | Input@1] -> + {ok, {{string, String}, Input@1}}; + + [<<"\\"/utf8>>, <<"t"/utf8>> | Input@2] -> + parse_string(Input@2, <<String/binary, "\t"/utf8>>); + + [<<"\\"/utf8>>, <<"n"/utf8>> | Input@3] -> + parse_string(Input@3, <<String/binary, "\n"/utf8>>); + + [<<"\\"/utf8>>, <<"r"/utf8>> | Input@4] -> + parse_string(Input@4, <<String/binary, "\r"/utf8>>); + + [<<"\\"/utf8>>, <<"\""/utf8>> | Input@5] -> + parse_string(Input@5, <<String/binary, "\""/utf8>>); + + [<<"\\"/utf8>>, <<"\\"/utf8>> | Input@6] -> + parse_string(Input@6, <<String/binary, "\\"/utf8>>); + + [] -> + {error, {unexpected, <<"EOF"/utf8>>, <<"\""/utf8>>}}; + + [<<"\n"/utf8>> | _] -> + {error, {unexpected, <<"\n"/utf8>>, <<"\""/utf8>>}}; + + [<<"\r\n"/utf8>> | _] -> + {error, {unexpected, <<"\r\n"/utf8>>, <<"\""/utf8>>}}; + + [G | Input@7] -> + parse_string(Input@7, <<String/binary, G/binary>>) + end. + +-spec parse_multi_line_string(list(binary()), binary()) -> {ok, + {toml(), list(binary())}} | + {error, parse_error()}. +parse_multi_line_string(Input, String) -> + case Input of + [<<"\""/utf8>>, <<"\""/utf8>>, <<"\""/utf8>> | Input@1] -> + {ok, {{string, String}, Input@1}}; + + [<<"\\"/utf8>>, <<"\n"/utf8>> | Input@2] -> + parse_multi_line_string(skip_whitespace(Input@2), String); + + [<<"\\"/utf8>>, <<"\r\n"/utf8>> | Input@3] -> + parse_multi_line_string(skip_whitespace(Input@3), String); + + [<<"\r\n"/utf8>> | Input@4] when String =:= <<""/utf8>> -> + parse_multi_line_string(Input@4, String); + + [<<"\n"/utf8>> | Input@5] when String =:= <<""/utf8>> -> + parse_multi_line_string(Input@5, String); + + [<<"\r\n"/utf8>> | Input@6] when String =:= <<""/utf8>> -> + parse_multi_line_string(Input@6, String); + + [<<"\\"/utf8>>, <<"t"/utf8>> | Input@7] -> + parse_multi_line_string(Input@7, <<String/binary, "\t"/utf8>>); + + [<<"\\"/utf8>>, <<"n"/utf8>> | Input@8] -> + parse_multi_line_string(Input@8, <<String/binary, "\n"/utf8>>); + + [<<"\\"/utf8>>, <<"r"/utf8>> | Input@9] -> + parse_multi_line_string(Input@9, <<String/binary, "\r"/utf8>>); + + [<<"\\"/utf8>>, <<"\""/utf8>> | Input@10] -> + parse_multi_line_string(Input@10, <<String/binary, "\""/utf8>>); + + [<<"\\"/utf8>>, <<"\\"/utf8>> | Input@11] -> + parse_multi_line_string(Input@11, <<String/binary, "\\"/utf8>>); + + [] -> + {error, {unexpected, <<"EOF"/utf8>>, <<"\""/utf8>>}}; + + [G | Input@12] -> + parse_multi_line_string(Input@12, <<String/binary, G/binary>>) + end. + +-spec parse_multi_line_literal_string(list(binary()), binary()) -> {ok, + {toml(), list(binary())}} | + {error, parse_error()}. +parse_multi_line_literal_string(Input, String) -> + case Input of + [] -> + {error, {unexpected, <<"EOF"/utf8>>, <<"\""/utf8>>}}; + + [<<"'"/utf8>>, <<"'"/utf8>>, <<"'"/utf8>>, <<"'"/utf8>> | _] -> + {error, {unexpected, <<"''''"/utf8>>, <<"'''"/utf8>>}}; + + [<<"'"/utf8>>, <<"'"/utf8>>, <<"'"/utf8>> | Input@1] -> + {ok, {{string, String}, Input@1}}; + + [<<"\n"/utf8>> | Input@2] when String =:= <<""/utf8>> -> + parse_multi_line_literal_string(Input@2, String); + + [<<"\r\n"/utf8>> | Input@3] when String =:= <<""/utf8>> -> + parse_multi_line_literal_string(Input@3, String); + + [G | Input@4] -> + parse_multi_line_literal_string( + Input@4, + <<String/binary, G/binary>> + ) + end. + +-spec parse_literal_string(list(binary()), binary()) -> {ok, + {toml(), list(binary())}} | + {error, parse_error()}. +parse_literal_string(Input, String) -> + case Input of + [] -> + {error, {unexpected, <<"EOF"/utf8>>, <<"\""/utf8>>}}; + + [<<"\n"/utf8>> | _] -> + {error, {unexpected, <<"\n"/utf8>>, <<"'"/utf8>>}}; + + [<<"\r\n"/utf8>> | _] -> + {error, {unexpected, <<"\r\n"/utf8>>, <<"'"/utf8>>}}; + + [<<"'"/utf8>> | Input@1] -> + {ok, {{string, String}, Input@1}}; + + [G | Input@2] -> + parse_literal_string(Input@2, <<String/binary, G/binary>>) + end. + +-spec parse_time_ms(list(binary()), integer(), integer()) -> {ok, + {{integer(), integer()}, list(binary())}} | + {error, parse_error()}. +parse_time_ms(Input, Seconds, Ms) -> + case Input of + [<<"0"/utf8>> | Input@1] when Ms < 100000 -> + parse_time_ms(Input@1, Seconds, (Ms * 10) + 0); + + [<<"1"/utf8>> | Input@2] when Ms < 100000 -> + parse_time_ms(Input@2, Seconds, (Ms * 10) + 1); + + [<<"2"/utf8>> | Input@3] when Ms < 100000 -> + parse_time_ms(Input@3, Seconds, (Ms * 10) + 2); + + [<<"3"/utf8>> | Input@4] when Ms < 100000 -> + parse_time_ms(Input@4, Seconds, (Ms * 10) + 3); + + [<<"4"/utf8>> | Input@5] when Ms < 100000 -> + parse_time_ms(Input@5, Seconds, (Ms * 10) + 4); + + [<<"5"/utf8>> | Input@6] when Ms < 100000 -> + parse_time_ms(Input@6, Seconds, (Ms * 10) + 5); + + [<<"6"/utf8>> | Input@7] when Ms < 100000 -> + parse_time_ms(Input@7, Seconds, (Ms * 10) + 6); + + [<<"7"/utf8>> | Input@8] when Ms < 100000 -> + parse_time_ms(Input@8, Seconds, (Ms * 10) + 7); + + [<<"8"/utf8>> | Input@9] when Ms < 100000 -> + parse_time_ms(Input@9, Seconds, (Ms * 10) + 8); + + [<<"9"/utf8>> | Input@10] when Ms < 100000 -> + parse_time_ms(Input@10, Seconds, (Ms * 10) + 9); + + _ -> + {ok, {{Seconds, Ms}, Input}} + end. + +-spec parse_number_under_60(list(binary()), binary()) -> {ok, + {integer(), list(binary())}} | + {error, parse_error()}. +parse_number_under_60(Input, Expected) -> + case Input of + [<<"0"/utf8>>, <<"0"/utf8>> | Input@1] -> + {ok, {0, Input@1}}; + + [<<"0"/utf8>>, <<"1"/utf8>> | Input@2] -> + {ok, {1, Input@2}}; + + [<<"0"/utf8>>, <<"2"/utf8>> | Input@3] -> + {ok, {2, Input@3}}; + + [<<"0"/utf8>>, <<"3"/utf8>> | Input@4] -> + {ok, {3, Input@4}}; + + [<<"0"/utf8>>, <<"4"/utf8>> | Input@5] -> + {ok, {4, Input@5}}; + + [<<"0"/utf8>>, <<"5"/utf8>> | Input@6] -> + {ok, {5, Input@6}}; + + [<<"0"/utf8>>, <<"6"/utf8>> | Input@7] -> + {ok, {6, Input@7}}; + + [<<"0"/utf8>>, <<"7"/utf8>> | Input@8] -> + {ok, {7, Input@8}}; + + [<<"0"/utf8>>, <<"8"/utf8>> | Input@9] -> + {ok, {8, Input@9}}; + + [<<"0"/utf8>>, <<"9"/utf8>> | Input@10] -> + {ok, {9, Input@10}}; + + [<<"1"/utf8>>, <<"0"/utf8>> | Input@11] -> + {ok, {10, Input@11}}; + + [<<"1"/utf8>>, <<"1"/utf8>> | Input@12] -> + {ok, {11, Input@12}}; + + [<<"1"/utf8>>, <<"2"/utf8>> | Input@13] -> + {ok, {12, Input@13}}; + + [<<"1"/utf8>>, <<"3"/utf8>> | Input@14] -> + {ok, {13, Input@14}}; + + [<<"1"/utf8>>, <<"4"/utf8>> | Input@15] -> + {ok, {14, Input@15}}; + + [<<"1"/utf8>>, <<"5"/utf8>> | Input@16] -> + {ok, {15, Input@16}}; + + [<<"1"/utf8>>, <<"6"/utf8>> | Input@17] -> + {ok, {16, Input@17}}; + + [<<"1"/utf8>>, <<"7"/utf8>> | Input@18] -> + {ok, {17, Input@18}}; + + [<<"1"/utf8>>, <<"8"/utf8>> | Input@19] -> + {ok, {18, Input@19}}; + + [<<"1"/utf8>>, <<"9"/utf8>> | Input@20] -> + {ok, {19, Input@20}}; + + [<<"2"/utf8>>, <<"0"/utf8>> | Input@21] -> + {ok, {20, Input@21}}; + + [<<"2"/utf8>>, <<"1"/utf8>> | Input@22] -> + {ok, {21, Input@22}}; + + [<<"2"/utf8>>, <<"2"/utf8>> | Input@23] -> + {ok, {22, Input@23}}; + + [<<"2"/utf8>>, <<"3"/utf8>> | Input@24] -> + {ok, {23, Input@24}}; + + [<<"2"/utf8>>, <<"4"/utf8>> | Input@25] -> + {ok, {24, Input@25}}; + + [<<"2"/utf8>>, <<"5"/utf8>> | Input@26] -> + {ok, {25, Input@26}}; + + [<<"2"/utf8>>, <<"6"/utf8>> | Input@27] -> + {ok, {26, Input@27}}; + + [<<"2"/utf8>>, <<"7"/utf8>> | Input@28] -> + {ok, {27, Input@28}}; + + [<<"2"/utf8>>, <<"8"/utf8>> | Input@29] -> + {ok, {28, Input@29}}; + + [<<"2"/utf8>>, <<"9"/utf8>> | Input@30] -> + {ok, {29, Input@30}}; + + [<<"3"/utf8>>, <<"0"/utf8>> | Input@31] -> + {ok, {30, Input@31}}; + + [<<"3"/utf8>>, <<"1"/utf8>> | Input@32] -> + {ok, {31, Input@32}}; + + [<<"3"/utf8>>, <<"2"/utf8>> | Input@33] -> + {ok, {32, Input@33}}; + + [<<"3"/utf8>>, <<"3"/utf8>> | Input@34] -> + {ok, {33, Input@34}}; + + [<<"3"/utf8>>, <<"4"/utf8>> | Input@35] -> + {ok, {34, Input@35}}; + + [<<"3"/utf8>>, <<"5"/utf8>> | Input@36] -> + {ok, {35, Input@36}}; + + [<<"3"/utf8>>, <<"6"/utf8>> | Input@37] -> + {ok, {36, Input@37}}; + + [<<"3"/utf8>>, <<"7"/utf8>> | Input@38] -> + {ok, {37, Input@38}}; + + [<<"3"/utf8>>, <<"8"/utf8>> | Input@39] -> + {ok, {38, Input@39}}; + + [<<"3"/utf8>>, <<"9"/utf8>> | Input@40] -> + {ok, {39, Input@40}}; + + [<<"4"/utf8>>, <<"0"/utf8>> | Input@41] -> + {ok, {40, Input@41}}; + + [<<"4"/utf8>>, <<"1"/utf8>> | Input@42] -> + {ok, {41, Input@42}}; + + [<<"4"/utf8>>, <<"2"/utf8>> | Input@43] -> + {ok, {42, Input@43}}; + + [<<"4"/utf8>>, <<"3"/utf8>> | Input@44] -> + {ok, {43, Input@44}}; + + [<<"4"/utf8>>, <<"4"/utf8>> | Input@45] -> + {ok, {44, Input@45}}; + + [<<"4"/utf8>>, <<"5"/utf8>> | Input@46] -> + {ok, {45, Input@46}}; + + [<<"4"/utf8>>, <<"6"/utf8>> | Input@47] -> + {ok, {46, Input@47}}; + + [<<"4"/utf8>>, <<"7"/utf8>> | Input@48] -> + {ok, {47, Input@48}}; + + [<<"4"/utf8>>, <<"8"/utf8>> | Input@49] -> + {ok, {48, Input@49}}; + + [<<"4"/utf8>>, <<"9"/utf8>> | Input@50] -> + {ok, {49, Input@50}}; + + [<<"5"/utf8>>, <<"0"/utf8>> | Input@51] -> + {ok, {50, Input@51}}; + + [<<"5"/utf8>>, <<"1"/utf8>> | Input@52] -> + {ok, {51, Input@52}}; + + [<<"5"/utf8>>, <<"2"/utf8>> | Input@53] -> + {ok, {52, Input@53}}; + + [<<"5"/utf8>>, <<"3"/utf8>> | Input@54] -> + {ok, {53, Input@54}}; + + [<<"5"/utf8>>, <<"4"/utf8>> | Input@55] -> + {ok, {54, Input@55}}; + + [<<"5"/utf8>>, <<"5"/utf8>> | Input@56] -> + {ok, {55, Input@56}}; + + [<<"5"/utf8>>, <<"6"/utf8>> | Input@57] -> + {ok, {56, Input@57}}; + + [<<"5"/utf8>>, <<"7"/utf8>> | Input@58] -> + {ok, {57, Input@58}}; + + [<<"5"/utf8>>, <<"8"/utf8>> | Input@59] -> + {ok, {58, Input@59}}; + + [<<"5"/utf8>>, <<"9"/utf8>> | Input@60] -> + {ok, {59, Input@60}}; + + [G | _] -> + {error, {unexpected, G, Expected}}; + + [] -> + {error, {unexpected, <<"EOF"/utf8>>, Expected}} + end. + +-spec parse_hour_minute(list(binary())) -> {ok, + {{integer(), integer()}, list(binary())}} | + {error, parse_error()}. +parse_hour_minute(Input) -> + do(case Input of + [<<"0"/utf8>>, <<"0"/utf8>>, <<":"/utf8>> | Input@1] -> + {ok, {0, Input@1}}; + + [<<"0"/utf8>>, <<"1"/utf8>>, <<":"/utf8>> | Input@2] -> + {ok, {1, Input@2}}; + + [<<"0"/utf8>>, <<"2"/utf8>>, <<":"/utf8>> | Input@3] -> + {ok, {2, Input@3}}; + + [<<"0"/utf8>>, <<"3"/utf8>>, <<":"/utf8>> | Input@4] -> + {ok, {3, Input@4}}; + + [<<"0"/utf8>>, <<"4"/utf8>>, <<":"/utf8>> | Input@5] -> + {ok, {4, Input@5}}; + + [<<"0"/utf8>>, <<"5"/utf8>>, <<":"/utf8>> | Input@6] -> + {ok, {5, Input@6}}; + + [<<"0"/utf8>>, <<"6"/utf8>>, <<":"/utf8>> | Input@7] -> + {ok, {6, Input@7}}; + + [<<"0"/utf8>>, <<"7"/utf8>>, <<":"/utf8>> | Input@8] -> + {ok, {7, Input@8}}; + + [<<"0"/utf8>>, <<"8"/utf8>>, <<":"/utf8>> | Input@9] -> + {ok, {8, Input@9}}; + + [<<"0"/utf8>>, <<"9"/utf8>>, <<":"/utf8>> | Input@10] -> + {ok, {9, Input@10}}; + + [<<"1"/utf8>>, <<"0"/utf8>>, <<":"/utf8>> | Input@11] -> + {ok, {10, Input@11}}; + + [<<"1"/utf8>>, <<"1"/utf8>>, <<":"/utf8>> | Input@12] -> + {ok, {11, Input@12}}; + + [<<"1"/utf8>>, <<"2"/utf8>>, <<":"/utf8>> | Input@13] -> + {ok, {12, Input@13}}; + + [<<"1"/utf8>>, <<"3"/utf8>>, <<":"/utf8>> | Input@14] -> + {ok, {13, Input@14}}; + + [<<"1"/utf8>>, <<"4"/utf8>>, <<":"/utf8>> | Input@15] -> + {ok, {14, Input@15}}; + + [<<"1"/utf8>>, <<"5"/utf8>>, <<":"/utf8>> | Input@16] -> + {ok, {15, Input@16}}; + + [<<"1"/utf8>>, <<"6"/utf8>>, <<":"/utf8>> | Input@17] -> + {ok, {16, Input@17}}; + + [<<"1"/utf8>>, <<"7"/utf8>>, <<":"/utf8>> | Input@18] -> + {ok, {17, Input@18}}; + + [<<"1"/utf8>>, <<"8"/utf8>>, <<":"/utf8>> | Input@19] -> + {ok, {18, Input@19}}; + + [<<"1"/utf8>>, <<"9"/utf8>>, <<":"/utf8>> | Input@20] -> + {ok, {19, Input@20}}; + + [<<"2"/utf8>>, <<"0"/utf8>>, <<":"/utf8>> | Input@21] -> + {ok, {20, Input@21}}; + + [<<"2"/utf8>>, <<"1"/utf8>>, <<":"/utf8>> | Input@22] -> + {ok, {21, Input@22}}; + + [<<"2"/utf8>>, <<"2"/utf8>>, <<":"/utf8>> | Input@23] -> + {ok, {22, Input@23}}; + + [<<"2"/utf8>>, <<"3"/utf8>>, <<":"/utf8>> | Input@24] -> + {ok, {23, Input@24}}; + + [G | _] -> + {error, {unexpected, G, <<"time"/utf8>>}}; + + [] -> + {error, {unexpected, <<"EOF"/utf8>>, <<"time"/utf8>>}} + end, fun(Hours, Input@25) -> + do( + parse_number_under_60(Input@25, <<"minutes"/utf8>>), + fun(Minutes, Input@26) -> {ok, {{Hours, Minutes}, Input@26}} end + ) + end). + +-spec parse_time_s_ms(list(binary())) -> {ok, + {{integer(), integer()}, list(binary())}} | + {error, parse_error()}. +parse_time_s_ms(Input) -> + case Input of + [<<":"/utf8>> | Input@1] -> + do( + parse_number_under_60(Input@1, <<"seconds"/utf8>>), + fun(Seconds, Input@2) -> case Input@2 of + [<<"."/utf8>> | Input@3] -> + parse_time_ms(Input@3, Seconds, 0); + + _ -> + {ok, {{Seconds, 0}, Input@2}} + end end + ); + + _ -> + {ok, {{0, 0}, Input}} + end. + +-spec parse_time_minute(list(binary()), integer()) -> {ok, + {toml(), list(binary())}} | + {error, parse_error()}. +parse_time_minute(Input, Hours) -> + do( + parse_number_under_60(Input, <<"minutes"/utf8>>), + fun(Minutes, Input@1) -> + do( + parse_time_s_ms(Input@1), + fun(_use0, Input@2) -> + {Seconds, Ms} = _use0, + Time = {time_value, Hours, Minutes, Seconds, Ms}, + {ok, {{time, Time}, Input@2}} + end + ) + end + ). + +-spec parse_time_value(list(binary())) -> {ok, {time(), list(binary())}} | + {error, parse_error()}. +parse_time_value(Input) -> + do( + parse_hour_minute(Input), + fun(_use0, Input@1) -> + {Hours, Minutes} = _use0, + do( + parse_time_s_ms(Input@1), + fun(_use0@1, Input@2) -> + {Seconds, Ms} = _use0@1, + Time = {time_value, Hours, Minutes, Seconds, Ms}, + {ok, {Time, Input@2}} + end + ) + end + ). + +-spec parse_offset_hours(list(binary()), sign()) -> {ok, + {offset(), list(binary())}} | + {error, parse_error()}. +parse_offset_hours(Input, Sign) -> + do( + parse_hour_minute(Input), + fun(_use0, Input@1) -> + {Hours, Minutes} = _use0, + {ok, {{offset, Sign, Hours, Minutes}, Input@1}} + end + ). + +-spec parse_offset(list(binary())) -> {ok, {offset(), list(binary())}} | + {error, parse_error()}. +parse_offset(Input) -> + case Input of + [<<"Z"/utf8>> | Input@1] -> + {ok, {{offset, positive, 0, 0}, Input@1}}; + + [<<"+"/utf8>> | Input@2] -> + parse_offset_hours(Input@2, positive); + + [<<"-"/utf8>> | Input@3] -> + parse_offset_hours(Input@3, negative); + + _ -> + {ok, {local, Input}} + end. + +-spec parse_date_end(list(binary()), integer(), integer(), integer()) -> {ok, + {toml(), list(binary())}} | + {error, parse_error()}. +parse_date_end(Input, Year, Month, Day) -> + Date = {date_value, Year, Month, Day}, + case Input of + [<<" "/utf8>> | Input@1] -> + do( + parse_time_value(Input@1), + fun(Time, Input@2) -> + do( + parse_offset(Input@2), + fun(Offset, Input@3) -> + {ok, + {{date_time, + {date_time_value, Date, Time, Offset}}, + Input@3}} + end + ) + end + ); + + [<<"T"/utf8>> | Input@1] -> + do( + parse_time_value(Input@1), + fun(Time, Input@2) -> + do( + parse_offset(Input@2), + fun(Offset, Input@3) -> + {ok, + {{date_time, + {date_time_value, Date, Time, Offset}}, + Input@3}} + end + ) + end + ); + + _ -> + {ok, {{date, Date}, Input}} + end. + +-spec parse_date_day(list(binary()), integer(), integer()) -> {ok, + {toml(), list(binary())}} | + {error, parse_error()}. +parse_date_day(Input, Year, Month) -> + case Input of + [<<"0"/utf8>>, <<"1"/utf8>> | Input@1] -> + parse_date_end(Input@1, Year, Month, 1); + + [<<"0"/utf8>>, <<"2"/utf8>> | Input@2] -> + parse_date_end(Input@2, Year, Month, 2); + + [<<"0"/utf8>>, <<"3"/utf8>> | Input@3] -> + parse_date_end(Input@3, Year, Month, 3); + + [<<"0"/utf8>>, <<"4"/utf8>> | Input@4] -> + parse_date_end(Input@4, Year, Month, 4); + + [<<"0"/utf8>>, <<"5"/utf8>> | Input@5] -> + parse_date_end(Input@5, Year, Month, 5); + + [<<"0"/utf8>>, <<"6"/utf8>> | Input@6] -> + parse_date_end(Input@6, Year, Month, 6); + + [<<"0"/utf8>>, <<"7"/utf8>> | Input@7] -> + parse_date_end(Input@7, Year, Month, 7); + + [<<"0"/utf8>>, <<"8"/utf8>> | Input@8] -> + parse_date_end(Input@8, Year, Month, 8); + + [<<"0"/utf8>>, <<"9"/utf8>> | Input@9] -> + parse_date_end(Input@9, Year, Month, 9); + + [<<"1"/utf8>>, <<"0"/utf8>> | Input@10] -> + parse_date_end(Input@10, Year, Month, 10); + + [<<"1"/utf8>>, <<"1"/utf8>> | Input@11] -> + parse_date_end(Input@11, Year, Month, 11); + + [<<"1"/utf8>>, <<"2"/utf8>> | Input@12] -> + parse_date_end(Input@12, Year, Month, 12); + + [<<"1"/utf8>>, <<"3"/utf8>> | Input@13] -> + parse_date_end(Input@13, Year, Month, 13); + + [<<"1"/utf8>>, <<"4"/utf8>> | Input@14] -> + parse_date_end(Input@14, Year, Month, 14); + + [<<"1"/utf8>>, <<"5"/utf8>> | Input@15] -> + parse_date_end(Input@15, Year, Month, 15); + + [<<"1"/utf8>>, <<"6"/utf8>> | Input@16] -> + parse_date_end(Input@16, Year, Month, 16); + + [<<"1"/utf8>>, <<"7"/utf8>> | Input@17] -> + parse_date_end(Input@17, Year, Month, 17); + + [<<"1"/utf8>>, <<"8"/utf8>> | Input@18] -> + parse_date_end(Input@18, Year, Month, 18); + + [<<"1"/utf8>>, <<"9"/utf8>> | Input@19] -> + parse_date_end(Input@19, Year, Month, 19); + + [<<"2"/utf8>>, <<"0"/utf8>> | Input@20] -> + parse_date_end(Input@20, Year, Month, 20); + + [<<"2"/utf8>>, <<"1"/utf8>> | Input@21] -> + parse_date_end(Input@21, Year, Month, 21); + + [<<"2"/utf8>>, <<"2"/utf8>> | Input@22] -> + parse_date_end(Input@22, Year, Month, 22); + + [<<"2"/utf8>>, <<"3"/utf8>> | Input@23] -> + parse_date_end(Input@23, Year, Month, 23); + + [<<"2"/utf8>>, <<"4"/utf8>> | Input@24] -> + parse_date_end(Input@24, Year, Month, 24); + + [<<"2"/utf8>>, <<"5"/utf8>> | Input@25] -> + parse_date_end(Input@25, Year, Month, 25); + + [<<"2"/utf8>>, <<"6"/utf8>> | Input@26] -> + parse_date_end(Input@26, Year, Month, 26); + + [<<"2"/utf8>>, <<"7"/utf8>> | Input@27] -> + parse_date_end(Input@27, Year, Month, 27); + + [<<"2"/utf8>>, <<"8"/utf8>> | Input@28] -> + parse_date_end(Input@28, Year, Month, 28); + + [<<"2"/utf8>>, <<"9"/utf8>> | Input@29] -> + parse_date_end(Input@29, Year, Month, 29); + + [<<"3"/utf8>>, <<"0"/utf8>> | Input@30] -> + parse_date_end(Input@30, Year, Month, 30); + + [<<"3"/utf8>>, <<"1"/utf8>> | Input@31] -> + parse_date_end(Input@31, Year, Month, 31); + + [G | _] -> + {error, {unexpected, G, <<"date day"/utf8>>}}; + + [] -> + {error, {unexpected, <<"EOF"/utf8>>, <<"date day"/utf8>>}} + end. + +-spec parse_date(list(binary()), integer()) -> {ok, {toml(), list(binary())}} | + {error, parse_error()}. +parse_date(Input, Year) -> + case Input of + [<<"0"/utf8>>, <<"1"/utf8>>, <<"-"/utf8>> | Input@1] -> + parse_date_day(Input@1, Year, 1); + + [<<"0"/utf8>>, <<"2"/utf8>>, <<"-"/utf8>> | Input@2] -> + parse_date_day(Input@2, Year, 2); + + [<<"0"/utf8>>, <<"3"/utf8>>, <<"-"/utf8>> | Input@3] -> + parse_date_day(Input@3, Year, 3); + + [<<"0"/utf8>>, <<"4"/utf8>>, <<"-"/utf8>> | Input@4] -> + parse_date_day(Input@4, Year, 4); + + [<<"0"/utf8>>, <<"5"/utf8>>, <<"-"/utf8>> | Input@5] -> + parse_date_day(Input@5, Year, 5); + + [<<"0"/utf8>>, <<"6"/utf8>>, <<"-"/utf8>> | Input@6] -> + parse_date_day(Input@6, Year, 6); + + [<<"0"/utf8>>, <<"7"/utf8>>, <<"-"/utf8>> | Input@7] -> + parse_date_day(Input@7, Year, 7); + + [<<"0"/utf8>>, <<"8"/utf8>>, <<"-"/utf8>> | Input@8] -> + parse_date_day(Input@8, Year, 8); + + [<<"0"/utf8>>, <<"9"/utf8>>, <<"-"/utf8>> | Input@9] -> + parse_date_day(Input@9, Year, 9); + + [<<"1"/utf8>>, <<"0"/utf8>>, <<"-"/utf8>> | Input@10] -> + parse_date_day(Input@10, Year, 10); + + [<<"1"/utf8>>, <<"1"/utf8>>, <<"-"/utf8>> | Input@11] -> + parse_date_day(Input@11, Year, 11); + + [<<"1"/utf8>>, <<"2"/utf8>>, <<"-"/utf8>> | Input@12] -> + parse_date_day(Input@12, Year, 12); + + [G | _] -> + {error, {unexpected, G, <<"date month"/utf8>>}}; + + [] -> + {error, {unexpected, <<"EOF"/utf8>>, <<"date month"/utf8>>}} + end. + +-spec parse_number(list(binary()), integer(), sign()) -> {ok, + {toml(), list(binary())}} | + {error, parse_error()}. +parse_number(Input, Number, Sign) -> + case Input of + [<<"_"/utf8>> | Input@1] -> + parse_number(Input@1, Number, Sign); + + [<<"0"/utf8>> | Input@2] -> + parse_number(Input@2, (Number * 10) + 0, Sign); + + [<<"1"/utf8>> | Input@3] -> + parse_number(Input@3, (Number * 10) + 1, Sign); + + [<<"2"/utf8>> | Input@4] -> + parse_number(Input@4, (Number * 10) + 2, Sign); + + [<<"3"/utf8>> | Input@5] -> + parse_number(Input@5, (Number * 10) + 3, Sign); + + [<<"4"/utf8>> | Input@6] -> + parse_number(Input@6, (Number * 10) + 4, Sign); + + [<<"5"/utf8>> | Input@7] -> + parse_number(Input@7, (Number * 10) + 5, Sign); + + [<<"6"/utf8>> | Input@8] -> + parse_number(Input@8, (Number * 10) + 6, Sign); + + [<<"7"/utf8>> | Input@9] -> + parse_number(Input@9, (Number * 10) + 7, Sign); + + [<<"8"/utf8>> | Input@10] -> + parse_number(Input@10, (Number * 10) + 8, Sign); + + [<<"9"/utf8>> | Input@11] -> + parse_number(Input@11, (Number * 10) + 9, Sign); + + [<<"-"/utf8>> | Input@12] -> + parse_date(Input@12, Number); + + [<<":"/utf8>> | Input@13] when Number < 24 -> + parse_time_minute(Input@13, Number); + + [<<"."/utf8>> | Input@14] -> + parse_float(Input@14, gleam@int:to_float(Number), Sign, 0.1); + + [<<"e"/utf8>>, <<"+"/utf8>> | Input@15] -> + parse_exponent( + Input@15, + gleam@int:to_float(Number), + Sign, + 0, + positive + ); + + [<<"e"/utf8>>, <<"-"/utf8>> | Input@16] -> + parse_exponent( + Input@16, + gleam@int:to_float(Number), + Sign, + 0, + negative + ); + + [<<"e"/utf8>> | Input@17] -> + parse_exponent( + Input@17, + gleam@int:to_float(Number), + Sign, + 0, + positive + ); + + [<<"E"/utf8>>, <<"+"/utf8>> | Input@18] -> + parse_exponent( + Input@18, + gleam@int:to_float(Number), + Sign, + 0, + positive + ); + + [<<"E"/utf8>>, <<"-"/utf8>> | Input@19] -> + parse_exponent( + Input@19, + gleam@int:to_float(Number), + Sign, + 0, + negative + ); + + [<<"E"/utf8>> | Input@20] -> + parse_exponent( + Input@20, + gleam@int:to_float(Number), + Sign, + 0, + positive + ); + + Input@21 -> + Number@1 = case Sign of + positive -> + Number; + + negative -> + - Number + end, + {ok, {{int, Number@1}, Input@21}} + end. + +-spec reverse_arrays_of_tables(toml()) -> toml(). +reverse_arrays_of_tables(Toml) -> + case Toml of + {array_of_tables, Tables} -> + {array_of_tables, reverse_arrays_of_tables_array(Tables, [])}; + + {table, Table} -> + {table, reverse_arrays_of_tables_table(Table)}; + + _ -> + Toml + end. + +-spec reverse_arrays_of_tables_table(gleam@map:map_(binary(), toml())) -> gleam@map:map_(binary(), toml()). +reverse_arrays_of_tables_table(Table) -> + gleam@map:map_values(Table, fun(_, V) -> reverse_arrays_of_tables(V) end). + +-spec reverse_arrays_of_tables_array( + list(gleam@map:map_(binary(), toml())), + list(gleam@map:map_(binary(), toml())) +) -> list(gleam@map:map_(binary(), toml())). +reverse_arrays_of_tables_array(Array, Acc) -> + case Array of + [] -> + Acc; + + [First | Rest] -> + First@1 = reverse_arrays_of_tables_table(First), + reverse_arrays_of_tables_array(Rest, [First@1 | Acc]) + end. + +-spec parse_inline_table_property( + list(binary()), + gleam@map:map_(binary(), toml()) +) -> {ok, {gleam@map:map_(binary(), toml()), list(binary())}} | + {error, parse_error()}. +parse_inline_table_property(Input, Properties) -> + Input@1 = skip_whitespace(Input), + do( + parse_key(Input@1, []), + fun(Key, Input@2) -> + Input@3 = skip_line_whitespace(Input@2), + expect( + Input@3, + <<"="/utf8>>, + fun(Input@4) -> + Input@5 = skip_line_whitespace(Input@4), + do( + parse_value(Input@5), + fun(Value, Input@6) -> + case insert(Properties, Key, Value) of + {ok, Properties@1} -> + {ok, {Properties@1, Input@6}}; + + {error, E} -> + {error, E} + end + end + ) + end + ) + end + ). + +-spec parse_value(list(binary())) -> {ok, {toml(), list(binary())}} | + {error, parse_error()}. +parse_value(Input) -> + case Input of + [<<"t"/utf8>>, <<"r"/utf8>>, <<"u"/utf8>>, <<"e"/utf8>> | Input@1] -> + {ok, {{bool, true}, Input@1}}; + + [<<"f"/utf8>>, + <<"a"/utf8>>, + <<"l"/utf8>>, + <<"s"/utf8>>, + <<"e"/utf8>> | + Input@2] -> + {ok, {{bool, false}, Input@2}}; + + [<<"n"/utf8>>, <<"a"/utf8>>, <<"n"/utf8>> | Input@3] -> + {ok, {{nan, positive}, Input@3}}; + + [<<"+"/utf8>>, <<"n"/utf8>>, <<"a"/utf8>>, <<"n"/utf8>> | Input@4] -> + {ok, {{nan, positive}, Input@4}}; + + [<<"-"/utf8>>, <<"n"/utf8>>, <<"a"/utf8>>, <<"n"/utf8>> | Input@5] -> + {ok, {{nan, negative}, Input@5}}; + + [<<"i"/utf8>>, <<"n"/utf8>>, <<"f"/utf8>> | Input@6] -> + {ok, {{infinity, positive}, Input@6}}; + + [<<"+"/utf8>>, <<"i"/utf8>>, <<"n"/utf8>>, <<"f"/utf8>> | Input@7] -> + {ok, {{infinity, positive}, Input@7}}; + + [<<"-"/utf8>>, <<"i"/utf8>>, <<"n"/utf8>>, <<"f"/utf8>> | Input@8] -> + {ok, {{infinity, negative}, Input@8}}; + + [<<"["/utf8>> | Input@9] -> + parse_array(Input@9, []); + + [<<"{"/utf8>> | Input@10] -> + parse_inline_table(Input@10, gleam@map:new()); + + [<<"0"/utf8>>, <<"x"/utf8>> | Input@11] -> + parse_hex(Input@11, 0, positive); + + [<<"+"/utf8>>, <<"0"/utf8>>, <<"x"/utf8>> | Input@12] -> + parse_hex(Input@12, 0, positive); + + [<<"-"/utf8>>, <<"0"/utf8>>, <<"x"/utf8>> | Input@13] -> + parse_hex(Input@13, 0, negative); + + [<<"0"/utf8>>, <<"o"/utf8>> | Input@14] -> + parse_octal(Input@14, 0, positive); + + [<<"+"/utf8>>, <<"0"/utf8>>, <<"o"/utf8>> | Input@15] -> + parse_octal(Input@15, 0, positive); + + [<<"-"/utf8>>, <<"0"/utf8>>, <<"o"/utf8>> | Input@16] -> + parse_octal(Input@16, 0, negative); + + [<<"0"/utf8>>, <<"b"/utf8>> | Input@17] -> + parse_binary(Input@17, 0, positive); + + [<<"+"/utf8>>, <<"0"/utf8>>, <<"b"/utf8>> | Input@18] -> + parse_binary(Input@18, 0, positive); + + [<<"-"/utf8>>, <<"0"/utf8>>, <<"b"/utf8>> | Input@19] -> + parse_binary(Input@19, 0, negative); + + [<<"+"/utf8>> | Input@20] -> + parse_number(Input@20, 0, positive); + + [<<"-"/utf8>> | Input@21] -> + parse_number(Input@21, 0, negative); + + [<<"0"/utf8>> | _] -> + parse_number(Input, 0, positive); + + [<<"1"/utf8>> | _] -> + parse_number(Input, 0, positive); + + [<<"2"/utf8>> | _] -> + parse_number(Input, 0, positive); + + [<<"3"/utf8>> | _] -> + parse_number(Input, 0, positive); + + [<<"4"/utf8>> | _] -> + parse_number(Input, 0, positive); + + [<<"5"/utf8>> | _] -> + parse_number(Input, 0, positive); + + [<<"6"/utf8>> | _] -> + parse_number(Input, 0, positive); + + [<<"7"/utf8>> | _] -> + parse_number(Input, 0, positive); + + [<<"8"/utf8>> | _] -> + parse_number(Input, 0, positive); + + [<<"9"/utf8>> | _] -> + parse_number(Input, 0, positive); + + [<<"\""/utf8>>, <<"\""/utf8>>, <<"\""/utf8>> | Input@22] -> + parse_multi_line_string(Input@22, <<""/utf8>>); + + [<<"\""/utf8>> | Input@23] -> + parse_string(Input@23, <<""/utf8>>); + + [<<"'"/utf8>>, <<"'"/utf8>>, <<"'"/utf8>> | Input@24] -> + parse_multi_line_literal_string(Input@24, <<""/utf8>>); + + [<<"'"/utf8>> | Input@25] -> + parse_literal_string(Input@25, <<""/utf8>>); + + [G | _] -> + {error, {unexpected, G, <<"value"/utf8>>}}; + + [] -> + {error, {unexpected, <<"EOF"/utf8>>, <<"value"/utf8>>}} + end. + +-spec parse_inline_table(list(binary()), gleam@map:map_(binary(), toml())) -> {ok, + {toml(), list(binary())}} | + {error, parse_error()}. +parse_inline_table(Input, Properties) -> + Input@1 = skip_whitespace(Input), + case Input@1 of + [<<"}"/utf8>> | Input@2] -> + {ok, {{inline_table, Properties}, Input@2}}; + + _ -> + case parse_inline_table_property(Input@1, Properties) of + {ok, {Properties@1, Input@3}} -> + Input@4 = skip_whitespace(Input@3), + case Input@4 of + [<<"}"/utf8>> | Input@5] -> + {ok, {{inline_table, Properties@1}, Input@5}}; + + [<<","/utf8>> | Input@6] -> + Input@7 = skip_whitespace(Input@6), + parse_inline_table(Input@7, Properties@1); + + [G | _] -> + {error, {unexpected, G, <<"}"/utf8>>}}; + + [] -> + {error, {unexpected, <<"EOF"/utf8>>, <<"}"/utf8>>}} + end; + + {error, E} -> + {error, E} + end + end. + +-spec parse_key_value(list(binary()), gleam@map:map_(binary(), toml())) -> {ok, + {gleam@map:map_(binary(), toml()), list(binary())}} | + {error, parse_error()}. +parse_key_value(Input, Toml) -> + do( + parse_key(Input, []), + fun(Key, Input@1) -> + Input@2 = skip_line_whitespace(Input@1), + expect( + Input@2, + <<"="/utf8>>, + fun(Input@3) -> + Input@4 = skip_line_whitespace(Input@3), + do( + parse_value(Input@4), + fun(Value, Input@5) -> case insert(Toml, Key, Value) of + {ok, Toml@1} -> + {ok, {Toml@1, Input@5}}; + + {error, E} -> + {error, E} + end end + ) + end + ) + end + ). + +-spec parse_table(list(binary()), gleam@map:map_(binary(), toml())) -> {ok, + {gleam@map:map_(binary(), toml()), list(binary())}} | + {error, parse_error()}. +parse_table(Input, Toml) -> + Input@1 = skip_whitespace(Input), + case Input@1 of + [<<"["/utf8>> | _] -> + {ok, {Toml, Input@1}}; + + [] -> + {ok, {Toml, Input@1}}; + + _ -> + case parse_key_value(Input@1, Toml) of + {ok, {Toml@1, Input@2}} -> + case skip_line_whitespace(Input@2) of + [] -> + {ok, {Toml@1, []}}; + + [<<"\n"/utf8>> | In] -> + parse_table(In, Toml@1); + + [<<"\r\n"/utf8>> | In] -> + parse_table(In, Toml@1); + + [G | _] -> + {error, {unexpected, G, <<"\n"/utf8>>}} + end; + + E -> + E + end + end. + +-spec parse_array_of_tables(list(binary())) -> {ok, + {{list(binary()), gleam@map:map_(binary(), toml())}, list(binary())}} | + {error, parse_error()}. +parse_array_of_tables(Input) -> + Input@1 = skip_line_whitespace(Input), + do( + parse_key(Input@1, []), + fun(Key, Input@2) -> + expect( + Input@2, + <<"]"/utf8>>, + fun(Input@3) -> + expect( + Input@3, + <<"]"/utf8>>, + fun(Input@4) -> + do( + parse_table(Input@4, gleam@map:new()), + fun(Table, Input@5) -> + {ok, {{Key, Table}, Input@5}} + end + ) + end + ) + end + ) + end + ). + +-spec parse_table_and_header(list(binary())) -> {ok, + {{list(binary()), gleam@map:map_(binary(), toml())}, list(binary())}} | + {error, parse_error()}. +parse_table_and_header(Input) -> + do( + parse_table_header(Input), + fun(Key, Input@1) -> + do( + parse_table(Input@1, gleam@map:new()), + fun(Table, Input@2) -> {ok, {{Key, Table}, Input@2}} end + ) + end + ). + +-spec parse_tables(list(binary()), gleam@map:map_(binary(), toml())) -> {ok, + gleam@map:map_(binary(), toml())} | + {error, parse_error()}. +parse_tables(Input, Toml) -> + case Input of + [<<"["/utf8>>, <<"["/utf8>> | Input@1] -> + case parse_array_of_tables(Input@1) of + {error, E} -> + {error, E}; + + {ok, {{Key, Table}, Input@2}} -> + case insert(Toml, Key, {array_of_tables, [Table]}) of + {ok, Toml@1} -> + parse_tables(Input@2, Toml@1); + + {error, E@1} -> + {error, E@1} + end + end; + + [<<"["/utf8>> | Input@3] -> + case parse_table_and_header(Input@3) of + {error, E@2} -> + {error, E@2}; + + {ok, {{Key@1, Table@1}, Input@4}} -> + case insert(Toml, Key@1, {table, Table@1}) of + {ok, Toml@2} -> + parse_tables(Input@4, Toml@2); + + {error, E@3} -> + {error, E@3} + end + end; + + [G | _] -> + {error, {unexpected, G, <<"["/utf8>>}}; + + [] -> + {ok, Toml} + end. + +-spec parse(binary()) -> {ok, gleam@map:map_(binary(), toml())} | + {error, parse_error()}. +parse(Input) -> + Input@1 = gleam@string:to_graphemes(Input), + Input@2 = drop_comments(Input@1, []), + Input@3 = skip_whitespace(Input@2), + do( + parse_table(Input@3, gleam@map:new()), + fun(Toml, Input@4) -> case parse_tables(Input@4, Toml) of + {ok, Toml@1} -> + {ok, reverse_arrays_of_tables_table(Toml@1)}; + + {error, E} -> + {error, E} + end end + ). + +-spec parse_array(list(binary()), list(toml())) -> {ok, + {toml(), list(binary())}} | + {error, parse_error()}. +parse_array(Input, Elements) -> + Input@1 = skip_whitespace(Input), + case Input@1 of + [<<"]"/utf8>> | Input@2] -> + {ok, {{array, gleam@list:reverse(Elements)}, Input@2}}; + + _ -> + do( + parse_value(Input@1), + fun(Element, Input@3) -> + Elements@1 = [Element | Elements], + Input@4 = skip_whitespace(Input@3), + case Input@4 of + [<<"]"/utf8>> | Input@5] -> + {ok, + {{array, gleam@list:reverse(Elements@1)}, + Input@5}}; + + [<<","/utf8>> | Input@6] -> + Input@7 = skip_whitespace(Input@6), + parse_array(Input@7, Elements@1); + + [G | _] -> + {error, {unexpected, G, <<"]"/utf8>>}}; + + [] -> + {error, {unexpected, <<"EOF"/utf8>>, <<"]"/utf8>>}} + end + end + ) + end. diff --git a/aoc2023/build/packages/tom/src/tom.gleam b/aoc2023/build/packages/tom/src/tom.gleam new file mode 100644 index 0000000..e19ce3e --- /dev/null +++ b/aoc2023/build/packages/tom/src/tom.gleam @@ -0,0 +1,1317 @@ +//// A pure Gleam TOML parser! +//// +//// ```gleam +//// import tom +//// +//// const config = " +//// [person] +//// name = \"Lucy\" +//// is_cool = true +//// " +//// +//// pub fn main() { +//// // Parse a string of TOML +//// let assert Ok(parsed) = tom.parse(config) +//// +//// // Now you can work with the data directly, or you can use the `get_*` +//// // functions to retrieve values. +//// +//// tom.get_string(parsed, ["person", "name"]) +//// // -> Ok("Lucy") +//// +//// let is_cool = tom.get_bool(parsed, ["person", "is_cool"]) +//// // -> Ok(True) +//// } +//// ``` + +import gleam/int +import gleam/list +import gleam/float +import gleam/string +import gleam/result +import gleam/map.{type Map} + +/// A TOML document. +pub type Toml { + Int(Int) + Float(Float) + /// Infinity is a valid number in TOML but Gleam does not support it, so this + /// variant represents the infinity values. + Infinity(Sign) + /// NaN is a valid number in TOML but Gleam does not support it, so this + /// variant represents the NaN values. + Nan(Sign) + Bool(Bool) + String(String) + Date(Date) + Time(Time) + DateTime(DateTime) + Array(List(Toml)) + ArrayOfTables(List(Map(String, Toml))) + Table(Map(String, Toml)) + InlineTable(Map(String, Toml)) +} + +pub type DateTime { + DateTimeValue(date: Date, time: Time, offset: Offset) +} + +pub type Date { + DateValue(year: Int, month: Int, day: Int) +} + +pub type Time { + TimeValue(hour: Int, minute: Int, second: Int, millisecond: Int) +} + +pub type Offset { + Local + Offset(direction: Sign, hours: Int, minutes: Int) +} + +pub type Sign { + Positive + Negative +} + +/// An error that can occur when parsing a TOML document. +pub type ParseError { + /// An unexpected character was encountered when parsing the document. + Unexpected(got: String, expected: String) + /// More than one items have the same key in the document. + KeyAlreadyInUse(key: List(String)) +} + +type Tokens = + List(String) + +type Parsed(a) = + Result(#(a, Tokens), ParseError) + +/// A number of any kind, returned by the `get_number` function. +pub type Number { + NumberInt(Int) + NumberFloat(Float) + NumberInfinity(Sign) + NumberNan(Sign) +} + +/// An error that can occur when retrieving a value from a TOML document with +/// one of the `get_*` functions. +pub type GetError { + /// There was no value at the given key. + NotFound(key: List(String)) + /// The value at the given key was not of the expected type. + WrongType(key: List(String), expected: String, got: String) +} + +// TODO: test +/// Get a value of any type from a TOML document. +/// +/// ## Examples +/// +/// ```gleam +/// let assert Ok(parsed) = parse("a.b.c = 1") +/// get(parsed, ["a", "b", "c"]) +/// // -> Ok(Int(1)) +/// ``` +/// +pub fn get(toml: Map(String, Toml), key: List(String)) -> Result(Toml, GetError) { + case key { + [] -> Error(NotFound([])) + [k] -> result.replace_error(map.get(toml, k), NotFound([k])) + [k, ..key] -> { + case map.get(toml, k) { + Ok(Table(t)) -> push_key(get(t, key), k) + Ok(other) -> Error(WrongType([k], "Table", classify(other))) + Error(_) -> Error(NotFound([k])) + } + } + } +} + +// TODO: test +/// Get an int from a TOML document. +/// +/// ## Examples +/// +/// ```gleam +/// let assert Ok(parsed) = parse("a.b.c = 1") +/// get_int(parsed, ["a", "b", "c"]) +/// // -> Ok(1) +/// ``` +/// +pub fn get_int( + toml: Map(String, Toml), + key: List(String), +) -> Result(Int, GetError) { + case get(toml, key) { + Ok(Int(i)) -> Ok(i) + Ok(other) -> Error(WrongType(key, "Int", classify(other))) + Error(e) -> Error(e) + } +} + +// TODO: test +/// Get a float from a TOML document. +/// +/// ## Examples +/// +/// ```gleam +/// let assert Ok(parsed) = parse("a.b.c = 1.1") +/// get_float(parsed, ["a", "b", "c"]) +/// // -> Ok(1.1) +/// ``` +/// +pub fn get_float( + toml: Map(String, Toml), + key: List(String), +) -> Result(Float, GetError) { + case get(toml, key) { + Ok(Float(i)) -> Ok(i) + Ok(other) -> Error(WrongType(key, "Float", classify(other))) + Error(e) -> Error(e) + } +} + +// TODO: test +/// Get a bool from a TOML document. +/// +/// ## Examples +/// +/// ```gleam +/// let assert Ok(parsed) = parse("a.b.c = true") +/// get_bool(parsed, ["a", "b", "c"]) +/// // -> Ok(True) +/// ``` +/// +pub fn get_bool( + toml: Map(String, Toml), + key: List(String), +) -> Result(Bool, GetError) { + case get(toml, key) { + Ok(Bool(i)) -> Ok(i) + Ok(other) -> Error(WrongType(key, "Bool", classify(other))) + Error(e) -> Error(e) + } +} + +// TODO: test +/// Get a string from a TOML document. +/// +/// ## Examples +/// +/// ```gleam +/// let assert Ok(parsed) = parse("a.b.c = \"ok\"") +/// get_string(parsed, ["a", "b", "c"]) +/// // -> Ok("ok") +/// ``` +/// +pub fn get_string( + toml: Map(String, Toml), + key: List(String), +) -> Result(String, GetError) { + case get(toml, key) { + Ok(String(i)) -> Ok(i) + Ok(other) -> Error(WrongType(key, "String", classify(other))) + Error(e) -> Error(e) + } +} + +// TODO: test +/// Get a date from a TOML document. +/// +/// ## Examples +/// +/// ```gleam +/// let assert Ok(parsed) = parse("a.b.c = 1979-05-27") +/// get_date(parsed, ["a", "b", "c"]) +/// // -> Ok("1979-05-27") +/// ``` +/// +pub fn get_date( + toml: Map(String, Toml), + key: List(String), +) -> Result(Date, GetError) { + case get(toml, key) { + Ok(Date(i)) -> Ok(i) + Ok(other) -> Error(WrongType(key, "Date", classify(other))) + Error(e) -> Error(e) + } +} + +// TODO: test +/// Get a time from a TOML document. +/// +/// ## Examples +/// +/// ```gleam +/// let assert Ok(parsed) = parse("a.b.c = 07:32:00") +/// get_time(parsed, ["a", "b", "c"]) +/// // -> Ok("07:32:00") +/// ``` +/// +pub fn get_time( + toml: Map(String, Toml), + key: List(String), +) -> Result(Time, GetError) { + case get(toml, key) { + Ok(Time(i)) -> Ok(i) + Ok(other) -> Error(WrongType(key, "Time", classify(other))) + Error(e) -> Error(e) + } +} + +// TODO: test +/// Get a date-time from a TOML document. +/// +/// ## Examples +/// +/// ```gleam +/// let assert Ok(parsed) = parse("a.b.c = 1979-05-27T07:32:00") +/// get_date_time(parsed, ["a", "b", "c"]) +/// // -> Ok("1979-05-27T07:32:00") +/// ``` +/// +pub fn get_date_time( + toml: Map(String, Toml), + key: List(String), +) -> Result(DateTime, GetError) { + case get(toml, key) { + Ok(DateTime(i)) -> Ok(i) + Ok(other) -> Error(WrongType(key, "DateTime", classify(other))) + Error(e) -> Error(e) + } +} + +// TODO: test +/// Get an array from a TOML document. +/// +/// ## Examples +/// +/// ```gleam +/// let assert Ok(parsed) = parse("a.b.c = [1, 2]") +/// get_array(parsed, ["a", "b", "c"]) +/// // -> Ok([Int(1), Int(2)]) +/// ``` +/// +pub fn get_array( + toml: Map(String, Toml), + key: List(String), +) -> Result(List(Toml), GetError) { + case get(toml, key) { + Ok(Array(i)) -> Ok(i) + Ok(ArrayOfTables(i)) -> Ok(list.map(i, Table)) + Ok(other) -> Error(WrongType(key, "Array", classify(other))) + Error(e) -> Error(e) + } +} + +// TODO: test +/// Get a table from a TOML document. +/// +/// ## Examples +/// +/// ```gleam +/// let assert Ok(parsed) = parse("a.b.c = { d = 1 }") +/// get_table(parsed, ["a", "b", "c"]) +/// // -> Ok(map.from_list([#("d", Int(1))])) +/// ``` +/// +pub fn get_table( + toml: Map(String, Toml), + key: List(String), +) -> Result(Map(String, Toml), GetError) { + case get(toml, key) { + Ok(Table(i)) -> Ok(i) + Ok(InlineTable(i)) -> Ok(i) + Ok(other) -> Error(WrongType(key, "Table", classify(other))) + Error(e) -> Error(e) + } +} + +// TODO: test +/// Get a number of any kind from a TOML document. +/// This could be an int, a float, a NaN, or an infinity. +/// +/// ## Examples +/// +/// ```gleam +/// let assert Ok(parsed) = parse("a.b.c = { d = inf }") +/// get_number(parsed, ["a", "b", "c"]) +/// // -> Ok(NumberInfinity(Positive))) +/// ``` +/// +pub fn get_number( + toml: Map(String, Toml), + key: List(String), +) -> Result(Number, GetError) { + case get(toml, key) { + Ok(Int(x)) -> Ok(NumberInt(x)) + Ok(Float(x)) -> Ok(NumberFloat(x)) + Ok(Nan(x)) -> Ok(NumberNan(x)) + Ok(Infinity(x)) -> Ok(NumberInfinity(x)) + Ok(other) -> Error(WrongType(key, "Number", classify(other))) + Error(e) -> Error(e) + } +} + +fn classify(toml: Toml) -> String { + case toml { + Int(_) -> "Int" + Float(_) -> "Float" + Nan(Positive) -> "NaN" + Nan(Negative) -> "Negative NaN" + Infinity(Positive) -> "Infinity" + Infinity(Negative) -> "Negative Infinity" + Bool(_) -> "Bool" + String(_) -> "String" + Date(_) -> "Date" + Time(_) -> "Time" + DateTime(_) -> "DateTime" + Array(_) -> "Array" + ArrayOfTables(_) -> "Array" + Table(_) -> "Table" + InlineTable(_) -> "Table" + } +} + +fn push_key(result: Result(t, GetError), key: String) -> Result(t, GetError) { + case result { + Ok(t) -> Ok(t) + Error(NotFound(path)) -> Error(NotFound([key, ..path])) + Error(WrongType(path, expected, got)) -> + Error(WrongType([key, ..path], expected, got)) + } +} + +pub fn parse(input: String) -> Result(Map(String, Toml), ParseError) { + let input = string.to_graphemes(input) + let input = drop_comments(input, []) + let input = skip_whitespace(input) + use toml, input <- do(parse_table(input, map.new())) + case parse_tables(input, toml) { + Ok(toml) -> Ok(reverse_arrays_of_tables_table(toml)) + Error(e) -> Error(e) + } +} + +fn parse_tables( + input: Tokens, + toml: Map(String, Toml), +) -> Result(Map(String, Toml), ParseError) { + case input { + ["[", "[", ..input] -> { + case parse_array_of_tables(input) { + Error(e) -> Error(e) + Ok(#(#(key, table), input)) -> { + case insert(toml, key, ArrayOfTables([table])) { + Ok(toml) -> parse_tables(input, toml) + Error(e) -> Error(e) + } + } + } + } + ["[", ..input] -> { + case parse_table_and_header(input) { + Error(e) -> Error(e) + Ok(#(#(key, table), input)) -> { + case insert(toml, key, Table(table)) { + Ok(toml) -> parse_tables(input, toml) + Error(e) -> Error(e) + } + } + } + } + [g, ..] -> Error(Unexpected(g, "[")) + [] -> Ok(toml) + } +} + +fn parse_array_of_tables( + input: Tokens, +) -> Parsed(#(List(String), Map(String, Toml))) { + let input = skip_line_whitespace(input) + use key, input <- do(parse_key(input, [])) + use input <- expect(input, "]") + use input <- expect(input, "]") + use table, input <- do(parse_table(input, map.new())) + Ok(#(#(key, table), input)) +} + +fn parse_table_header(input: Tokens) -> Parsed(List(String)) { + let input = skip_line_whitespace(input) + use key, input <- do(parse_key(input, [])) + use input <- expect(input, "]") + let input = skip_line_whitespace(input) + use input <- expect_end_of_line(input) + Ok(#(key, input)) +} + +fn parse_table_and_header( + input: Tokens, +) -> Parsed(#(List(String), Map(String, Toml))) { + use key, input <- do(parse_table_header(input)) + use table, input <- do(parse_table(input, map.new())) + Ok(#(#(key, table), input)) +} + +fn parse_table( + input: Tokens, + toml: Map(String, Toml), +) -> Parsed(Map(String, Toml)) { + let input = skip_whitespace(input) + case input { + ["[", ..] | [] -> Ok(#(toml, input)) + _ -> + case parse_key_value(input, toml) { + Ok(#(toml, input)) -> + case skip_line_whitespace(input) { + [] -> Ok(#(toml, [])) + ["\n", ..in] | ["\r\n", ..in] -> parse_table(in, toml) + [g, ..] -> Error(Unexpected(g, "\n")) + } + e -> e + } + } +} + +fn parse_key_value( + input: Tokens, + toml: Map(String, Toml), +) -> Parsed(Map(String, Toml)) { + use key, input <- do(parse_key(input, [])) + let input = skip_line_whitespace(input) + use input <- expect(input, "=") + let input = skip_line_whitespace(input) + use value, input <- do(parse_value(input)) + case insert(toml, key, value) { + Ok(toml) -> Ok(#(toml, input)) + Error(e) -> Error(e) + } +} + +fn insert( + table: Map(String, Toml), + key: List(String), + value: Toml, +) -> Result(Map(String, Toml), ParseError) { + case insert_loop(table, key, value) { + Ok(table) -> Ok(table) + Error(path) -> Error(KeyAlreadyInUse(path)) + } +} + +fn insert_loop( + table: Map(String, Toml), + key: List(String), + value: Toml, +) -> Result(Map(String, Toml), List(String)) { + case key { + [] -> panic as "unreachable" + [k] -> { + case map.get(table, k) { + Error(Nil) -> Ok(map.insert(table, k, value)) + Ok(old) -> merge(table, k, old, value) + } + } + [k, ..key] -> { + case map.get(table, k) { + Error(Nil) -> { + case insert_loop(map.new(), key, value) { + Ok(inner) -> Ok(map.insert(table, k, Table(inner))) + Error(path) -> Error([k, ..path]) + } + } + Ok(ArrayOfTables([inner, ..rest])) -> { + case insert_loop(inner, key, value) { + Ok(inner) -> + Ok(map.insert(table, k, ArrayOfTables([inner, ..rest]))) + Error(path) -> Error([k, ..path]) + } + } + Ok(Table(inner)) -> { + case insert_loop(inner, key, value) { + Ok(inner) -> Ok(map.insert(table, k, Table(inner))) + Error(path) -> Error([k, ..path]) + } + } + Ok(_) -> Error([k]) + } + } + } +} + +fn merge( + table: Map(String, Toml), + key: String, + old: Toml, + new: Toml, +) -> Result(Map(String, Toml), List(String)) { + case old, new { + // When both are arrays of tables then they are merged together + ArrayOfTables(tables), ArrayOfTables(new) -> + Ok(map.insert(table, key, ArrayOfTables(list.append(new, tables)))) + + _, _ -> Error([key]) + } +} + +fn expect_end_of_line(input: Tokens, next: fn(Tokens) -> Parsed(a)) -> Parsed(a) { + case input { + ["\n", ..input] -> next(input) + ["\r\n", ..input] -> next(input) + [g, ..] -> Error(Unexpected(g, "\n")) + [] -> Error(Unexpected("EOF", "\n")) + } +} + +fn parse_value(input) -> Parsed(Toml) { + case input { + ["t", "r", "u", "e", ..input] -> Ok(#(Bool(True), input)) + ["f", "a", "l", "s", "e", ..input] -> Ok(#(Bool(False), input)) + + ["n", "a", "n", ..input] -> Ok(#(Nan(Positive), input)) + ["+", "n", "a", "n", ..input] -> Ok(#(Nan(Positive), input)) + ["-", "n", "a", "n", ..input] -> Ok(#(Nan(Negative), input)) + + ["i", "n", "f", ..input] -> Ok(#(Infinity(Positive), input)) + ["+", "i", "n", "f", ..input] -> Ok(#(Infinity(Positive), input)) + ["-", "i", "n", "f", ..input] -> Ok(#(Infinity(Negative), input)) + + ["[", ..input] -> parse_array(input, []) + ["{", ..input] -> parse_inline_table(input, map.new()) + + ["0", "x", ..input] -> parse_hex(input, 0, Positive) + ["+", "0", "x", ..input] -> parse_hex(input, 0, Positive) + ["-", "0", "x", ..input] -> parse_hex(input, 0, Negative) + + ["0", "o", ..input] -> parse_octal(input, 0, Positive) + ["+", "0", "o", ..input] -> parse_octal(input, 0, Positive) + ["-", "0", "o", ..input] -> parse_octal(input, 0, Negative) + + ["0", "b", ..input] -> parse_binary(input, 0, Positive) + ["+", "0", "b", ..input] -> parse_binary(input, 0, Positive) + ["-", "0", "b", ..input] -> parse_binary(input, 0, Negative) + + ["+", ..input] -> parse_number(input, 0, Positive) + ["-", ..input] -> parse_number(input, 0, Negative) + ["0", ..] + | ["1", ..] + | ["2", ..] + | ["3", ..] + | ["4", ..] + | ["5", ..] + | ["6", ..] + | ["7", ..] + | ["8", ..] + | ["9", ..] -> parse_number(input, 0, Positive) + + ["\"", "\"", "\"", ..input] -> parse_multi_line_string(input, "") + ["\"", ..input] -> parse_string(input, "") + + ["'", "'", "'", ..input] -> parse_multi_line_literal_string(input, "") + ["'", ..input] -> parse_literal_string(input, "") + + [g, ..] -> Error(Unexpected(g, "value")) + [] -> Error(Unexpected("EOF", "value")) + } +} + +fn parse_key(input: Tokens, segments: List(String)) -> Parsed(List(String)) { + use segment, input <- do(parse_key_segment(input)) + let segments = [segment, ..segments] + let input = skip_line_whitespace(input) + + case input { + [".", ..input] -> parse_key(input, segments) + _ -> Ok(#(list.reverse(segments), input)) + } +} + +fn parse_key_segment(input: Tokens) -> Parsed(String) { + let input = skip_line_whitespace(input) + case input { + ["=", ..] -> Error(Unexpected("=", "Key")) + ["\n", ..] -> Error(Unexpected("\n", "Key")) + ["\r\n", ..] -> Error(Unexpected("\r\n", "Key")) + ["[", ..] -> Error(Unexpected("[", "Key")) + ["\"", ..input] -> parse_key_quoted(input, "\"", "") + ["'", ..input] -> parse_key_quoted(input, "'", "") + _ -> parse_key_bare(input, "") + } +} + +fn parse_key_quoted( + input: Tokens, + close: String, + name: String, +) -> Parsed(String) { + case input { + [g, ..input] if g == close -> Ok(#(name, input)) + [g, ..input] -> parse_key_quoted(input, close, name <> g) + [] -> Error(Unexpected("EOF", close)) + } +} + +fn parse_key_bare(input: Tokens, name: String) -> Parsed(String) { + case input { + [" ", ..input] if name != "" -> Ok(#(name, input)) + ["=", ..] if name != "" -> Ok(#(name, input)) + [".", ..] if name != "" -> Ok(#(name, input)) + ["]", ..] if name != "" -> Ok(#(name, input)) + [",", ..] if name != "" -> Error(Unexpected(",", "=")) + ["\n", ..] if name != "" -> Error(Unexpected("\n", "=")) + ["\r\n", ..] if name != "" -> Error(Unexpected("\r\n", "=")) + ["\n", ..] -> Error(Unexpected("\n", "key")) + ["\r\n", ..] -> Error(Unexpected("\r\n", "key")) + ["]", ..] -> Error(Unexpected("]", "key")) + [",", ..] -> Error(Unexpected(",", "key")) + [g, ..input] -> parse_key_bare(input, name <> g) + [] -> Error(Unexpected("EOF", "key")) + } +} + +fn skip_line_whitespace(input: Tokens) -> Tokens { + list.drop_while(input, fn(g) { g == " " || g == "\t" }) +} + +fn skip_whitespace(input: Tokens) -> Tokens { + case input { + [" ", ..input] -> skip_whitespace(input) + ["\t", ..input] -> skip_whitespace(input) + ["\n", ..input] -> skip_whitespace(input) + ["\r\n", ..input] -> skip_whitespace(input) + input -> input + } +} + +fn drop_comments(input: Tokens, acc: Tokens) -> Tokens { + case input { + ["#", ..input] -> + input + |> list.drop_while(fn(g) { g != "\n" }) + |> drop_comments(acc) + [g, ..input] -> drop_comments(input, [g, ..acc]) + [] -> list.reverse(acc) + } +} + +fn do( + result: Result(#(a, Tokens), ParseError), + next: fn(a, Tokens) -> Result(b, ParseError), +) -> Result(b, ParseError) { + case result { + Ok(#(a, input)) -> next(a, input) + Error(e) -> Error(e) + } +} + +fn expect( + input: Tokens, + expected: String, + next: fn(Tokens) -> Parsed(a), +) -> Parsed(a) { + case input { + [g, ..input] if g == expected -> next(input) + [g, ..] -> Error(Unexpected(g, expected)) + [] -> Error(Unexpected("EOF", expected)) + } +} + +fn parse_inline_table( + input: Tokens, + properties: Map(String, Toml), +) -> Parsed(Toml) { + let input = skip_whitespace(input) + case input { + ["}", ..input] -> Ok(#(InlineTable(properties), input)) + _ -> + case parse_inline_table_property(input, properties) { + Ok(#(properties, input)) -> { + let input = skip_whitespace(input) + case input { + ["}", ..input] -> Ok(#(InlineTable(properties), input)) + [",", ..input] -> { + let input = skip_whitespace(input) + parse_inline_table(input, properties) + } + [g, ..] -> Error(Unexpected(g, "}")) + [] -> Error(Unexpected("EOF", "}")) + } + } + Error(e) -> Error(e) + } + } +} + +fn parse_inline_table_property( + input: Tokens, + properties: Map(String, Toml), +) -> Parsed(Map(String, Toml)) { + let input = skip_whitespace(input) + use key, input <- do(parse_key(input, [])) + let input = skip_line_whitespace(input) + use input <- expect(input, "=") + let input = skip_line_whitespace(input) + use value, input <- do(parse_value(input)) + case insert(properties, key, value) { + Ok(properties) -> Ok(#(properties, input)) + Error(e) -> Error(e) + } +} + +fn parse_array(input: Tokens, elements: List(Toml)) -> Parsed(Toml) { + let input = skip_whitespace(input) + case input { + ["]", ..input] -> Ok(#(Array(list.reverse(elements)), input)) + _ -> { + use element, input <- do(parse_value(input)) + let elements = [element, ..elements] + let input = skip_whitespace(input) + case input { + ["]", ..input] -> Ok(#(Array(list.reverse(elements)), input)) + [",", ..input] -> { + let input = skip_whitespace(input) + parse_array(input, elements) + } + [g, ..] -> Error(Unexpected(g, "]")) + [] -> Error(Unexpected("EOF", "]")) + } + } + } +} + +fn parse_hex(input: Tokens, number: Int, sign: Sign) -> Parsed(Toml) { + case input { + ["_", ..input] -> parse_hex(input, number, sign) + ["0", ..input] -> parse_hex(input, number * 16 + 0, sign) + ["1", ..input] -> parse_hex(input, number * 16 + 1, sign) + ["2", ..input] -> parse_hex(input, number * 16 + 2, sign) + ["3", ..input] -> parse_hex(input, number * 16 + 3, sign) + ["4", ..input] -> parse_hex(input, number * 16 + 4, sign) + ["5", ..input] -> parse_hex(input, number * 16 + 5, sign) + ["6", ..input] -> parse_hex(input, number * 16 + 6, sign) + ["7", ..input] -> parse_hex(input, number * 16 + 7, sign) + ["8", ..input] -> parse_hex(input, number * 16 + 8, sign) + ["9", ..input] -> parse_hex(input, number * 16 + 9, sign) + ["a", ..input] -> parse_hex(input, number * 16 + 10, sign) + ["b", ..input] -> parse_hex(input, number * 16 + 11, sign) + ["c", ..input] -> parse_hex(input, number * 16 + 12, sign) + ["d", ..input] -> parse_hex(input, number * 16 + 13, sign) + ["e", ..input] -> parse_hex(input, number * 16 + 14, sign) + ["f", ..input] -> parse_hex(input, number * 16 + 15, sign) + ["A", ..input] -> parse_hex(input, number * 16 + 10, sign) + ["B", ..input] -> parse_hex(input, number * 16 + 11, sign) + ["C", ..input] -> parse_hex(input, number * 16 + 12, sign) + ["D", ..input] -> parse_hex(input, number * 16 + 13, sign) + ["E", ..input] -> parse_hex(input, number * 16 + 14, sign) + ["F", ..input] -> parse_hex(input, number * 16 + 15, sign) + + // Anything else and the number is terminated + input -> { + let number = case sign { + Positive -> number + Negative -> -number + } + Ok(#(Int(number), input)) + } + } +} + +fn parse_octal(input: Tokens, number: Int, sign: Sign) -> Parsed(Toml) { + case input { + ["_", ..input] -> parse_octal(input, number, sign) + ["0", ..input] -> parse_octal(input, number * 8 + 0, sign) + ["1", ..input] -> parse_octal(input, number * 8 + 1, sign) + ["2", ..input] -> parse_octal(input, number * 8 + 2, sign) + ["3", ..input] -> parse_octal(input, number * 8 + 3, sign) + ["4", ..input] -> parse_octal(input, number * 8 + 4, sign) + ["5", ..input] -> parse_octal(input, number * 8 + 5, sign) + ["6", ..input] -> parse_octal(input, number * 8 + 6, sign) + ["7", ..input] -> parse_octal(input, number * 8 + 7, sign) + + // Anything else and the number is terminated + input -> { + let number = case sign { + Positive -> number + Negative -> -number + } + Ok(#(Int(number), input)) + } + } +} + +fn parse_binary(input: Tokens, number: Int, sign: Sign) -> Parsed(Toml) { + case input { + ["_", ..input] -> parse_binary(input, number, sign) + ["0", ..input] -> parse_binary(input, number * 2 + 0, sign) + ["1", ..input] -> parse_binary(input, number * 2 + 1, sign) + + // Anything else and the number is terminated + input -> { + let number = case sign { + Positive -> number + Negative -> -number + } + Ok(#(Int(number), input)) + } + } +} + +fn parse_number(input: Tokens, number: Int, sign: Sign) -> Parsed(Toml) { + case input { + ["_", ..input] -> parse_number(input, number, sign) + ["0", ..input] -> parse_number(input, number * 10 + 0, sign) + ["1", ..input] -> parse_number(input, number * 10 + 1, sign) + ["2", ..input] -> parse_number(input, number * 10 + 2, sign) + ["3", ..input] -> parse_number(input, number * 10 + 3, sign) + ["4", ..input] -> parse_number(input, number * 10 + 4, sign) + ["5", ..input] -> parse_number(input, number * 10 + 5, sign) + ["6", ..input] -> parse_number(input, number * 10 + 6, sign) + ["7", ..input] -> parse_number(input, number * 10 + 7, sign) + ["8", ..input] -> parse_number(input, number * 10 + 8, sign) + ["9", ..input] -> parse_number(input, number * 10 + 9, sign) + + ["-", ..input] -> parse_date(input, number) + [":", ..input] if number < 24 -> parse_time_minute(input, number) + + [".", ..input] -> parse_float(input, int.to_float(number), sign, 0.1) + + ["e", "+", ..input] -> + parse_exponent(input, int.to_float(number), sign, 0, Positive) + ["e", "-", ..input] -> + parse_exponent(input, int.to_float(number), sign, 0, Negative) + ["e", ..input] -> + parse_exponent(input, int.to_float(number), sign, 0, Positive) + ["E", "+", ..input] -> + parse_exponent(input, int.to_float(number), sign, 0, Positive) + ["E", "-", ..input] -> + parse_exponent(input, int.to_float(number), sign, 0, Negative) + ["E", ..input] -> + parse_exponent(input, int.to_float(number), sign, 0, Positive) + + // Anything else and the number is terminated + input -> { + let number = case sign { + Positive -> number + Negative -> -number + } + Ok(#(Int(number), input)) + } + } +} + +fn parse_exponent( + input: Tokens, + n: Float, + n_sign: Sign, + ex: Int, + ex_sign: Sign, +) -> Parsed(Toml) { + case input { + ["_", ..input] -> parse_exponent(input, n, n_sign, ex, ex_sign) + ["0", ..input] -> parse_exponent(input, n, n_sign, ex * 10, ex_sign) + ["1", ..input] -> parse_exponent(input, n, n_sign, ex * 10 + 1, ex_sign) + ["2", ..input] -> parse_exponent(input, n, n_sign, ex * 10 + 2, ex_sign) + ["3", ..input] -> parse_exponent(input, n, n_sign, ex * 10 + 3, ex_sign) + ["4", ..input] -> parse_exponent(input, n, n_sign, ex * 10 + 4, ex_sign) + ["5", ..input] -> parse_exponent(input, n, n_sign, ex * 10 + 5, ex_sign) + ["6", ..input] -> parse_exponent(input, n, n_sign, ex * 10 + 6, ex_sign) + ["7", ..input] -> parse_exponent(input, n, n_sign, ex * 10 + 7, ex_sign) + ["8", ..input] -> parse_exponent(input, n, n_sign, ex * 10 + 8, ex_sign) + ["9", ..input] -> parse_exponent(input, n, n_sign, ex * 10 + 9, ex_sign) + + // Anything else and the number is terminated + input -> { + let number = case n_sign { + Positive -> n + Negative -> n *. -1.0 + } + let exponent = + int.to_float(case ex_sign { + Positive -> ex + Negative -> -ex + }) + let multiplier = case float.power(10.0, exponent) { + Ok(multiplier) -> multiplier + Error(_) -> 1.0 + } + Ok(#(Float(number *. multiplier), input)) + } + } +} + +fn parse_float( + input: Tokens, + number: Float, + sign: Sign, + unit: Float, +) -> Parsed(Toml) { + case input { + ["_", ..input] -> parse_float(input, number, sign, unit) + ["0", ..input] -> parse_float(input, number, sign, unit *. 0.1) + ["1", ..input] -> + parse_float(input, number +. 1.0 *. unit, sign, unit *. 0.1) + ["2", ..input] -> + parse_float(input, number +. 2.0 *. unit, sign, unit *. 0.1) + ["3", ..input] -> + parse_float(input, number +. 3.0 *. unit, sign, unit *. 0.1) + ["4", ..input] -> + parse_float(input, number +. 4.0 *. unit, sign, unit *. 0.1) + ["5", ..input] -> + parse_float(input, number +. 5.0 *. unit, sign, unit *. 0.1) + ["6", ..input] -> + parse_float(input, number +. 6.0 *. unit, sign, unit *. 0.1) + ["7", ..input] -> + parse_float(input, number +. 7.0 *. unit, sign, unit *. 0.1) + ["8", ..input] -> + parse_float(input, number +. 8.0 *. unit, sign, unit *. 0.1) + ["9", ..input] -> + parse_float(input, number +. 9.0 *. unit, sign, unit *. 0.1) + + ["e", "+", ..input] -> parse_exponent(input, number, sign, 0, Positive) + ["e", "-", ..input] -> parse_exponent(input, number, sign, 0, Negative) + ["e", ..input] -> parse_exponent(input, number, sign, 0, Positive) + ["E", "+", ..input] -> parse_exponent(input, number, sign, 0, Positive) + ["E", "-", ..input] -> parse_exponent(input, number, sign, 0, Negative) + ["E", ..input] -> parse_exponent(input, number, sign, 0, Positive) + + // Anything else and the number is terminated + input -> { + let number = case sign { + Positive -> number + Negative -> number *. -1.0 + } + Ok(#(Float(number), input)) + } + } +} + +fn parse_string(input: Tokens, string: String) -> Parsed(Toml) { + case input { + ["\"", ..input] -> Ok(#(String(string), input)) + ["\\", "t", ..input] -> parse_string(input, string <> "\t") + ["\\", "n", ..input] -> parse_string(input, string <> "\n") + ["\\", "r", ..input] -> parse_string(input, string <> "\r") + ["\\", "\"", ..input] -> parse_string(input, string <> "\"") + ["\\", "\\", ..input] -> parse_string(input, string <> "\\") + [] -> Error(Unexpected("EOF", "\"")) + ["\n", ..] -> Error(Unexpected("\n", "\"")) + ["\r\n", ..] -> Error(Unexpected("\r\n", "\"")) + [g, ..input] -> parse_string(input, string <> g) + } +} + +fn parse_multi_line_string(input: Tokens, string: String) -> Parsed(Toml) { + case input { + ["\"", "\"", "\"", ..input] -> Ok(#(String(string), input)) + ["\\", "\n", ..input] -> + parse_multi_line_string(skip_whitespace(input), string) + ["\\", "\r\n", ..input] -> + parse_multi_line_string(skip_whitespace(input), string) + ["\r\n", ..input] if string == "" -> parse_multi_line_string(input, string) + ["\n", ..input] if string == "" -> parse_multi_line_string(input, string) + ["\r\n", ..input] if string == "" -> parse_multi_line_string(input, string) + ["\\", "t", ..input] -> parse_multi_line_string(input, string <> "\t") + ["\\", "n", ..input] -> parse_multi_line_string(input, string <> "\n") + ["\\", "r", ..input] -> parse_multi_line_string(input, string <> "\r") + ["\\", "\"", ..input] -> parse_multi_line_string(input, string <> "\"") + ["\\", "\\", ..input] -> parse_multi_line_string(input, string <> "\\") + [] -> Error(Unexpected("EOF", "\"")) + [g, ..input] -> parse_multi_line_string(input, string <> g) + } +} + +fn parse_multi_line_literal_string( + input: Tokens, + string: String, +) -> Parsed(Toml) { + case input { + [] -> Error(Unexpected("EOF", "\"")) + ["'", "'", "'", "'", ..] -> Error(Unexpected("''''", "'''")) + ["'", "'", "'", ..input] -> Ok(#(String(string), input)) + ["\n", ..input] if string == "" -> + parse_multi_line_literal_string(input, string) + ["\r\n", ..input] if string == "" -> + parse_multi_line_literal_string(input, string) + [g, ..input] -> parse_multi_line_literal_string(input, string <> g) + } +} + +fn parse_literal_string(input: Tokens, string: String) -> Parsed(Toml) { + case input { + [] -> Error(Unexpected("EOF", "\"")) + ["\n", ..] -> Error(Unexpected("\n", "'")) + ["\r\n", ..] -> Error(Unexpected("\r\n", "'")) + ["'", ..input] -> Ok(#(String(string), input)) + [g, ..input] -> parse_literal_string(input, string <> g) + } +} + +fn reverse_arrays_of_tables(toml: Toml) -> Toml { + case toml { + ArrayOfTables(tables) -> + ArrayOfTables(reverse_arrays_of_tables_array(tables, [])) + + Table(table) -> Table(reverse_arrays_of_tables_table(table)) + + _ -> toml + } +} + +fn reverse_arrays_of_tables_table(table: Map(String, Toml)) -> Map(String, Toml) { + map.map_values(table, fn(_, v) { reverse_arrays_of_tables(v) }) +} + +fn reverse_arrays_of_tables_array( + array: List(Map(String, Toml)), + acc: List(Map(String, Toml)), +) -> List(Map(String, Toml)) { + case array { + [] -> acc + [first, ..rest] -> { + let first = reverse_arrays_of_tables_table(first) + reverse_arrays_of_tables_array(rest, [first, ..acc]) + } + } +} + +fn parse_time_minute(input: Tokens, hours: Int) -> Parsed(Toml) { + use minutes, input <- do(parse_number_under_60(input, "minutes")) + use #(seconds, ms), input <- do(parse_time_s_ms(input)) + let time = TimeValue(hours, minutes, seconds, ms) + Ok(#(Time(time), input)) +} + +fn parse_hour_minute(input: Tokens) -> Parsed(#(Int, Int)) { + use hours, input <- do(case input { + ["0", "0", ":", ..input] -> Ok(#(0, input)) + ["0", "1", ":", ..input] -> Ok(#(1, input)) + ["0", "2", ":", ..input] -> Ok(#(2, input)) + ["0", "3", ":", ..input] -> Ok(#(3, input)) + ["0", "4", ":", ..input] -> Ok(#(4, input)) + ["0", "5", ":", ..input] -> Ok(#(5, input)) + ["0", "6", ":", ..input] -> Ok(#(6, input)) + ["0", "7", ":", ..input] -> Ok(#(7, input)) + ["0", "8", ":", ..input] -> Ok(#(8, input)) + ["0", "9", ":", ..input] -> Ok(#(9, input)) + ["1", "0", ":", ..input] -> Ok(#(10, input)) + ["1", "1", ":", ..input] -> Ok(#(11, input)) + ["1", "2", ":", ..input] -> Ok(#(12, input)) + ["1", "3", ":", ..input] -> Ok(#(13, input)) + ["1", "4", ":", ..input] -> Ok(#(14, input)) + ["1", "5", ":", ..input] -> Ok(#(15, input)) + ["1", "6", ":", ..input] -> Ok(#(16, input)) + ["1", "7", ":", ..input] -> Ok(#(17, input)) + ["1", "8", ":", ..input] -> Ok(#(18, input)) + ["1", "9", ":", ..input] -> Ok(#(19, input)) + ["2", "0", ":", ..input] -> Ok(#(20, input)) + ["2", "1", ":", ..input] -> Ok(#(21, input)) + ["2", "2", ":", ..input] -> Ok(#(22, input)) + ["2", "3", ":", ..input] -> Ok(#(23, input)) + [g, ..] -> Error(Unexpected(g, "time")) + [] -> Error(Unexpected("EOF", "time")) + }) + + use minutes, input <- do(parse_number_under_60(input, "minutes")) + Ok(#(#(hours, minutes), input)) +} + +fn parse_time_value(input: Tokens) -> Parsed(Time) { + use #(hours, minutes), input <- do(parse_hour_minute(input)) + use #(seconds, ms), input <- do(parse_time_s_ms(input)) + let time = TimeValue(hours, minutes, seconds, ms) + Ok(#(time, input)) +} + +fn parse_time_s_ms(input: Tokens) -> Parsed(#(Int, Int)) { + case input { + [":", ..input] -> { + use seconds, input <- do(parse_number_under_60(input, "seconds")) + case input { + [".", ..input] -> parse_time_ms(input, seconds, 0) + _ -> Ok(#(#(seconds, 0), input)) + } + } + + _ -> Ok(#(#(0, 0), input)) + } +} + +fn parse_time_ms(input: Tokens, seconds: Int, ms: Int) -> Parsed(#(Int, Int)) { + case input { + ["0", ..input] if ms < 100_000 -> parse_time_ms(input, seconds, ms * 10 + 0) + ["1", ..input] if ms < 100_000 -> parse_time_ms(input, seconds, ms * 10 + 1) + ["2", ..input] if ms < 100_000 -> parse_time_ms(input, seconds, ms * 10 + 2) + ["3", ..input] if ms < 100_000 -> parse_time_ms(input, seconds, ms * 10 + 3) + ["4", ..input] if ms < 100_000 -> parse_time_ms(input, seconds, ms * 10 + 4) + ["5", ..input] if ms < 100_000 -> parse_time_ms(input, seconds, ms * 10 + 5) + ["6", ..input] if ms < 100_000 -> parse_time_ms(input, seconds, ms * 10 + 6) + ["7", ..input] if ms < 100_000 -> parse_time_ms(input, seconds, ms * 10 + 7) + ["8", ..input] if ms < 100_000 -> parse_time_ms(input, seconds, ms * 10 + 8) + ["9", ..input] if ms < 100_000 -> parse_time_ms(input, seconds, ms * 10 + 9) + + // Anything else and the number is terminated + _ -> Ok(#(#(seconds, ms), input)) + } +} + +fn parse_number_under_60(input: Tokens, expected: String) -> Parsed(Int) { + case input { + ["0", "0", ..input] -> Ok(#(0, input)) + ["0", "1", ..input] -> Ok(#(1, input)) + ["0", "2", ..input] -> Ok(#(2, input)) + ["0", "3", ..input] -> Ok(#(3, input)) + ["0", "4", ..input] -> Ok(#(4, input)) + ["0", "5", ..input] -> Ok(#(5, input)) + ["0", "6", ..input] -> Ok(#(6, input)) + ["0", "7", ..input] -> Ok(#(7, input)) + ["0", "8", ..input] -> Ok(#(8, input)) + ["0", "9", ..input] -> Ok(#(9, input)) + ["1", "0", ..input] -> Ok(#(10, input)) + ["1", "1", ..input] -> Ok(#(11, input)) + ["1", "2", ..input] -> Ok(#(12, input)) + ["1", "3", ..input] -> Ok(#(13, input)) + ["1", "4", ..input] -> Ok(#(14, input)) + ["1", "5", ..input] -> Ok(#(15, input)) + ["1", "6", ..input] -> Ok(#(16, input)) + ["1", "7", ..input] -> Ok(#(17, input)) + ["1", "8", ..input] -> Ok(#(18, input)) + ["1", "9", ..input] -> Ok(#(19, input)) + ["2", "0", ..input] -> Ok(#(20, input)) + ["2", "1", ..input] -> Ok(#(21, input)) + ["2", "2", ..input] -> Ok(#(22, input)) + ["2", "3", ..input] -> Ok(#(23, input)) + ["2", "4", ..input] -> Ok(#(24, input)) + ["2", "5", ..input] -> Ok(#(25, input)) + ["2", "6", ..input] -> Ok(#(26, input)) + ["2", "7", ..input] -> Ok(#(27, input)) + ["2", "8", ..input] -> Ok(#(28, input)) + ["2", "9", ..input] -> Ok(#(29, input)) + ["3", "0", ..input] -> Ok(#(30, input)) + ["3", "1", ..input] -> Ok(#(31, input)) + ["3", "2", ..input] -> Ok(#(32, input)) + ["3", "3", ..input] -> Ok(#(33, input)) + ["3", "4", ..input] -> Ok(#(34, input)) + ["3", "5", ..input] -> Ok(#(35, input)) + ["3", "6", ..input] -> Ok(#(36, input)) + ["3", "7", ..input] -> Ok(#(37, input)) + ["3", "8", ..input] -> Ok(#(38, input)) + ["3", "9", ..input] -> Ok(#(39, input)) + ["4", "0", ..input] -> Ok(#(40, input)) + ["4", "1", ..input] -> Ok(#(41, input)) + ["4", "2", ..input] -> Ok(#(42, input)) + ["4", "3", ..input] -> Ok(#(43, input)) + ["4", "4", ..input] -> Ok(#(44, input)) + ["4", "5", ..input] -> Ok(#(45, input)) + ["4", "6", ..input] -> Ok(#(46, input)) + ["4", "7", ..input] -> Ok(#(47, input)) + ["4", "8", ..input] -> Ok(#(48, input)) + ["4", "9", ..input] -> Ok(#(49, input)) + ["5", "0", ..input] -> Ok(#(50, input)) + ["5", "1", ..input] -> Ok(#(51, input)) + ["5", "2", ..input] -> Ok(#(52, input)) + ["5", "3", ..input] -> Ok(#(53, input)) + ["5", "4", ..input] -> Ok(#(54, input)) + ["5", "5", ..input] -> Ok(#(55, input)) + ["5", "6", ..input] -> Ok(#(56, input)) + ["5", "7", ..input] -> Ok(#(57, input)) + ["5", "8", ..input] -> Ok(#(58, input)) + ["5", "9", ..input] -> Ok(#(59, input)) + + [g, ..] -> Error(Unexpected(g, expected)) + [] -> Error(Unexpected("EOF", expected)) + } +} + +fn parse_date(input: Tokens, year: Int) -> Parsed(Toml) { + case input { + ["0", "1", "-", ..input] -> parse_date_day(input, year, 1) + ["0", "2", "-", ..input] -> parse_date_day(input, year, 2) + ["0", "3", "-", ..input] -> parse_date_day(input, year, 3) + ["0", "4", "-", ..input] -> parse_date_day(input, year, 4) + ["0", "5", "-", ..input] -> parse_date_day(input, year, 5) + ["0", "6", "-", ..input] -> parse_date_day(input, year, 6) + ["0", "7", "-", ..input] -> parse_date_day(input, year, 7) + ["0", "8", "-", ..input] -> parse_date_day(input, year, 8) + ["0", "9", "-", ..input] -> parse_date_day(input, year, 9) + ["1", "0", "-", ..input] -> parse_date_day(input, year, 10) + ["1", "1", "-", ..input] -> parse_date_day(input, year, 11) + ["1", "2", "-", ..input] -> parse_date_day(input, year, 12) + + [g, ..] -> Error(Unexpected(g, "date month")) + [] -> Error(Unexpected("EOF", "date month")) + } +} + +fn parse_date_day(input: Tokens, year: Int, month: Int) -> Parsed(Toml) { + case input { + ["0", "1", ..input] -> parse_date_end(input, year, month, 1) + ["0", "2", ..input] -> parse_date_end(input, year, month, 2) + ["0", "3", ..input] -> parse_date_end(input, year, month, 3) + ["0", "4", ..input] -> parse_date_end(input, year, month, 4) + ["0", "5", ..input] -> parse_date_end(input, year, month, 5) + ["0", "6", ..input] -> parse_date_end(input, year, month, 6) + ["0", "7", ..input] -> parse_date_end(input, year, month, 7) + ["0", "8", ..input] -> parse_date_end(input, year, month, 8) + ["0", "9", ..input] -> parse_date_end(input, year, month, 9) + ["1", "0", ..input] -> parse_date_end(input, year, month, 10) + ["1", "1", ..input] -> parse_date_end(input, year, month, 11) + ["1", "2", ..input] -> parse_date_end(input, year, month, 12) + ["1", "3", ..input] -> parse_date_end(input, year, month, 13) + ["1", "4", ..input] -> parse_date_end(input, year, month, 14) + ["1", "5", ..input] -> parse_date_end(input, year, month, 15) + ["1", "6", ..input] -> parse_date_end(input, year, month, 16) + ["1", "7", ..input] -> parse_date_end(input, year, month, 17) + ["1", "8", ..input] -> parse_date_end(input, year, month, 18) + ["1", "9", ..input] -> parse_date_end(input, year, month, 19) + ["2", "0", ..input] -> parse_date_end(input, year, month, 20) + ["2", "1", ..input] -> parse_date_end(input, year, month, 21) + ["2", "2", ..input] -> parse_date_end(input, year, month, 22) + ["2", "3", ..input] -> parse_date_end(input, year, month, 23) + ["2", "4", ..input] -> parse_date_end(input, year, month, 24) + ["2", "5", ..input] -> parse_date_end(input, year, month, 25) + ["2", "6", ..input] -> parse_date_end(input, year, month, 26) + ["2", "7", ..input] -> parse_date_end(input, year, month, 27) + ["2", "8", ..input] -> parse_date_end(input, year, month, 28) + ["2", "9", ..input] -> parse_date_end(input, year, month, 29) + ["3", "0", ..input] -> parse_date_end(input, year, month, 30) + ["3", "1", ..input] -> parse_date_end(input, year, month, 31) + + [g, ..] -> Error(Unexpected(g, "date day")) + [] -> Error(Unexpected("EOF", "date day")) + } +} + +fn parse_date_end( + input: Tokens, + year: Int, + month: Int, + day: Int, +) -> Parsed(Toml) { + let date = DateValue(year, month, day) + case input { + [" ", ..input] | ["T", ..input] -> { + use time, input <- do(parse_time_value(input)) + use offset, input <- do(parse_offset(input)) + Ok(#(DateTime(DateTimeValue(date, time, offset)), input)) + } + + _ -> Ok(#(Date(date), input)) + } +} + +fn parse_offset(input: Tokens) -> Parsed(Offset) { + case input { + ["Z", ..input] -> Ok(#(Offset(Positive, 0, 0), input)) + ["+", ..input] -> parse_offset_hours(input, Positive) + ["-", ..input] -> parse_offset_hours(input, Negative) + _ -> Ok(#(Local, input)) + } +} + +fn parse_offset_hours(input: Tokens, sign: Sign) -> Parsed(Offset) { + use #(hours, minutes), input <- do(parse_hour_minute(input)) + Ok(#(Offset(sign, hours, minutes), input)) +} |