diff options
Diffstat (limited to 'aoc2023/build/packages/gleam_stdlib')
66 files changed, 19957 insertions, 0 deletions
diff --git a/aoc2023/build/packages/gleam_stdlib/LICENCE b/aoc2023/build/packages/gleam_stdlib/LICENCE new file mode 100644 index 0000000..c1dabd0 --- /dev/null +++ b/aoc2023/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/gleam_stdlib/README.md b/aoc2023/build/packages/gleam_stdlib/README.md new file mode 100644 index 0000000..05c68ca --- /dev/null +++ b/aoc2023/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/gleam_stdlib/gleam.toml b/aoc2023/build/packages/gleam_stdlib/gleam.toml new file mode 100644 index 0000000..0cb0531 --- /dev/null +++ b/aoc2023/build/packages/gleam_stdlib/gleam.toml @@ -0,0 +1,16 @@ +name = "gleam_stdlib" +version = "0.33.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/gleam_stdlib/include/gleam@dynamic_DecodeError.hrl b/aoc2023/build/packages/gleam_stdlib/include/gleam@dynamic_DecodeError.hrl new file mode 100644 index 0000000..b1135f2 --- /dev/null +++ b/aoc2023/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/gleam_stdlib/include/gleam@iterator_Iterator.hrl b/aoc2023/build/packages/gleam_stdlib/include/gleam@iterator_Iterator.hrl new file mode 100644 index 0000000..b0d08dc --- /dev/null +++ b/aoc2023/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/gleam_stdlib/include/gleam@iterator_Next.hrl b/aoc2023/build/packages/gleam_stdlib/include/gleam@iterator_Next.hrl new file mode 100644 index 0000000..1f61922 --- /dev/null +++ b/aoc2023/build/packages/gleam_stdlib/include/gleam@iterator_Next.hrl @@ -0,0 +1 @@ +-record(next, {element :: any(), accumulator :: any()}). diff --git a/aoc2023/build/packages/gleam_stdlib/include/gleam@queue_Queue.hrl b/aoc2023/build/packages/gleam_stdlib/include/gleam@queue_Queue.hrl new file mode 100644 index 0000000..88ac25e --- /dev/null +++ b/aoc2023/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/gleam_stdlib/include/gleam@regex_CompileError.hrl b/aoc2023/build/packages/gleam_stdlib/include/gleam@regex_CompileError.hrl new file mode 100644 index 0000000..ad5511e --- /dev/null +++ b/aoc2023/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/gleam_stdlib/include/gleam@regex_Match.hrl b/aoc2023/build/packages/gleam_stdlib/include/gleam@regex_Match.hrl new file mode 100644 index 0000000..4216619 --- /dev/null +++ b/aoc2023/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/gleam_stdlib/include/gleam@regex_Options.hrl b/aoc2023/build/packages/gleam_stdlib/include/gleam@regex_Options.hrl new file mode 100644 index 0000000..0074603 --- /dev/null +++ b/aoc2023/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/gleam_stdlib/include/gleam@set_Set.hrl b/aoc2023/build/packages/gleam_stdlib/include/gleam@set_Set.hrl new file mode 100644 index 0000000..6e1e226 --- /dev/null +++ b/aoc2023/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/gleam_stdlib/include/gleam@uri_Uri.hrl b/aoc2023/build/packages/gleam_stdlib/include/gleam@uri_Uri.hrl new file mode 100644 index 0000000..50150f4 --- /dev/null +++ b/aoc2023/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/gleam_stdlib/src/dict.mjs b/aoc2023/build/packages/gleam_stdlib/src/dict.mjs new file mode 100644 index 0000000..a8309e0 --- /dev/null +++ b/aoc2023/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/gleam_stdlib/src/gleam/base.gleam b/aoc2023/build/packages/gleam_stdlib/src/gleam/base.gleam new file mode 100644 index 0000000..eab2f0b --- /dev/null +++ b/aoc2023/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/gleam_stdlib/src/gleam/bit_array.gleam b/aoc2023/build/packages/gleam_stdlib/src/gleam/bit_array.gleam new file mode 100644 index 0000000..79860e9 --- /dev/null +++ b/aoc2023/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/gleam_stdlib/src/gleam/bit_builder.gleam b/aoc2023/build/packages/gleam_stdlib/src/gleam/bit_builder.gleam new file mode 100644 index 0000000..ce6fe52 --- /dev/null +++ b/aoc2023/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/gleam_stdlib/src/gleam/bit_string.gleam b/aoc2023/build/packages/gleam_stdlib/src/gleam/bit_string.gleam new file mode 100644 index 0000000..b703da0 --- /dev/null +++ b/aoc2023/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/gleam_stdlib/src/gleam/bool.gleam b/aoc2023/build/packages/gleam_stdlib/src/gleam/bool.gleam new file mode 100644 index 0000000..91bd6b7 --- /dev/null +++ b/aoc2023/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/gleam_stdlib/src/gleam/bytes_builder.gleam b/aoc2023/build/packages/gleam_stdlib/src/gleam/bytes_builder.gleam new file mode 100644 index 0000000..20c145d --- /dev/null +++ b/aoc2023/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/gleam_stdlib/src/gleam/dict.gleam b/aoc2023/build/packages/gleam_stdlib/src/gleam/dict.gleam new file mode 100644 index 0000000..280bf9d --- /dev/null +++ b/aoc2023/build/packages/gleam_stdlib/src/gleam/dict.gleam @@ -0,0 +1,544 @@ +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 +/// ``` +/// +pub fn size(dict: Dict(k, v)) -> Int { + do_size(dict) +} + +@external(erlang, "maps", "size") +@external(javascript, "../gleam_stdlib.mjs", "map_size") +fn do_size(a: 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)] +/// ``` +/// +pub fn to_list(dict: Dict(key, value)) -> List(#(key, value)) { + do_to_list(dict) +} + +@external(erlang, "maps", "to_list") +@external(javascript, "../gleam_stdlib.mjs", "map_to_list") +fn do_to_list(a: 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. +/// +pub fn from_list(list: List(#(k, v))) -> Dict(k, v) { + do_from_list(list) +} + +@target(erlang) +@external(erlang, "maps", "from_list") +fn do_from_list(a: List(#(key, value))) -> Dict(key, value) + +@target(javascript) +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)) + } +} + +@target(javascript) +fn do_from_list(list: List(#(k, v))) -> Dict(k, v) { + fold_list_of_pair(list, new()) +} + +/// 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) +} + +@target(erlang) +@external(erlang, "maps", "is_key") +fn do_has_key(a: key, b: Dict(key, v)) -> Bool + +@target(javascript) +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) +} + +@target(erlang) +@external(erlang, "maps", "map") +fn do_map_values(a: fn(key, value) -> b, b: Dict(key, value)) -> Dict(key, b) + +@target(javascript) +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) +} + +@target(erlang) +@external(erlang, "maps", "keys") +fn do_keys(a: Dict(keys, v)) -> List(keys) + +@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]) + } +} + +@target(javascript) +fn do_keys(dict: Dict(k, v)) -> List(k) { + let list_of_pairs = to_list(dict) + do_keys_acc(list_of_pairs, []) +} + +/// 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) +} + +@target(erlang) +@external(erlang, "maps", "values") +fn do_values(a: Dict(k, values)) -> List(values) + +@target(javascript) +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]) + } +} + +@target(javascript) +fn do_values(dict: Dict(k, v)) -> List(v) { + let list_of_pairs = to_list(dict) + do_values_acc(list_of_pairs, []) +} + +/// 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) +} + +@target(erlang) +@external(erlang, "maps", "filter") +fn do_filter(a: fn(key, value) -> Bool, b: Dict(key, value)) -> Dict(key, value) + +@target(javascript) +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) +} + +@target(erlang) +@external(erlang, "maps", "with") +fn do_take(a: List(k), b: Dict(k, v)) -> Dict(k, v) + +@target(javascript) +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)) + } +} + +@target(javascript) +fn do_take(desired_keys: List(k), dict: Dict(k, v)) -> Dict(k, v) { + insert_taken(dict, desired_keys, new()) +} + +/// 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) +} + +@target(erlang) +@external(erlang, "maps", "merge") +fn do_merge(a: Dict(k, v), b: Dict(k, v)) -> Dict(k, v) + +@target(javascript) +fn insert_pair(dict: Dict(k, v), pair: #(k, v)) -> Dict(k, v) { + insert(dict, pair.0, pair.1) +} + +@target(javascript) +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)) + } +} + +@target(javascript) +fn do_merge(dict: Dict(k, v), new_entries: Dict(k, v)) -> Dict(k, v) { + new_entries + |> to_list + |> fold_inserts(dict) +} + +/// 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/gleam_stdlib/src/gleam/dynamic.gleam b/aoc2023/build/packages/gleam_stdlib/src/gleam/dynamic.gleam new file mode 100644 index 0000000..c71c6f3 --- /dev/null +++ b/aoc2023/build/packages/gleam_stdlib/src/gleam/dynamic.gleam @@ -0,0 +1,1508 @@ +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/gleam_stdlib/src/gleam/float.gleam b/aoc2023/build/packages/gleam_stdlib/src/gleam/float.gleam new file mode 100644 index 0000000..5d62419 --- /dev/null +++ b/aoc2023/build/packages/gleam_stdlib/src/gleam/float.gleam @@ -0,0 +1,546 @@ +//// 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 minimum and maximum values. +/// +/// +/// ## Examples +/// +/// ```gleam +/// > random(1.0, 5.0) +/// 2.646355926896028 +/// ``` +/// +pub fn random(min: Float, max: Float) -> Float { + do_random_uniform() *. { max -. min } +. min +} + +/// Returns a random float uniformly distributed in the value range +/// 0.0 =< X < 1.0 and updates the state in the process dictionary. +/// See: <https://www.erlang.org/doc/man/rand.html#uniform-0> +/// +@external(erlang, "rand", "uniform") +@external(javascript, "../gleam_stdlib.mjs", "random_uniform") +fn do_random_uniform() -> 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/gleam_stdlib/src/gleam/function.gleam b/aoc2023/build/packages/gleam_stdlib/src/gleam/function.gleam new file mode 100644 index 0000000..daa997d --- /dev/null +++ b/aoc2023/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/gleam_stdlib/src/gleam/int.gleam b/aoc2023/build/packages/gleam_stdlib/src/gleam/int.gleam new file mode 100644 index 0000000..d93c16a --- /dev/null +++ b/aoc2023/build/packages/gleam_stdlib/src/gleam/int.gleam @@ -0,0 +1,874 @@ +//// 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 the given minimum and maximum values. +/// +/// ## Examples +/// +/// ```gleam +/// > random(1, 5) +/// 2 +/// ``` +/// +pub fn random(min: Int, max: Int) -> Int { + float.random(to_float(min), 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/gleam_stdlib/src/gleam/io.gleam b/aoc2023/build/packages/gleam_stdlib/src/gleam/io.gleam new file mode 100644 index 0000000..0c0a3ee --- /dev/null +++ b/aoc2023/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/gleam_stdlib/src/gleam/iterator.gleam b/aoc2023/build/packages/gleam_stdlib/src/gleam/iterator.gleam new file mode 100644 index 0000000..c57e7fd --- /dev/null +++ b/aoc2023/build/packages/gleam_stdlib/src/gleam/iterator.gleam @@ -0,0 +1,1530 @@ +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(#(Int, element)) { + fn() { + case continuation() { + Stop -> Stop + Continue(e, continuation) -> + Continue(#(next, e), 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 +/// [#(0, "a"), #(1, "b"), #(2, "c")] +/// ``` +/// +pub fn index(over iterator: Iterator(element)) -> Iterator(#(Int, element)) { + 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/gleam_stdlib/src/gleam/list.gleam b/aoc2023/build/packages/gleam_stdlib/src/gleam/list.gleam new file mode 100644 index 0000000..a5cffa9 --- /dev/null +++ b/aoc2023/build/packages/gleam_stdlib/src/gleam/list.gleam @@ -0,0 +1,2154 @@ +//// 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(Int, a) -> b, + index: Int, + acc: List(b), +) -> List(b) { + case list { + [] -> reverse(acc) + [x, ..xs] -> { + let acc = [fun(index, x), ..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(i, x) { #(i, x) }) +/// [#(0, "a"), #(1, "b")] +/// ``` +/// +pub fn index_map(list: List(a), with fun: fn(Int, a) -> 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_idx, i) { + 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 Erlang's `:rand` module or Javascript's +/// `Math.random()` to calculate the index shuffling. +/// +/// ## 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(0.0, 1.0), a), ..acc] }) + |> do_shuffle_by_pair_indexes() + |> do_shuffle_pair_unwrap([]) +} diff --git a/aoc2023/build/packages/gleam_stdlib/src/gleam/map.gleam b/aoc2023/build/packages/gleam_stdlib/src/gleam/map.gleam new file mode 100644 index 0000000..1f8b228 --- /dev/null +++ b/aoc2023/build/packages/gleam_stdlib/src/gleam/map.gleam @@ -0,0 +1,127 @@ +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]) + } +} + +@target(javascript) +fn do_keys(map) -> List(k) { + let list_of_pairs = + map + |> to_list + do_keys_acc(list_of_pairs, []) +} + +@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) +} + +@target(javascript) +fn do_filter(f: fn(key, value) -> Bool, map) { + let insert = fn(map, k, v) { + case f(k, v) { + True -> insert(map, k, v) + _ -> map + } + } + map + |> fold(from: new(), with: insert) +} + +@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/gleam_stdlib/src/gleam/option.gleam b/aoc2023/build/packages/gleam_stdlib/src/gleam/option.gleam new file mode 100644 index 0000000..6015c0f --- /dev/null +++ b/aoc2023/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/gleam_stdlib/src/gleam/order.gleam b/aoc2023/build/packages/gleam_stdlib/src/gleam/order.gleam new file mode 100644 index 0000000..12ce011 --- /dev/null +++ b/aoc2023/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/gleam_stdlib/src/gleam/pair.gleam b/aoc2023/build/packages/gleam_stdlib/src/gleam/pair.gleam new file mode 100644 index 0000000..894e6a8 --- /dev/null +++ b/aoc2023/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/gleam_stdlib/src/gleam/queue.gleam b/aoc2023/build/packages/gleam_stdlib/src/gleam/queue.gleam new file mode 100644 index 0000000..5bf60c8 --- /dev/null +++ b/aoc2023/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/gleam_stdlib/src/gleam/regex.gleam b/aoc2023/build/packages/gleam_stdlib/src/gleam/regex.gleam new file mode 100644 index 0000000..9ffda78 --- /dev/null +++ b/aoc2023/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/gleam_stdlib/src/gleam/result.gleam b/aoc2023/build/packages/gleam_stdlib/src/gleam/result.gleam new file mode 100644 index 0000000..fb6dddb --- /dev/null +++ b/aoc2023/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/gleam_stdlib/src/gleam/set.gleam b/aoc2023/build/packages/gleam_stdlib/src/gleam/set.gleam new file mode 100644 index 0000000..df8d500 --- /dev/null +++ b/aoc2023/build/packages/gleam_stdlib/src/gleam/set.gleam @@ -0,0 +1,264 @@ +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/gleam_stdlib/src/gleam/string.gleam b/aoc2023/build/packages/gleam_stdlib/src/gleam/string.gleam new file mode 100644 index 0000000..d4496f3 --- /dev/null +++ b/aoc2023/build/packages/gleam_stdlib/src/gleam/string.gleam @@ -0,0 +1,906 @@ +//// 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) + } + } +} + +@target(erlang) +@external(erlang, "string", "slice") +fn do_slice(a: String, b: Int, c: Int) -> String + +@target(javascript) +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) +} + +@target(erlang) +fn do_join(strings: List(String), separator: String) -> String { + strings + |> list.intersperse(with: separator) + |> concat +} + +@target(javascript) +@external(javascript, "../gleam_stdlib.mjs", "join") +fn do_join(strings strings: List(String), string string: String) -> String + +/// 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. +/// +@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/gleam_stdlib/src/gleam/string_builder.gleam b/aoc2023/build/packages/gleam_stdlib/src/gleam/string_builder.gleam new file mode 100644 index 0000000..5792ca8 --- /dev/null +++ b/aoc2023/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/gleam_stdlib/src/gleam/uri.gleam b/aoc2023/build/packages/gleam_stdlib/src/gleam/uri.gleam new file mode 100644 index 0000000..11f6ea6 --- /dev/null +++ b/aoc2023/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/gleam_stdlib/src/gleam@base.erl b/aoc2023/build/packages/gleam_stdlib/src/gleam@base.erl new file mode 100644 index 0000000..65bc3f6 --- /dev/null +++ b/aoc2023/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]). + +-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/gleam_stdlib/src/gleam@bit_array.erl b/aoc2023/build/packages/gleam_stdlib/src/gleam@bit_array.erl new file mode 100644 index 0000000..ba18dfa --- /dev/null +++ b/aoc2023/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]). + +-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/gleam_stdlib/src/gleam@bit_builder.erl b/aoc2023/build/packages/gleam_stdlib/src/gleam@bit_builder.erl new file mode 100644 index 0000000..284c6d4 --- /dev/null +++ b/aoc2023/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]). + +-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/gleam_stdlib/src/gleam@bit_string.erl b/aoc2023/build/packages/gleam_stdlib/src/gleam@bit_string.erl new file mode 100644 index 0000000..7dabaa3 --- /dev/null +++ b/aoc2023/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]). + +-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/gleam_stdlib/src/gleam@bool.erl b/aoc2023/build/packages/gleam_stdlib/src/gleam@bool.erl new file mode 100644 index 0000000..cd55358 --- /dev/null +++ b/aoc2023/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]). + +-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(), DDZ, fun(() -> DDZ)) -> DDZ. +guard(Requirement, Consequence, Alternative) -> + case Requirement of + true -> + Consequence; + + false -> + Alternative() + end. + +-spec lazy_guard(boolean(), fun(() -> DEA), fun(() -> DEA)) -> DEA. +lazy_guard(Requirement, Consequence, Alternative) -> + case Requirement of + true -> + Consequence(); + + false -> + Alternative() + end. diff --git a/aoc2023/build/packages/gleam_stdlib/src/gleam@bytes_builder.erl b/aoc2023/build/packages/gleam_stdlib/src/gleam@bytes_builder.erl new file mode 100644 index 0000000..2f6dd93 --- /dev/null +++ b/aoc2023/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]). + +-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/gleam_stdlib/src/gleam@dict.erl b/aoc2023/build/packages/gleam_stdlib/src/gleam@dict.erl new file mode 100644 index 0000000..44b89ea --- /dev/null +++ b/aoc2023/build/packages/gleam_stdlib/src/gleam@dict.erl @@ -0,0 +1,97 @@ +-module(gleam@dict). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function]). + +-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]). +-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(LC, LD)) -> list({LC, LD}). +to_list(Dict) -> + maps:to_list(Dict). + +-spec from_list(list({LM, LN})) -> dict(LM, LN). +from_list(List) -> + maps:from_list(List). + +-spec has_key(dict(LW, any()), LW) -> boolean(). +has_key(Dict, Key) -> + maps:is_key(Key, Dict). + +-spec new() -> dict(any(), any()). +new() -> + maps:new(). + +-spec get(dict(MM, MN), MM) -> {ok, MN} | {error, nil}. +get(From, Get) -> + gleam_stdlib:map_get(From, Get). + +-spec insert(dict(MY, MZ), MY, MZ) -> dict(MY, MZ). +insert(Dict, Key, Value) -> + maps:put(Key, Value, Dict). + +-spec map_values(dict(NK, NL), fun((NK, NL) -> NO)) -> dict(NK, NO). +map_values(Dict, Fun) -> + maps:map(Fun, Dict). + +-spec keys(dict(NY, any())) -> list(NY). +keys(Dict) -> + maps:keys(Dict). + +-spec values(dict(any(), OJ)) -> list(OJ). +values(Dict) -> + maps:values(Dict). + +-spec filter(dict(OS, OT), fun((OS, OT) -> boolean())) -> dict(OS, OT). +filter(Dict, Predicate) -> + maps:filter(Predicate, Dict). + +-spec take(dict(PE, PF), list(PE)) -> dict(PE, PF). +take(Dict, Desired_keys) -> + maps:with(Desired_keys, Dict). + +-spec merge(dict(PS, PT), dict(PS, PT)) -> dict(PS, PT). +merge(Dict, New_entries) -> + maps:merge(Dict, New_entries). + +-spec delete(dict(QI, QJ), QI) -> dict(QI, QJ). +delete(Dict, Key) -> + maps:remove(Key, Dict). + +-spec drop(dict(QU, QV), list(QU)) -> dict(QU, QV). +drop(Dict, Disallowed_keys) -> + case Disallowed_keys of + [] -> + Dict; + + [X | Xs] -> + drop(delete(Dict, X), Xs) + end. + +-spec update(dict(RB, RC), RB, fun((gleam@option:option(RC)) -> RC)) -> dict(RB, RC). +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({RI, RJ}), RL, fun((RL, RI, RJ) -> RL)) -> RL. +do_fold(List, Initial, Fun) -> + case List of + [] -> + Initial; + + [{K, V} | Rest] -> + do_fold(Rest, Fun(Initial, K, V), Fun) + end. + +-spec fold(dict(RM, RN), RQ, fun((RQ, RM, RN) -> RQ)) -> RQ. +fold(Dict, Initial, Fun) -> + _pipe = Dict, + _pipe@1 = to_list(_pipe), + do_fold(_pipe@1, Initial, Fun). diff --git a/aoc2023/build/packages/gleam_stdlib/src/gleam@dynamic.erl b/aoc2023/build/packages/gleam_stdlib/src/gleam@dynamic.erl new file mode 100644 index 0000000..38f4b4e --- /dev/null +++ b/aoc2023/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]). + +-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, DZX} | {error, list(decode_error())})) -> fun((dynamic_()) -> {ok, + gleam@option:option(DZX)} | + {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, EEE} | {error, list(decode_error())}))) -> fun((dynamic_()) -> {ok, + EEE} | + {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((EEI) -> EEJ), + fun((dynamic_()) -> {ok, EEI} | {error, list(decode_error())}) +) -> fun((dynamic_()) -> {ok, EEJ} | {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, DZL} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, DZN} | {error, list(decode_error())}) +) -> fun((dynamic_()) -> {ok, {ok, DZL} | {error, DZN}} | + {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, DZS} | {error, list(decode_error())})) -> fun((dynamic_()) -> {ok, + list(DZS)} | + {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, DYG} | {error, list(decode_error())}, + fun((decode_error()) -> decode_error()) +) -> {ok, DYG} | {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, EAH} | {error, list(decode_error())}) +) -> fun((dynamic_()) -> {ok, EAH} | {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, EAL} | {error, list(decode_error())}) +) -> fun((dynamic_()) -> {ok, gleam@option:option(EAL)} | + {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, EAT} | {error, list(decode_error())}) +) -> fun((dynamic_()) -> {ok, EAT} | {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, EBT} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, EBV} | {error, list(decode_error())}) +) -> fun((dynamic_()) -> {ok, {EBT, EBV}} | {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, EBY} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, ECA} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, ECC} | {error, list(decode_error())}) +) -> fun((dynamic_()) -> {ok, {EBY, ECA, ECC}} | {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, ECF} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, ECH} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, ECJ} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, ECL} | {error, list(decode_error())}) +) -> fun((dynamic_()) -> {ok, {ECF, ECH, ECJ, ECL}} | + {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, ECO} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, ECQ} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, ECS} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, ECU} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, ECW} | {error, list(decode_error())}) +) -> fun((dynamic_()) -> {ok, {ECO, ECQ, ECS, ECU, ECW}} | + {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, ECZ} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, EDB} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, EDD} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, EDF} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, EDH} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, EDJ} | {error, list(decode_error())}) +) -> fun((dynamic_()) -> {ok, {ECZ, EDB, EDD, EDF, EDH, EDJ}} | + {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, EDM} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, EDO} | {error, list(decode_error())}) +) -> fun((dynamic_()) -> {ok, gleam@dict:dict(EDM, EDO)} | + {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 = gleam@dict: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, gleam@dict:from_list(Pairs)} end + ) + end + ) + end. + +-spec map( + fun((dynamic_()) -> {ok, EDT} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, EDV} | {error, list(decode_error())}) +) -> fun((dynamic_()) -> {ok, gleam@dict:dict(EDT, EDV)} | + {error, list(decode_error())}). +map(Key_type, Value_type) -> + dict(Key_type, Value_type). + +-spec decode2( + fun((EEM, EEN) -> EEO), + fun((dynamic_()) -> {ok, EEM} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, EEN} | {error, list(decode_error())}) +) -> fun((dynamic_()) -> {ok, EEO} | {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((EES, EET, EEU) -> EEV), + fun((dynamic_()) -> {ok, EES} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, EET} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, EEU} | {error, list(decode_error())}) +) -> fun((dynamic_()) -> {ok, EEV} | {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((EFA, EFB, EFC, EFD) -> EFE), + fun((dynamic_()) -> {ok, EFA} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, EFB} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, EFC} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, EFD} | {error, list(decode_error())}) +) -> fun((dynamic_()) -> {ok, EFE} | {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((EFK, EFL, EFM, EFN, EFO) -> EFP), + fun((dynamic_()) -> {ok, EFK} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, EFL} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, EFM} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, EFN} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, EFO} | {error, list(decode_error())}) +) -> fun((dynamic_()) -> {ok, EFP} | {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((EFW, EFX, EFY, EFZ, EGA, EGB) -> EGC), + fun((dynamic_()) -> {ok, EFW} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, EFX} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, EFY} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, EFZ} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, EGA} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, EGB} | {error, list(decode_error())}) +) -> fun((dynamic_()) -> {ok, EGC} | {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((EGK, EGL, EGM, EGN, EGO, EGP, EGQ) -> EGR), + fun((dynamic_()) -> {ok, EGK} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, EGL} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, EGM} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, EGN} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, EGO} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, EGP} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, EGQ} | {error, list(decode_error())}) +) -> fun((dynamic_()) -> {ok, EGR} | {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((EHA, EHB, EHC, EHD, EHE, EHF, EHG, EHH) -> EHI), + fun((dynamic_()) -> {ok, EHA} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, EHB} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, EHC} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, EHD} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, EHE} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, EHF} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, EHG} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, EHH} | {error, list(decode_error())}) +) -> fun((dynamic_()) -> {ok, EHI} | {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((EHS, EHT, EHU, EHV, EHW, EHX, EHY, EHZ, EIA) -> EIB), + fun((dynamic_()) -> {ok, EHS} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, EHT} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, EHU} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, EHV} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, EHW} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, EHX} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, EHY} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, EHZ} | {error, list(decode_error())}), + fun((dynamic_()) -> {ok, EIA} | {error, list(decode_error())}) +) -> fun((dynamic_()) -> {ok, EIB} | {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/gleam_stdlib/src/gleam@float.erl b/aoc2023/build/packages/gleam_stdlib/src/gleam@float.erl new file mode 100644 index 0000000..33b3d4a --- /dev/null +++ b/aoc2023/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]). + +-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/2, 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(), float()) -> float(). +random(Min, Max) -> + (rand:uniform() * (Max - Min)) + Min. + +-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/gleam_stdlib/src/gleam@function.erl b/aoc2023/build/packages/gleam_stdlib/src/gleam@function.erl new file mode 100644 index 0000000..3496318 --- /dev/null +++ b/aoc2023/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]). + +-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((DOO) -> DOP), fun((DOP) -> DOQ)) -> fun((DOO) -> DOQ). +compose(Fun1, Fun2) -> + fun(A) -> Fun2(Fun1(A)) end. + +-spec curry2(fun((DOR, DOS) -> DOT)) -> fun((DOR) -> fun((DOS) -> DOT)). +curry2(Fun) -> + fun(A) -> fun(B) -> Fun(A, B) end end. + +-spec curry3(fun((DOV, DOW, DOX) -> DOY)) -> fun((DOV) -> fun((DOW) -> fun((DOX) -> DOY))). +curry3(Fun) -> + fun(A) -> fun(B) -> fun(C) -> Fun(A, B, C) end end end. + +-spec curry4(fun((DPA, DPB, DPC, DPD) -> DPE)) -> fun((DPA) -> fun((DPB) -> fun((DPC) -> fun((DPD) -> DPE)))). +curry4(Fun) -> + fun(A) -> fun(B) -> fun(C) -> fun(D) -> Fun(A, B, C, D) end end end end. + +-spec curry5(fun((DPG, DPH, DPI, DPJ, DPK) -> DPL)) -> fun((DPG) -> fun((DPH) -> fun((DPI) -> fun((DPJ) -> fun((DPK) -> DPL))))). +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((DPN, DPO, DPP, DPQ, DPR, DPS) -> DPT)) -> fun((DPN) -> fun((DPO) -> fun((DPP) -> fun((DPQ) -> fun((DPR) -> fun((DPS) -> DPT)))))). +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((DPV, DPW) -> DPX)) -> fun((DPW, DPV) -> DPX). +flip(Fun) -> + fun(B, A) -> Fun(A, B) end. + +-spec identity(DPY) -> DPY. +identity(X) -> + X. + +-spec constant(DPZ) -> fun((any()) -> DPZ). +constant(Value) -> + fun(_) -> Value end. + +-spec tap(DQB, fun((DQB) -> any())) -> DQB. +tap(Arg, Effect) -> + Effect(Arg), + Arg. + +-spec apply1(fun((DQD) -> DQE), DQD) -> DQE. +apply1(Fun, Arg1) -> + Fun(Arg1). + +-spec apply2(fun((DQF, DQG) -> DQH), DQF, DQG) -> DQH. +apply2(Fun, Arg1, Arg2) -> + Fun(Arg1, Arg2). + +-spec apply3(fun((DQI, DQJ, DQK) -> DQL), DQI, DQJ, DQK) -> DQL. +apply3(Fun, Arg1, Arg2, Arg3) -> + Fun(Arg1, Arg2, Arg3). diff --git a/aoc2023/build/packages/gleam_stdlib/src/gleam@int.erl b/aoc2023/build/packages/gleam_stdlib/src/gleam@int.erl new file mode 100644 index 0000000..2a5dd2c --- /dev/null +++ b/aoc2023/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]). + +-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/2, 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()) -> integer(). +random(Min, Max) -> + _pipe = gleam@float:random(to_float(Min), 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/gleam_stdlib/src/gleam@io.erl b/aoc2023/build/packages/gleam_stdlib/src/gleam@io.erl new file mode 100644 index 0000000..a46eae3 --- /dev/null +++ b/aoc2023/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]). + +-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(CZT) -> CZT. +debug(Term) -> + _pipe = Term, + _pipe@1 = gleam@string:inspect(_pipe), + gleam_stdlib:println_error(_pipe@1), + Term. diff --git a/aoc2023/build/packages/gleam_stdlib/src/gleam@iterator.erl b/aoc2023/build/packages/gleam_stdlib/src/gleam@iterator.erl new file mode 100644 index 0000000..aa84139 --- /dev/null +++ b/aoc2023/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]). + +-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(BPF) :: stop | {continue, BPF, fun(() -> action(BPF))}. + +-opaque iterator(BPG) :: {iterator, fun(() -> action(BPG))}. + +-type step(BPH, BPI) :: {next, BPH, BPI} | done. + +-type chunk(BPJ, BPK) :: {another_by, + list(BPJ), + BPK, + BPJ, + fun(() -> action(BPJ))} | + {last_by, list(BPJ)}. + +-type sized_chunk(BPL) :: {another, list(BPL), fun(() -> action(BPL))} | + {last, list(BPL)} | + no_more. + +-spec stop() -> action(any()). +stop() -> + stop. + +-spec do_unfold(BPO, fun((BPO) -> step(BPP, BPO))) -> fun(() -> action(BPP)). +do_unfold(Initial, F) -> + fun() -> case F(Initial) of + {next, X, Acc} -> + {continue, X, do_unfold(Acc, F)}; + + done -> + stop + end end. + +-spec unfold(BPT, fun((BPT) -> step(BPU, BPT))) -> iterator(BPU). +unfold(Initial, F) -> + _pipe = Initial, + _pipe@1 = do_unfold(_pipe, F), + {iterator, _pipe@1}. + +-spec repeatedly(fun(() -> BPY)) -> iterator(BPY). +repeatedly(F) -> + unfold(nil, fun(_) -> {next, F(), nil} end). + +-spec repeat(BQA) -> iterator(BQA). +repeat(X) -> + repeatedly(fun() -> X end). + +-spec from_list(list(BQC)) -> iterator(BQC). +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(BQF)), + BQH, + fun((BQH, BQF) -> step(BQI, BQH)) +) -> fun(() -> action(BQI)). +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(BQM), BQO, fun((BQO, BQM) -> step(BQP, BQO))) -> iterator(BQP). +transform(Iterator, Initial, F) -> + _pipe = do_transform(erlang:element(2, Iterator), Initial, F), + {iterator, _pipe}. + +-spec do_fold(fun(() -> action(BQT)), fun((BQV, BQT) -> BQV), BQV) -> BQV. +do_fold(Continuation, F, Accumulator) -> + case Continuation() of + {continue, Elem, Next} -> + do_fold(Next, F, F(Accumulator, Elem)); + + stop -> + Accumulator + end. + +-spec fold(iterator(BQW), BQY, fun((BQY, BQW) -> BQY)) -> BQY. +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(BRB)) -> list(BRB). +to_list(Iterator) -> + _pipe = Iterator, + _pipe@1 = fold(_pipe, [], fun(Acc, E) -> [E | Acc] end), + gleam@list:reverse(_pipe@1). + +-spec step(iterator(BRE)) -> step(BRE, iterator(BRE)). +step(Iterator) -> + case (erlang:element(2, Iterator))() of + stop -> + done; + + {continue, E, A} -> + {next, E, {iterator, A}} + end. + +-spec do_take(fun(() -> action(BRJ)), integer()) -> fun(() -> action(BRJ)). +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(BRM), integer()) -> iterator(BRM). +take(Iterator, Desired) -> + _pipe = erlang:element(2, Iterator), + _pipe@1 = do_take(_pipe, Desired), + {iterator, _pipe@1}. + +-spec do_drop(fun(() -> action(BRP)), integer()) -> action(BRP). +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(BRS), integer()) -> iterator(BRS). +drop(Iterator, Desired) -> + _pipe = fun() -> do_drop(erlang:element(2, Iterator), Desired) end, + {iterator, _pipe}. + +-spec do_map(fun(() -> action(BRV)), fun((BRV) -> BRX)) -> fun(() -> action(BRX)). +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(BRZ), fun((BRZ) -> BSB)) -> iterator(BSB). +map(Iterator, F) -> + _pipe = erlang:element(2, Iterator), + _pipe@1 = do_map(_pipe, F), + {iterator, _pipe@1}. + +-spec do_map2( + fun(() -> action(BSD)), + fun(() -> action(BSF)), + fun((BSD, BSF) -> BSH) +) -> fun(() -> action(BSH)). +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(BSJ), iterator(BSL), fun((BSJ, BSL) -> BSN)) -> iterator(BSN). +map2(Iterator1, Iterator2, Fun) -> + _pipe = do_map2( + erlang:element(2, Iterator1), + erlang:element(2, Iterator2), + Fun + ), + {iterator, _pipe}. + +-spec do_append(fun(() -> action(BSP)), fun(() -> action(BSP))) -> action(BSP). +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(BST), iterator(BST)) -> iterator(BST). +append(First, Second) -> + _pipe = fun() -> + do_append(erlang:element(2, First), erlang:element(2, Second)) + end, + {iterator, _pipe}. + +-spec do_flatten(fun(() -> action(iterator(BSX)))) -> action(BSX). +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(BTB))) -> iterator(BTB). +flatten(Iterator) -> + _pipe = fun() -> do_flatten(erlang:element(2, Iterator)) end, + {iterator, _pipe}. + +-spec concat(list(iterator(BTF))) -> iterator(BTF). +concat(Iterators) -> + flatten(from_list(Iterators)). + +-spec flat_map(iterator(BTJ), fun((BTJ) -> iterator(BTL))) -> iterator(BTL). +flat_map(Iterator, F) -> + _pipe = Iterator, + _pipe@1 = map(_pipe, F), + flatten(_pipe@1). + +-spec do_filter(fun(() -> action(BTO)), fun((BTO) -> boolean())) -> action(BTO). +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(BTR), fun((BTR) -> boolean())) -> iterator(BTR). +filter(Iterator, Predicate) -> + _pipe = fun() -> do_filter(erlang:element(2, Iterator), Predicate) end, + {iterator, _pipe}. + +-spec cycle(iterator(BTU)) -> iterator(BTU). +cycle(Iterator) -> + _pipe = repeat(Iterator), + flatten(_pipe). + +-spec do_find(fun(() -> action(BTY)), fun((BTY) -> boolean())) -> {ok, BTY} | + {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(BUC), fun((BUC) -> boolean())) -> {ok, BUC} | {error, nil}. +find(Haystack, Is_desired) -> + _pipe = erlang:element(2, Haystack), + do_find(_pipe, Is_desired). + +-spec do_index(fun(() -> action(BUG)), integer()) -> fun(() -> action({integer(), + BUG})). +do_index(Continuation, Next) -> + fun() -> case Continuation() of + stop -> + stop; + + {continue, E, Continuation@1} -> + {continue, {Next, E}, do_index(Continuation@1, Next + 1)} + end end. + +-spec index(iterator(BUJ)) -> iterator({integer(), BUJ}). +index(Iterator) -> + _pipe = erlang:element(2, Iterator), + _pipe@1 = do_index(_pipe, 0), + {iterator, _pipe@1}. + +-spec iterate(BUM, fun((BUM) -> BUM)) -> iterator(BUM). +iterate(Initial, F) -> + unfold(Initial, fun(Element) -> {next, Element, F(Element)} end). + +-spec do_take_while(fun(() -> action(BUO)), fun((BUO) -> boolean())) -> fun(() -> action(BUO)). +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(BUR), fun((BUR) -> boolean())) -> iterator(BUR). +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(BUU)), fun((BUU) -> boolean())) -> action(BUU). +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(BUX), fun((BUX) -> boolean())) -> iterator(BUX). +drop_while(Iterator, Predicate) -> + _pipe = fun() -> do_drop_while(erlang:element(2, Iterator), Predicate) end, + {iterator, _pipe}. + +-spec do_scan(fun(() -> action(BVA)), fun((BVC, BVA) -> BVC), BVC) -> fun(() -> action(BVC)). +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(BVE), BVG, fun((BVG, BVE) -> BVG)) -> iterator(BVG). +scan(Iterator, Initial, F) -> + _pipe = erlang:element(2, Iterator), + _pipe@1 = do_scan(_pipe, F, Initial), + {iterator, _pipe@1}. + +-spec do_zip(fun(() -> action(BVI)), fun(() -> action(BVK))) -> fun(() -> action({BVI, + BVK})). +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(BVN), iterator(BVP)) -> iterator({BVN, BVP}). +zip(Left, Right) -> + _pipe = do_zip(erlang:element(2, Left), erlang:element(2, Right)), + {iterator, _pipe}. + +-spec next_chunk(fun(() -> action(BVS)), fun((BVS) -> BVU), BVU, list(BVS)) -> chunk(BVS, BVU). +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(BVY)), fun((BVY) -> BWA), BWA, BVY) -> action(list(BVY)). +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(BWD), fun((BWD) -> any())) -> iterator(list(BWD)). +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(BWI)), integer(), list(BWI)) -> sized_chunk(BWI). +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(BWM)), integer()) -> fun(() -> action(list(BWM))). +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(BWQ), integer()) -> iterator(list(BWQ)). +sized_chunk(Iterator, Count) -> + _pipe = erlang:element(2, Iterator), + _pipe@1 = do_sized_chunk(_pipe, Count), + {iterator, _pipe@1}. + +-spec do_intersperse(fun(() -> action(BWU)), BWU) -> action(BWU). +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(BWX), BWX) -> iterator(BWX). +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(BXA)), fun((BXA) -> 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(BXC), fun((BXC) -> boolean())) -> boolean(). +any(Iterator, Predicate) -> + _pipe = erlang:element(2, Iterator), + do_any(_pipe, Predicate). + +-spec do_all(fun(() -> action(BXE)), fun((BXE) -> 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(BXG), fun((BXG) -> boolean())) -> boolean(). +all(Iterator, Predicate) -> + _pipe = erlang:element(2, Iterator), + do_all(_pipe, Predicate). + +-spec update_group_with(BXI) -> fun((gleam@option:option(list(BXI))) -> list(BXI)). +update_group_with(El) -> + fun(Maybe_group) -> case Maybe_group of + {some, Group} -> + [El | Group]; + + none -> + [El] + end end. + +-spec group_updater(fun((BXM) -> BXN)) -> fun((gleam@dict:dict(BXN, list(BXM)), BXM) -> gleam@dict:dict(BXN, list(BXM))). +group_updater(F) -> + fun(Groups, Elem) -> _pipe = Groups, + gleam@dict:update(_pipe, F(Elem), update_group_with(Elem)) end. + +-spec group(iterator(BXU), fun((BXU) -> BXW)) -> gleam@dict:dict(BXW, list(BXU)). +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(BYA), fun((BYA, BYA) -> BYA)) -> {ok, BYA} | {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(BYE)) -> {ok, BYE} | {error, nil}. +last(Iterator) -> + _pipe = Iterator, + reduce(_pipe, fun(_, Elem) -> Elem end). + +-spec empty() -> iterator(any()). +empty() -> + {iterator, fun stop/0}. + +-spec once(fun(() -> BYK)) -> iterator(BYK). +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(BYM) -> iterator(BYM). +single(Elem) -> + once(fun() -> Elem end). + +-spec do_interleave(fun(() -> action(BYO)), fun(() -> action(BYO))) -> action(BYO). +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(BYS), iterator(BYS)) -> iterator(BYS). +interleave(Left, Right) -> + _pipe = fun() -> + do_interleave(erlang:element(2, Left), erlang:element(2, Right)) + end, + {iterator, _pipe}. + +-spec do_fold_until( + fun(() -> action(BYW)), + fun((BYY, BYW) -> gleam@list:continue_or_stop(BYY)), + BYY +) -> BYY. +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(BZA), + BZC, + fun((BZC, BZA) -> gleam@list:continue_or_stop(BZC)) +) -> BZC. +fold_until(Iterator, Initial, F) -> + _pipe = erlang:element(2, Iterator), + do_fold_until(_pipe, F, Initial). + +-spec do_try_fold( + fun(() -> action(BZE)), + fun((BZG, BZE) -> {ok, BZG} | {error, BZH}), + BZG +) -> {ok, BZG} | {error, BZH}. +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(BZM), BZO, fun((BZO, BZM) -> {ok, BZO} | {error, BZP})) -> {ok, + BZO} | + {error, BZP}. +try_fold(Iterator, Initial, F) -> + _pipe = erlang:element(2, Iterator), + do_try_fold(_pipe, F, Initial). + +-spec first(iterator(BZU)) -> {ok, BZU} | {error, nil}. +first(Iterator) -> + case (erlang:element(2, Iterator))() of + stop -> + {error, nil}; + + {continue, E, _} -> + {ok, E} + end. + +-spec at(iterator(BZY), integer()) -> {ok, BZY} | {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(CAG), fun((CAG) -> any())) -> nil. +each(Iterator, F) -> + _pipe = Iterator, + _pipe@1 = map(_pipe, F), + run(_pipe@1). + +-spec yield(CAJ, fun(() -> iterator(CAJ))) -> iterator(CAJ). +yield(Element, Next) -> + {iterator, fun() -> {continue, Element, erlang:element(2, Next())} end}. diff --git a/aoc2023/build/packages/gleam_stdlib/src/gleam@list.erl b/aoc2023/build/packages/gleam_stdlib/src/gleam@list.erl new file mode 100644 index 0000000..6c2e684 --- /dev/null +++ b/aoc2023/build/packages/gleam_stdlib/src/gleam@list.erl @@ -0,0 +1,1129 @@ +-module(gleam@list). +-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function]). + +-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(UD) :: {continue, UD} | {stop, UD}. + +-spec length(list(any())) -> integer(). +length(List) -> + erlang:length(List). + +-spec reverse(list(UI)) -> list(UI). +reverse(Xs) -> + lists:reverse(Xs). + +-spec is_empty(list(any())) -> boolean(). +is_empty(List) -> + List =:= []. + +-spec contains(list(UQ), UQ) -> boolean(). +contains(List, Elem) -> + case List of + [] -> + false; + + [First | _] when First =:= Elem -> + true; + + [_ | Rest] -> + contains(Rest, Elem) + end. + +-spec first(list(US)) -> {ok, US} | {error, nil}. +first(List) -> + case List of + [] -> + {error, nil}; + + [X | _] -> + {ok, X} + end. + +-spec rest(list(UW)) -> {ok, list(UW)} | {error, nil}. +rest(List) -> + case List of + [] -> + {error, nil}; + + [_ | Xs] -> + {ok, Xs} + end. + +-spec update_group(fun((VB) -> VC)) -> fun((gleam@dict:dict(VC, list(VB)), VB) -> gleam@dict:dict(VC, list(VB))). +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(VP), fun((VP) -> boolean()), list(VP)) -> list(VP). +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(VT), fun((VT) -> boolean())) -> list(VT). +filter(List, Predicate) -> + do_filter(List, Predicate, []). + +-spec do_filter_map(list(VW), fun((VW) -> {ok, VY} | {error, any()}), list(VY)) -> list(VY). +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(WE), fun((WE) -> {ok, WG} | {error, any()})) -> list(WG). +filter_map(List, Fun) -> + do_filter_map(List, Fun, []). + +-spec do_map(list(WL), fun((WL) -> WN), list(WN)) -> list(WN). +do_map(List, Fun, Acc) -> + case List of + [] -> + reverse(Acc); + + [X | Xs] -> + do_map(Xs, Fun, [Fun(X) | Acc]) + end. + +-spec map(list(WQ), fun((WQ) -> WS)) -> list(WS). +map(List, Fun) -> + do_map(List, Fun, []). + +-spec do_map2(list(XA), list(XC), fun((XA, XC) -> XE), list(XE)) -> list(XE). +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(WU), list(WW), fun((WU, WW) -> WY)) -> list(WY). +map2(List1, List2, Fun) -> + do_map2(List1, List2, Fun, []). + +-spec do_index_map(list(XM), fun((integer(), XM) -> XO), integer(), list(XO)) -> list(XO). +do_index_map(List, Fun, Index, Acc) -> + case List of + [] -> + reverse(Acc); + + [X | Xs] -> + Acc@1 = [Fun(Index, X) | Acc], + do_index_map(Xs, Fun, Index + 1, Acc@1) + end. + +-spec index_map(list(XR), fun((integer(), XR) -> XT)) -> list(XT). +index_map(List, Fun) -> + do_index_map(List, Fun, 0, []). + +-spec do_try_map(list(XV), fun((XV) -> {ok, XX} | {error, XY}), list(XX)) -> {ok, + list(XX)} | + {error, XY}. +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(YF), fun((YF) -> {ok, YH} | {error, YI})) -> {ok, list(YH)} | + {error, YI}. +try_map(List, Fun) -> + do_try_map(List, Fun, []). + +-spec drop(list(YO), integer()) -> list(YO). +drop(List, N) -> + case N =< 0 of + true -> + List; + + false -> + case List of + [] -> + []; + + [_ | Xs] -> + drop(Xs, N - 1) + end + end. + +-spec do_take(list(YR), integer(), list(YR)) -> list(YR). +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(YV), integer()) -> list(YV). +take(List, N) -> + do_take(List, N, []). + +-spec new() -> list(any()). +new() -> + []. + +-spec append(list(AAA), list(AAA)) -> list(AAA). +append(First, Second) -> + lists:append(First, Second). + +-spec prepend(list(AAI), AAI) -> list(AAI). +prepend(List, Item) -> + [Item | List]. + +-spec reverse_and_prepend(list(AAL), list(AAL)) -> list(AAL). +reverse_and_prepend(Prefix, Suffix) -> + case Prefix of + [] -> + Suffix; + + [First | Rest] -> + reverse_and_prepend(Rest, [First | Suffix]) + end. + +-spec do_concat(list(list(AAP)), list(AAP)) -> list(AAP). +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(AAU))) -> list(AAU). +concat(Lists) -> + do_concat(Lists, []). + +-spec flatten(list(list(AAY))) -> list(AAY). +flatten(Lists) -> + do_concat(Lists, []). + +-spec flat_map(list(ABC), fun((ABC) -> list(ABE))) -> list(ABE). +flat_map(List, Fun) -> + _pipe = map(List, Fun), + concat(_pipe). + +-spec fold(list(ABH), ABJ, fun((ABJ, ABH) -> ABJ)) -> ABJ. +fold(List, Initial, Fun) -> + case List of + [] -> + Initial; + + [X | Rest] -> + fold(Rest, Fun(Initial, X), Fun) + end. + +-spec group(list(VJ), fun((VJ) -> VL)) -> gleam@dict:dict(VL, list(VJ)). +group(List, Key) -> + fold(List, gleam@dict:new(), update_group(Key)). + +-spec map_fold(list(XH), XJ, fun((XJ, XH) -> {XJ, XK})) -> {XJ, list(XK)}. +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(ABK), ABM, fun((ABM, ABK) -> ABM)) -> ABM. +fold_right(List, Initial, Fun) -> + case List of + [] -> + Initial; + + [X | Rest] -> + Fun(fold_right(Rest, Initial, Fun), X) + end. + +-spec do_index_fold( + list(ABN), + ABP, + fun((ABP, ABN, integer()) -> ABP), + integer() +) -> ABP. +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(ABQ), ABS, fun((ABS, ABQ, integer()) -> ABS)) -> ABS. +index_fold(Over, Initial, Fun) -> + do_index_fold(Over, Initial, Fun, 0). + +-spec try_fold(list(ABT), ABV, fun((ABV, ABT) -> {ok, ABV} | {error, ABW})) -> {ok, + ABV} | + {error, ABW}. +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(ACB), ACD, fun((ACD, ACB) -> continue_or_stop(ACD))) -> ACD. +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(ACF), fun((ACF) -> boolean())) -> {ok, ACF} | {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(ACJ), fun((ACJ) -> {ok, ACL} | {error, any()})) -> {ok, ACL} | + {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(ACR), fun((ACR) -> 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(ACT), fun((ACT) -> 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(ACV), list(ACX), list({ACV, ACX})) -> list({ACV, ACX}). +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(ADB), list(ADD)) -> list({ADB, ADD}). +zip(List, Other) -> + do_zip(List, Other, []). + +-spec strict_zip(list(ADG), list(ADI)) -> {ok, list({ADG, ADI})} | + {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({ATA, ATB}), list(ATA), list(ATB)) -> {list(ATA), list(ATB)}. +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({ADR, ADS})) -> {list(ADR), list(ADS)}. +unzip(Input) -> + do_unzip(Input, [], []). + +-spec do_intersperse(list(ADW), ADW, list(ADW)) -> list(ADW). +do_intersperse(List, Separator, Acc) -> + case List of + [] -> + reverse(Acc); + + [X | Rest] -> + do_intersperse(Rest, Separator, [X, Separator | Acc]) + end. + +-spec intersperse(list(AEA), AEA) -> list(AEA). +intersperse(List, Elem) -> + case List of + [] -> + List; + + [_] -> + List; + + [X | Rest] -> + do_intersperse(Rest, Elem, [X]) + end. + +-spec at(list(AED), integer()) -> {ok, AED} | {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(AEH)) -> list(AEH). +unique(List) -> + case List of + [] -> + []; + + [X | Rest] -> + [X | unique(filter(Rest, fun(Y) -> Y /= X end))] + end. + +-spec merge_up( + integer(), + integer(), + list(AEK), + list(AEK), + list(AEK), + fun((AEK, AEK) -> gleam@order:order()) +) -> list(AEK). +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(AEP), + list(AEP), + list(AEP), + fun((AEP, AEP) -> gleam@order:order()) +) -> list(AEP). +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(AEU), + integer(), + fun((AEU, AEU) -> gleam@order:order()), + boolean() +) -> list(AEU). +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(AEX), fun((AEX, AEX) -> gleam@order:order())) -> list(AEX). +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(AFD, integer(), list(AFD)) -> list(AFD). +do_repeat(A, Times, Acc) -> + case Times =< 0 of + true -> + Acc; + + false -> + do_repeat(A, Times - 1, [A | Acc]) + end. + +-spec repeat(AFG, integer()) -> list(AFG). +repeat(A, Times) -> + do_repeat(A, Times, []). + +-spec do_split(list(AFI), integer(), list(AFI)) -> {list(AFI), list(AFI)}. +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(AFN), integer()) -> {list(AFN), list(AFN)}. +split(List, Index) -> + do_split(List, Index, []). + +-spec do_split_while(list(AFR), fun((AFR) -> boolean()), list(AFR)) -> {list(AFR), + list(AFR)}. +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(AFW), fun((AFW) -> boolean())) -> {list(AFW), list(AFW)}. +split_while(List, Predicate) -> + do_split_while(List, Predicate, []). + +-spec key_find(list({AGA, AGB}), AGA) -> {ok, AGB} | {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({AGF, AGG}), AGF) -> list(AGG). +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(AWT), fun((AWT) -> boolean()), list(AWT)) -> {ok, + {AWT, list(AWT)}} | + {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(AGN), fun((AGN) -> boolean())) -> {ok, {AGN, list(AGN)}} | + {error, nil}. +pop(Haystack, Is_desired) -> + do_pop(Haystack, Is_desired, []). + +-spec do_pop_map(list(AXH), fun((AXH) -> {ok, AXU} | {error, any()}), list(AXH)) -> {ok, + {AXU, list(AXH)}} | + {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(AGW), fun((AGW) -> {ok, AGY} | {error, any()})) -> {ok, + {AGY, list(AGW)}} | + {error, nil}. +pop_map(Haystack, Is_desired) -> + do_pop_map(Haystack, Is_desired, []). + +-spec key_pop(list({AHF, AHG}), AHF) -> {ok, {AHG, list({AHF, AHG})}} | + {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({AHL, AHM}), AHL, AHM) -> list({AHL, AHM}). +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(AHP), fun((AHP) -> any())) -> nil. +each(List, F) -> + case List of + [] -> + nil; + + [X | Xs] -> + F(X), + each(Xs, F) + end. + +-spec try_each(list(AHS), fun((AHS) -> {ok, any()} | {error, AHV})) -> {ok, nil} | + {error, AHV}. +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(AZB), fun((AZB) -> boolean()), list(AZB), list(AZB)) -> {list(AZB), + list(AZB)}. +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(AIF), fun((AIF) -> boolean())) -> {list(AIF), list(AIF)}. +partition(List, Categorise) -> + do_partition(List, Categorise, [], []). + +-spec permutations(list(AIJ)) -> list(list(AIJ)). +permutations(L) -> + case L of + [] -> + [[]]; + + _ -> + _pipe = L, + _pipe@5 = index_map(_pipe, fun(I_idx, I) -> _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(AIN)), list(AIN), integer()) -> list(list(AIN)). +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(AIT), integer()) -> list(list(AIT)). +window(L, N) -> + _pipe = do_window([], L, N), + reverse(_pipe). + +-spec window_by_2(list(AIX)) -> list({AIX, AIX}). +window_by_2(L) -> + zip(L, drop(L, 1)). + +-spec drop_while(list(AJA), fun((AJA) -> boolean())) -> list(AJA). +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(AJD), fun((AJD) -> boolean()), list(AJD)) -> list(AJD). +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(AJH), fun((AJH) -> boolean())) -> list(AJH). +take_while(List, Predicate) -> + do_take_while(List, Predicate, []). + +-spec do_chunk(list(AJK), fun((AJK) -> AJM), AJM, list(AJK), list(list(AJK))) -> list(list(AJK)). +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(AJS), fun((AJS) -> any())) -> list(list(AJS)). +chunk(List, F) -> + case List of + [] -> + []; + + [First | Rest] -> + do_chunk(Rest, F, F(First), [First], []) + end. + +-spec do_sized_chunk( + list(AJX), + integer(), + integer(), + list(AJX), + list(list(AJX)) +) -> list(list(AJX)). +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(AKE), integer()) -> list(list(AKE)). +sized_chunk(List, Count) -> + do_sized_chunk(List, Count, Count, [], []). + +-spec reduce(list(AKI), fun((AKI, AKI) -> AKI)) -> {ok, AKI} | {error, nil}. +reduce(List, Fun) -> + case List of + [] -> + {error, nil}; + + [First | Rest] -> + {ok, fold(Rest, First, Fun)} + end. + +-spec do_scan(list(AKM), AKO, list(AKO), fun((AKO, AKM) -> AKO)) -> list(AKO). +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(AKR), AKT, fun((AKT, AKR) -> AKT)) -> list(AKT). +scan(List, Initial, Fun) -> + do_scan(List, Initial, [], Fun). + +-spec last(list(AKV)) -> {ok, AKV} | {error, nil}. +last(List) -> + _pipe = List, + reduce(_pipe, fun(_, Elem) -> Elem end). + +-spec combinations(list(AKZ), integer()) -> list(list(AKZ)). +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(ALD)) -> list(list({ALD, ALD})). +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(ALH)) -> list({ALH, ALH}). +combination_pairs(Items) -> + _pipe = do_combination_pairs(Items), + concat(_pipe). + +-spec transpose(list(list(ALO))) -> list(list(ALO)). +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(ALK))) -> list(ALK). +interleave(List) -> + _pipe = transpose(List), + concat(_pipe). + +-spec do_shuffle_pair_unwrap(list({float(), ALT}), list(ALT)) -> list(ALT). +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(), ALX})) -> list({float(), ALX}). +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(AMA)) -> list(AMA). +shuffle(List) -> + _pipe = List, + _pipe@1 = fold( + _pipe, + [], + fun(Acc, A) -> [{gleam@float:random(+0.0, 1.0), A} | Acc] end + ), + _pipe@2 = do_shuffle_by_pair_indexes(_pipe@1), + do_shuffle_pair_unwrap(_pipe@2, []). diff --git a/aoc2023/build/packages/gleam_stdlib/src/gleam@map.erl b/aoc2023/build/packages/gleam_stdlib/src/gleam@map.erl new file mode 100644 index 0000000..33e89a9 --- /dev/null +++ b/aoc2023/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]). + +-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) -> + gleam@dict:size(Map). + +-spec to_list(gleam@dict:dict(DAJ, DAK)) -> list({DAJ, DAK}). +to_list(Map) -> + gleam@dict:to_list(Map). + +-spec from_list(list({DAM, DAN})) -> gleam@dict:dict(DAM, DAN). +from_list(List) -> + gleam@dict:from_list(List). + +-spec has_key(gleam@dict:dict(DAR, any()), DAR) -> 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(DAU, DAV), DAU) -> {ok, DAV} | {error, nil}. +get(From, Get) -> + gleam@dict:get(From, Get). + +-spec insert(gleam@dict:dict(DAZ, DBA), DAZ, DBA) -> gleam@dict:dict(DAZ, DBA). +insert(Map, Key, Value) -> + gleam@dict:insert(Map, Key, Value). + +-spec map_values(gleam@dict:dict(DBD, DBE), fun((DBD, DBE) -> DBF)) -> gleam@dict:dict(DBD, DBF). +map_values(Map, Fun) -> + gleam@dict:map_values(Map, Fun). + +-spec keys(gleam@dict:dict(DBI, any())) -> list(DBI). +keys(Map) -> + gleam@dict:keys(Map). + +-spec values(gleam@dict:dict(any(), DBL)) -> list(DBL). +values(Map) -> + gleam@dict:values(Map). + +-spec filter(gleam@dict:dict(DBO, DBP), fun((DBO, DBP) -> boolean())) -> gleam@dict:dict(DBO, DBP). +filter(Map, Predicate) -> + gleam@dict:filter(Map, Predicate). + +-spec take(gleam@dict:dict(DBS, DDM), list(DBS)) -> gleam@dict:dict(DBS, DDM). +take(Map, Desired_keys) -> + gleam@dict:take(Map, Desired_keys). + +-spec merge(gleam@dict:dict(DDN, DDO), gleam@dict:dict(DDN, DDO)) -> gleam@dict:dict(DDN, DDO). +merge(Map, New_entries) -> + gleam@dict:merge(Map, New_entries). + +-spec delete(gleam@dict:dict(DBZ, DDQ), DBZ) -> gleam@dict:dict(DBZ, DDQ). +delete(Map, Key) -> + gleam@dict:delete(Map, Key). + +-spec drop(gleam@dict:dict(DCC, DDS), list(DCC)) -> gleam@dict:dict(DCC, DDS). +drop(Map, Disallowed_keys) -> + gleam@dict:drop(Map, Disallowed_keys). + +-spec update( + gleam@dict:dict(DCG, DCH), + DCG, + fun((gleam@option:option(DCH)) -> DCH) +) -> gleam@dict:dict(DCG, DCH). +update(Map, Key, Fun) -> + gleam@dict:update(Map, Key, Fun). + +-spec fold(gleam@dict:dict(DCM, DCN), DCL, fun((DCL, DCM, DCN) -> DCL)) -> DCL. +fold(Map, Initial, Fun) -> + gleam@dict:fold(Map, Initial, Fun). diff --git a/aoc2023/build/packages/gleam_stdlib/src/gleam@option.erl b/aoc2023/build/packages/gleam_stdlib/src/gleam@option.erl new file mode 100644 index 0000000..5c20713 --- /dev/null +++ b/aoc2023/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]). + +-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/gleam_stdlib/src/gleam@order.erl b/aoc2023/build/packages/gleam_stdlib/src/gleam@order.erl new file mode 100644 index 0000000..61649b9 --- /dev/null +++ b/aoc2023/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]). + +-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/gleam_stdlib/src/gleam@pair.erl b/aoc2023/build/packages/gleam_stdlib/src/gleam@pair.erl new file mode 100644 index 0000000..f4eff52 --- /dev/null +++ b/aoc2023/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]). + +-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/gleam_stdlib/src/gleam@queue.erl b/aoc2023/build/packages/gleam_stdlib/src/gleam@queue.erl new file mode 100644 index 0000000..6b587e7 --- /dev/null +++ b/aoc2023/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]). + +-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(DRL) :: {queue, list(DRL), list(DRL)}. + +-spec new() -> queue(any()). +new() -> + {queue, [], []}. + +-spec from_list(list(DRO)) -> queue(DRO). +from_list(List) -> + {queue, [], List}. + +-spec to_list(queue(DRR)) -> list(DRR). +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(DRY), DRY) -> queue(DRY). +push_back(Queue, Item) -> + {queue, [Item | erlang:element(2, Queue)], erlang:element(3, Queue)}. + +-spec push_front(queue(DSB), DSB) -> queue(DSB). +push_front(Queue, Item) -> + {queue, erlang:element(2, Queue), [Item | erlang:element(3, Queue)]}. + +-spec pop_back(queue(DSE)) -> {ok, {DSE, queue(DSE)}} | {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(DSJ)) -> {ok, {DSJ, queue(DSJ)}} | {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(DSO)) -> queue(DSO). +reverse(Queue) -> + {queue, erlang:element(3, Queue), erlang:element(2, Queue)}. + +-spec check_equal( + list(DSR), + list(DSR), + list(DSR), + list(DSR), + fun((DSR, DSR) -> 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(DSW), queue(DSW), fun((DSW, DSW) -> 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(DSZ), queue(DSZ)) -> 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/gleam_stdlib/src/gleam@regex.erl b/aoc2023/build/packages/gleam_stdlib/src/gleam@regex.erl new file mode 100644 index 0000000..2d1c5fc --- /dev/null +++ b/aoc2023/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]). + +-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/gleam_stdlib/src/gleam@result.erl b/aoc2023/build/packages/gleam_stdlib/src/gleam@result.erl new file mode 100644 index 0000000..7324e45 --- /dev/null +++ b/aoc2023/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]). + +-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, BFM} | {error, BFN}, fun((BFM) -> BFQ)) -> {ok, BFQ} | + {error, BFN}. +map(Result, Fun) -> + case Result of + {ok, X} -> + {ok, Fun(X)}; + + {error, E} -> + {error, E} + end. + +-spec map_error({ok, BFT} | {error, BFU}, fun((BFU) -> BFX)) -> {ok, BFT} | + {error, BFX}. +map_error(Result, Fun) -> + case Result of + {ok, X} -> + {ok, X}; + + {error, Error} -> + {error, Fun(Error)} + end. + +-spec flatten({ok, {ok, BGA} | {error, BGB}} | {error, BGB}) -> {ok, BGA} | + {error, BGB}. +flatten(Result) -> + case Result of + {ok, X} -> + X; + + {error, Error} -> + {error, Error} + end. + +-spec 'try'({ok, BGI} | {error, BGJ}, fun((BGI) -> {ok, BGM} | {error, BGJ})) -> {ok, + BGM} | + {error, BGJ}. +'try'(Result, Fun) -> + case Result of + {ok, X} -> + Fun(X); + + {error, E} -> + {error, E} + end. + +-spec then({ok, BGR} | {error, BGS}, fun((BGR) -> {ok, BGV} | {error, BGS})) -> {ok, + BGV} | + {error, BGS}. +then(Result, Fun) -> + 'try'(Result, Fun). + +-spec unwrap({ok, BHA} | {error, any()}, BHA) -> BHA. +unwrap(Result, Default) -> + case Result of + {ok, V} -> + V; + + {error, _} -> + Default + end. + +-spec lazy_unwrap({ok, BHE} | {error, any()}, fun(() -> BHE)) -> BHE. +lazy_unwrap(Result, Default) -> + case Result of + {ok, V} -> + V; + + {error, _} -> + Default() + end. + +-spec unwrap_error({ok, any()} | {error, BHJ}, BHJ) -> BHJ. +unwrap_error(Result, Default) -> + case Result of + {ok, _} -> + Default; + + {error, E} -> + E + end. + +-spec unwrap_both({ok, BHM} | {error, BHM}) -> BHM. +unwrap_both(Result) -> + case Result of + {ok, A} -> + A; + + {error, A@1} -> + A@1 + end. + +-spec nil_error({ok, BHP} | {error, any()}) -> {ok, BHP} | {error, nil}. +nil_error(Result) -> + map_error(Result, fun(_) -> nil end). + +-spec 'or'({ok, BHV} | {error, BHW}, {ok, BHV} | {error, BHW}) -> {ok, BHV} | + {error, BHW}. +'or'(First, Second) -> + case First of + {ok, _} -> + First; + + {error, _} -> + Second + end. + +-spec lazy_or({ok, BID} | {error, BIE}, fun(() -> {ok, BID} | {error, BIE})) -> {ok, + BID} | + {error, BIE}. +lazy_or(First, Second) -> + case First of + {ok, _} -> + First; + + {error, _} -> + Second() + end. + +-spec all(list({ok, BIL} | {error, BIM})) -> {ok, list(BIL)} | {error, BIM}. +all(Results) -> + gleam@list:try_map(Results, fun(X) -> X end). + +-spec do_partition(list({ok, BJA} | {error, BJB}), list(BJA), list(BJB)) -> {list(BJA), + list(BJB)}. +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, BIT} | {error, BIU})) -> {list(BIT), list(BIU)}. +partition(Results) -> + do_partition(Results, [], []). + +-spec replace({ok, any()} | {error, BJJ}, BJM) -> {ok, BJM} | {error, BJJ}. +replace(Result, Value) -> + case Result of + {ok, _} -> + {ok, Value}; + + {error, Error} -> + {error, Error} + end. + +-spec replace_error({ok, BJP} | {error, any()}, BJT) -> {ok, BJP} | {error, BJT}. +replace_error(Result, Error) -> + case Result of + {ok, X} -> + {ok, X}; + + {error, _} -> + {error, Error} + end. + +-spec values(list({ok, BJW} | {error, any()})) -> list(BJW). +values(Results) -> + gleam@list:filter_map(Results, fun(R) -> R end). + +-spec try_recover( + {ok, BKC} | {error, BKD}, + fun((BKD) -> {ok, BKC} | {error, BKG}) +) -> {ok, BKC} | {error, BKG}. +try_recover(Result, Fun) -> + case Result of + {ok, Value} -> + {ok, Value}; + + {error, Error} -> + Fun(Error) + end. diff --git a/aoc2023/build/packages/gleam_stdlib/src/gleam@set.erl b/aoc2023/build/packages/gleam_stdlib/src/gleam@set.erl new file mode 100644 index 0000000..df87b13 --- /dev/null +++ b/aoc2023/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]). + +-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(DJZ) :: {set, gleam@dict:dict(DJZ, list(nil))}. + +-spec new() -> set(any()). +new() -> + {set, gleam@dict:new()}. + +-spec size(set(any())) -> integer(). +size(Set) -> + gleam@dict:size(erlang:element(2, Set)). + +-spec insert(set(DKF), DKF) -> set(DKF). +insert(Set, Member) -> + {set, gleam@dict:insert(erlang:element(2, Set), Member, [])}. + +-spec contains(set(DKI), DKI) -> 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(DKK), DKK) -> set(DKK). +delete(Set, Member) -> + {set, gleam@dict:delete(erlang:element(2, Set), Member)}. + +-spec to_list(set(DKN)) -> list(DKN). +to_list(Set) -> + gleam@dict:keys(erlang:element(2, Set)). + +-spec from_list(list(DKQ)) -> set(DKQ). +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(DKT), DKV, fun((DKV, DKT) -> DKV)) -> DKV. +fold(Set, Initial, Reducer) -> + gleam@dict:fold( + erlang:element(2, Set), + Initial, + fun(A, K, _) -> Reducer(A, K) end + ). + +-spec filter(set(DKW), fun((DKW) -> boolean())) -> set(DKW). +filter(Set, Predicate) -> + {set, + gleam@dict:filter(erlang:element(2, Set), fun(M, _) -> Predicate(M) end)}. + +-spec drop(set(DKZ), list(DKZ)) -> set(DKZ). +drop(Set, Disallowed) -> + gleam@list:fold(Disallowed, Set, fun delete/2). + +-spec take(set(DLD), list(DLD)) -> set(DLD). +take(Set, Desired) -> + {set, gleam@dict:take(erlang:element(2, Set), Desired)}. + +-spec order(set(DLH), set(DLH)) -> {set(DLH), set(DLH)}. +order(First, Second) -> + case gleam@dict:size(erlang:element(2, First)) > gleam@dict:size( + erlang:element(2, Second) + ) of + true -> + {First, Second}; + + false -> + {Second, First} + end. + +-spec union(set(DLM), set(DLM)) -> set(DLM). +union(First, Second) -> + {Larger, Smaller} = order(First, Second), + fold(Smaller, Larger, fun insert/2). + +-spec intersection(set(DLQ), set(DLQ)) -> set(DLQ). +intersection(First, Second) -> + {Larger, Smaller} = order(First, Second), + take(Larger, to_list(Smaller)). diff --git a/aoc2023/build/packages/gleam_stdlib/src/gleam@string.erl b/aoc2023/build/packages/gleam_stdlib/src/gleam@string.erl new file mode 100644 index 0000000..6cba31d --- /dev/null +++ b/aoc2023/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]). + +-export([is_empty/1, length/1, reverse/1, replace/3, lowercase/1, uppercase/1, compare/2, slice/3, crop/2, drop_left/2, drop_right/2, contains/2, starts_with/2, ends_with/2, split_once/2, append/2, concat/1, repeat/2, join/2, pad_left/3, pad_right/3, trim/1, trim_left/1, trim_right/1, pop_grapheme/1, to_graphemes/1, split/2, 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 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 crop(binary(), binary()) -> binary(). +crop(String, Substring) -> + gleam_stdlib:crop_string(String, Substring). + +-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 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 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_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 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 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/gleam_stdlib/src/gleam@string_builder.erl b/aoc2023/build/packages/gleam_stdlib/src/gleam@string_builder.erl new file mode 100644 index 0000000..693e840 --- /dev/null +++ b/aoc2023/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]). + +-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/gleam_stdlib/src/gleam@uri.erl b/aoc2023/build/packages/gleam_stdlib/src/gleam@uri.erl new file mode 100644 index 0000000..7ec4fe7 --- /dev/null +++ b/aoc2023/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]). + +-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(DFL)) -> list(DFL). +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/gleam_stdlib/src/gleam_stdlib.app.src b/aoc2023/build/packages/gleam_stdlib/src/gleam_stdlib.app.src new file mode 100644 index 0000000..bcf08e2 --- /dev/null +++ b/aoc2023/build/packages/gleam_stdlib/src/gleam_stdlib.app.src @@ -0,0 +1,31 @@ +{application, gleam_stdlib, [ + {vsn, "0.33.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/gleam_stdlib/src/gleam_stdlib.erl b/aoc2023/build/packages/gleam_stdlib/src/gleam_stdlib.erl new file mode 100644 index 0000000..c6ea125 --- /dev/null +++ b/aoc2023/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/gleam_stdlib/src/gleam_stdlib.mjs b/aoc2023/build/packages/gleam_stdlib/src/gleam_stdlib.mjs new file mode 100644 index 0000000..a908b23 --- /dev/null +++ b/aoc2023/build/packages/gleam_stdlib/src/gleam_stdlib.mjs @@ -0,0 +1,875 @@ +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) { + return List.fromArray( + Array.from(graphemes_iterator(string)).map((item) => item.segment) + ); +} + +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)); +} |