Initial commit

This commit is contained in:
Hugo Mårdbrink 2025-11-30 15:44:22 +01:00
commit a6272848f9
379 changed files with 74829 additions and 0 deletions

View file

@ -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.

View file

@ -0,0 +1,34 @@
# stdlib
[![Package Version](https://img.shields.io/hexpm/v/gleam_stdlib)](https://hex.pm/packages/gleam_stdlib)
[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/gleam_stdlib/)
[![Discord chat](https://img.shields.io/discord/768594524158427167?color=blue)](https://discord.gg/Fm8Pwmy)
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
```
```gleam
import gleam/io
pub fn greet(name: String) -> Nil {
io.println("Hello " <> name <> "!")
}
```
## Targets
Gleam's standard library supports both targets: Erlang and JavaScript.
### Compatibility
This library is compatible with all versions of Erlang/OTP 26 and higher,
as well as all NodeJS, Deno, Bun, 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.

View file

@ -0,0 +1,14 @@
name = "gleam_stdlib"
version = "0.65.0"
gleam = ">= 1.11.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 = ["./"]

View file

@ -0,0 +1,5 @@
-record(decode_error, {
expected :: binary(),
found :: binary(),
path :: list(binary())
}).

View file

@ -0,0 +1,4 @@
-record(decoder, {
function :: fun((gleam@dynamic:dynamic_()) -> {any(),
list(gleam@dynamic@decode:decode_error())})
}).

View file

@ -0,0 +1 @@
-record(set, {dict :: gleam@dict:dict(any(), list(nil))}).

View file

@ -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())
}).

View file

@ -0,0 +1,993 @@
/**
* 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 = /* @__PURE__ */ new WeakMap();
const tempDataView = /* @__PURE__ */ new DataView(
/* @__PURE__ */ 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;
}
try {
this.forEach((v, k) => {
if (!isEqual(o.get(k, !v), v)) {
throw unequalDictSymbol;
}
});
return true;
} catch (e) {
if (e === unequalDictSymbol) {
return false;
}
throw e;
}
}
}
// This is thrown internally in Dict.equals() so that it returns false as soon
// as a non-matching key is found
const unequalDictSymbol = /* @__PURE__ */ Symbol();

View file

@ -0,0 +1,280 @@
//// BitArrays are a sequence of binary data of any length.
import gleam/int
import gleam/order
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 bits in the bit array.
///
@external(erlang, "erlang", "bit_size")
@external(javascript, "../gleam_stdlib.mjs", "bit_array_bit_size")
pub fn bit_size(x: BitArray) -> Int
/// Returns an integer which is the number of bytes in the bit array.
///
@external(erlang, "erlang", "byte_size")
@external(javascript, "../gleam_stdlib.mjs", "bit_array_byte_size")
pub fn byte_size(x: BitArray) -> Int
/// Pads a bit array with zeros so that it is a whole number of bytes.
///
@external(erlang, "gleam_stdlib", "bit_array_pad_to_bytes")
@external(javascript, "../gleam_stdlib.mjs", "bit_array_pad_to_bytes")
pub fn pad_to_bytes(x: BitArray) -> BitArray
/// 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 {
is_utf8_loop(bits)
}
@target(erlang)
fn is_utf8_loop(bits: BitArray) -> Bool {
case bits {
<<>> -> True
<<_:utf8, rest:bytes>> -> is_utf8_loop(rest)
_ -> False
}
}
@target(javascript)
fn is_utf8_loop(bits: BitArray) -> Bool {
case to_string(bits) {
Ok(_) -> True
Error(_) -> False
}
}
/// Converts a bit array to a string.
///
/// Returns an error if the bit array is invalid UTF-8 data.
///
@external(javascript, "../gleam_stdlib.mjs", "bit_array_to_string")
pub fn to_string(bits: BitArray) -> Result(String, Nil) {
case is_utf8(bits) {
True -> Ok(unsafe_to_string(bits))
False -> Error(Nil)
}
}
@external(erlang, "gleam_stdlib", "identity")
fn unsafe_to_string(a: BitArray) -> String
/// 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.
///
/// If the bit array does not contain a whole number of bytes then it is padded
/// with zero bits prior to being encoded.
///
@external(erlang, "gleam_stdlib", "base64_encode")
@external(javascript, "../gleam_stdlib.mjs", "base64_encode")
pub fn base64_encode(input: BitArray, padding: Bool) -> 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", "base64_decode")
@external(javascript, "../gleam_stdlib.mjs", "base64_decode")
fn decode64(a: String) -> Result(BitArray, Nil)
/// Encodes a `BitArray` into a base 64 encoded string with URL and filename
/// safe alphabet.
///
/// If the bit array does not contain a whole number of bytes then it is padded
/// with zero bits prior to being encoded.
///
pub fn base64_url_encode(input: BitArray, padding: Bool) -> String {
input
|> base64_encode(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()
}
/// Encodes a `BitArray` into a base 16 encoded string.
///
/// If the bit array does not contain a whole number of bytes then it is padded
/// with zero bits prior to being encoded.
///
@external(erlang, "gleam_stdlib", "base16_encode")
@external(javascript, "../gleam_stdlib.mjs", "base16_encode")
pub fn base16_encode(input: BitArray) -> String
/// Decodes a base 16 encoded string into a `BitArray`.
///
@external(erlang, "gleam_stdlib", "base16_decode")
@external(javascript, "../gleam_stdlib.mjs", "base16_decode")
pub fn base16_decode(input: String) -> Result(BitArray, Nil)
/// Converts a bit array to a string containing the decimal value of each byte.
///
/// Use this over `string.inspect` when you have a bit array you want printed
/// in the array syntax even if it is valid UTF-8.
///
/// ## Examples
///
/// ```gleam
/// inspect(<<0, 20, 0x20, 255>>)
/// // -> "<<0, 20, 32, 255>>"
///
/// inspect(<<100, 5:3>>)
/// // -> "<<100, 5:size(3)>>"
/// ```
///
pub fn inspect(input: BitArray) -> String {
inspect_loop(input, "<<") <> ">>"
}
fn inspect_loop(input: BitArray, accumulator: String) -> String {
case input {
<<>> -> accumulator
<<x:size(1)>> -> accumulator <> int.to_string(x) <> ":size(1)"
<<x:size(2)>> -> accumulator <> int.to_string(x) <> ":size(2)"
<<x:size(3)>> -> accumulator <> int.to_string(x) <> ":size(3)"
<<x:size(4)>> -> accumulator <> int.to_string(x) <> ":size(4)"
<<x:size(5)>> -> accumulator <> int.to_string(x) <> ":size(5)"
<<x:size(6)>> -> accumulator <> int.to_string(x) <> ":size(6)"
<<x:size(7)>> -> accumulator <> int.to_string(x) <> ":size(7)"
<<x, rest:bits>> -> {
let suffix = case rest {
<<>> -> ""
_ -> ", "
}
let accumulator = accumulator <> int.to_string(x) <> suffix
inspect_loop(rest, accumulator)
}
_ -> accumulator
}
}
/// Compare two bit arrays as sequences of bytes.
///
/// ## Examples
///
/// ```gleam
/// compare(<<1>>, <<2>>)
/// // -> Lt
///
/// compare(<<"AB":utf8>>, <<"AA":utf8>>)
/// // -> Gt
///
/// compare(<<1, 2:size(2)>>, with: <<1, 2:size(2)>>)
/// // -> Eq
/// ```
///
pub fn compare(a: BitArray, with b: BitArray) -> order.Order {
case a, b {
<<first_byte, first_rest:bits>>, <<second_byte, second_rest:bits>> ->
case first_byte, second_byte {
f, s if f > s -> order.Gt
f, s if f < s -> order.Lt
_, _ -> compare(first_rest, second_rest)
}
<<>>, <<>> -> order.Eq
// First has more items, example: "AB" > "A":
_, <<>> -> order.Gt
// Second has more items, example: "A" < "AB":
<<>>, _ -> order.Lt
// This happens when there's unusually sized elements.
// Handle these special cases via custom erlang function.
first, second ->
case bit_array_to_int_and_size(first), bit_array_to_int_and_size(second) {
#(a, _), #(b, _) if a > b -> order.Gt
#(a, _), #(b, _) if a < b -> order.Lt
#(_, size_a), #(_, size_b) if size_a > size_b -> order.Gt
#(_, size_a), #(_, size_b) if size_a < size_b -> order.Lt
_, _ -> order.Eq
}
}
}
@external(erlang, "gleam_stdlib", "bit_array_to_int_and_size")
@external(javascript, "../gleam_stdlib.mjs", "bit_array_to_int_and_size")
fn bit_array_to_int_and_size(a: BitArray) -> #(Int, Int)
/// Checks whether the first `BitArray` starts with the second one.
///
/// ## Examples
///
/// ```gleam
/// starts_with(<<1, 2, 3, 4>>, <<1, 2>>)
/// // -> True
/// ```
///
@external(javascript, "../gleam_stdlib.mjs", "bit_array_starts_with")
pub fn starts_with(bits: BitArray, prefix: BitArray) -> Bool {
let prefix_size = bit_size(prefix)
case bits {
<<pref:bits-size(prefix_size), _:bits>> if pref == prefix -> True
_ -> False
}
}

View file

@ -0,0 +1,316 @@
//// 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`.
/// 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 {
!bool
}
/// 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 {
!{ a || b }
}
/// 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 {
!{ a && b }
}
/// 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 {
a != b
}
/// 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 {
a == b
}
/// 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 nor side-effects 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: a,
otherwise alternative: fn() -> a,
) -> a {
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(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()
}
}

View file

@ -0,0 +1,190 @@
//// `BytesTree` is a type used for efficiently building binary content to be
//// written to a file or a socket. Internally it is represented as tree so to
//// append or prepend to a bytes tree is a constant time operation that
//// allocates a new node in the tree without copying any of the content. When
//// writing to an output stream the tree is traversed and the content is sent
//// directly rather than copying it into a single buffer beforehand.
////
//// 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 data but copying can be expensive,
//// especially if we want to join many bit arrays together.
////
//// BytesTree 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 trees 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.
import gleam/bit_array
import gleam/list
import gleam/string_tree.{type StringTree}
pub opaque type BytesTree {
Bytes(BitArray)
Text(StringTree)
Many(List(BytesTree))
}
/// Create an empty `BytesTree`. Useful as the start of a pipe chaining many
/// trees together.
///
pub fn new() -> BytesTree {
concat([])
}
/// Prepends a bit array to the start of a bytes tree.
///
/// Runs in constant time.
///
pub fn prepend(to second: BytesTree, prefix first: BitArray) -> BytesTree {
append_tree(from_bit_array(first), second)
}
/// Appends a bit array to the end of a bytes tree.
///
/// Runs in constant time.
///
pub fn append(to first: BytesTree, suffix second: BitArray) -> BytesTree {
append_tree(first, from_bit_array(second))
}
/// Prepends a bytes tree onto the start of another.
///
/// Runs in constant time.
///
pub fn prepend_tree(to second: BytesTree, prefix first: BytesTree) -> BytesTree {
append_tree(first, second)
}
/// Appends a bytes tree onto the end of another.
///
/// Runs in constant time.
///
@external(erlang, "gleam_stdlib", "iodata_append")
pub fn append_tree(to first: BytesTree, suffix second: BytesTree) -> BytesTree {
case second {
Many(trees) -> Many([first, ..trees])
Text(_) | Bytes(_) -> Many([first, second])
}
}
/// Prepends a string onto the start of a bytes tree.
///
/// 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: BytesTree, prefix first: String) -> BytesTree {
append_tree(from_string(first), second)
}
/// Appends a string onto the end of a bytes tree.
///
/// 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: BytesTree, suffix second: String) -> BytesTree {
append_tree(first, from_string(second))
}
/// Joins a list of bytes trees into a single one.
///
/// Runs in constant time.
///
@external(erlang, "gleam_stdlib", "identity")
pub fn concat(trees: List(BytesTree)) -> BytesTree {
Many(trees)
}
/// Joins a list of bit arrays into a single bytes tree.
///
/// Runs in constant time.
///
pub fn concat_bit_arrays(bits: List(BitArray)) -> BytesTree {
bits
|> list.map(from_bit_array)
|> concat()
}
/// Creates a new bytes tree 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) -> BytesTree {
Text(string_tree.from_string(string))
}
/// Creates a new bytes tree from a string tree.
///
/// Runs in constant time when running on Erlang.
/// Runs in linear time otherwise.
///
@external(erlang, "gleam_stdlib", "wrap_list")
pub fn from_string_tree(tree: string_tree.StringTree) -> BytesTree {
Text(tree)
}
/// Creates a new bytes tree from a bit array.
///
/// Runs in constant time.
///
pub fn from_bit_array(bits: BitArray) -> BytesTree {
bits
|> bit_array.pad_to_bytes
|> wrap_list
}
@external(erlang, "gleam_stdlib", "wrap_list")
fn wrap_list(bits: BitArray) -> BytesTree {
Bytes(bits)
}
/// Turns a bytes tree 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(tree: BytesTree) -> BitArray {
[[tree]]
|> to_list([])
|> list.reverse
|> bit_array.concat
}
fn to_list(stack: List(List(BytesTree)), 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(tree), ..rest], ..remaining_stack] -> {
let bits = bit_array.from_string(string_tree.to_string(tree))
to_list([rest, ..remaining_stack], [bits, ..acc])
}
[[Many(trees), ..rest], ..remaining_stack] ->
to_list([trees, rest, ..remaining_stack], acc)
}
}
/// Returns the size of the bytes tree's content in bytes.
///
/// Runs in linear time.
///
@external(erlang, "erlang", "iolist_size")
pub fn byte_size(tree: BytesTree) -> Int {
[[tree]]
|> to_list([])
|> list.fold(0, fn(acc, bits) { bit_array.byte_size(bits) + acc })
}

View file

@ -0,0 +1,548 @@
import gleam/option.{type Option}
/// A dictionary of keys and values.
///
/// Any type can be used for the keys and values of a dict, but all the keys
/// must be of the same type and all the values must be of the same type.
///
/// Each key can only be present in a dict once.
///
/// Dicts are not ordered in any way, and any unintentional ordering is not to
/// be relied upon in your code as it may change in future versions of Erlang
/// or Gleam.
///
/// See [the Erlang map module](https://erlang.org/doc/man/maps.html) for more
/// information.
///
pub type Dict(key, value)
/// Determines the number of key-value pairs in the dict.
/// This function runs in constant time and does not need to iterate the dict.
///
/// ## Examples
///
/// ```gleam
/// new() |> size
/// // -> 0
/// ```
///
/// ```gleam
/// new() |> insert("key", "value") |> size
/// // -> 1
/// ```
///
@external(erlang, "maps", "size")
@external(javascript, "../gleam_stdlib.mjs", "map_size")
pub fn size(dict: Dict(k, v)) -> Int
/// Determines whether or not the dict is empty.
///
/// ## Examples
///
/// ```gleam
/// new() |> is_empty
/// // -> True
/// ```
///
/// ```gleam
/// new() |> insert("b", 1) |> is_empty
/// // -> False
/// ```
///
pub fn is_empty(dict: Dict(k, v)) -> Bool {
size(dict) == 0
}
/// 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
///
/// Calling `to_list` on an empty `dict` returns an empty list.
///
/// ```gleam
/// new() |> to_list
/// // -> []
/// ```
///
/// The ordering of elements in the resulting list is an implementation detail
/// that should not be relied upon.
///
/// ```gleam
/// new() |> insert("b", 1) |> insert("a", 0) |> insert("c", 2) |> to_list
/// // -> [#("a", 0), #("b", 1), #("c", 2)]
/// ```
///
@external(erlang, "maps", "to_list")
@external(javascript, "../gleam_stdlib.mjs", "map_to_list")
pub fn to_list(dict: Dict(k, v)) -> List(#(k, v))
/// Converts a list of 2-element tuples `#(key, value)` to a dict.
///
/// If two tuples have the same key the last one in the list will be the one
/// that is present in the dict.
///
@external(erlang, "maps", "from_list")
pub fn from_list(list: List(#(k, v))) -> Dict(k, v) {
from_list_loop(list, new())
}
fn from_list_loop(
over list: List(#(k, v)),
from initial: Dict(k, v),
) -> Dict(k, v) {
case list {
[] -> initial
[#(key, value), ..rest] -> from_list_loop(rest, insert(initial, key, value))
}
}
/// Determines whether or not a value present in the dict for a given key.
///
/// ## Examples
///
/// ```gleam
/// new() |> insert("a", 0) |> has_key("a")
/// // -> True
/// ```
///
/// ```gleam
/// new() |> insert("a", 0) |> has_key("b")
/// // -> False
/// ```
///
pub fn has_key(dict: Dict(k, v), key: k) -> Bool {
do_has_key(key, dict)
}
@external(erlang, "maps", "is_key")
fn do_has_key(key: k, dict: Dict(k, v)) -> Bool {
get(dict, key) != Error(Nil)
}
/// Creates a fresh dict that contains no values.
///
@external(erlang, "maps", "new")
@external(javascript, "../gleam_stdlib.mjs", "new_map")
pub fn new() -> Dict(k, v)
/// 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)
/// ```
///
@external(erlang, "gleam_stdlib", "map_get")
@external(javascript, "../gleam_stdlib.mjs", "map_get")
pub fn get(from: Dict(k, v), get: k) -> Result(v, 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)
/// // -> from_list([#("a", 0)])
/// ```
///
/// ```gleam
/// new() |> insert("a", 0) |> insert("a", 5)
/// // -> from_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(key: k, value: v, dict: Dict(k, v)) -> Dict(k, v)
/// Updates all values in a given dict by calling a given function on each key
/// and value.
///
/// ## Examples
///
/// ```gleam
/// from_list([#(3, 3), #(2, 4)])
/// |> map_values(fn(key, value) { key * value })
/// // -> from_list([#(3, 9), #(2, 8)])
/// ```
///
pub fn map_values(in dict: Dict(k, v), with fun: fn(k, v) -> a) -> Dict(k, a) {
do_map_values(fun, dict)
}
@external(erlang, "maps", "map")
fn do_map_values(f: fn(k, v) -> a, dict: Dict(k, v)) -> Dict(k, a) {
let f = fn(dict, k, v) { insert(dict, k, f(k, v)) }
fold(dict, 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
/// from_list([#("a", 0), #("b", 1)]) |> keys
/// // -> ["a", "b"]
/// ```
///
@external(erlang, "maps", "keys")
pub fn keys(dict: Dict(k, v)) -> List(k) {
do_keys_loop(to_list(dict), [])
}
fn do_keys_loop(list: List(#(k, v)), acc: List(k)) -> List(k) {
case list {
[] -> reverse_and_concat(acc, [])
[#(key, _value), ..rest] -> do_keys_loop(rest, [key, ..acc])
}
}
fn reverse_and_concat(remaining: List(a), accumulator: List(a)) -> List(a) {
case remaining {
[] -> accumulator
[first, ..rest] -> reverse_and_concat(rest, [first, ..accumulator])
}
}
/// 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
/// from_list([#("a", 0), #("b", 1)]) |> values
/// // -> [0, 1]
/// ```
///
@external(erlang, "maps", "values")
pub fn values(dict: Dict(k, v)) -> List(v) {
let list_of_pairs = to_list(dict)
do_values_loop(list_of_pairs, [])
}
fn do_values_loop(list: List(#(k, v)), acc: List(v)) -> List(v) {
case list {
[] -> reverse_and_concat(acc, [])
[#(_key, value), ..rest] -> do_values_loop(rest, [value, ..acc])
}
}
/// Creates a new dict from a given dict, minus any entries that a given function
/// returns `False` for.
///
/// ## Examples
///
/// ```gleam
/// from_list([#("a", 0), #("b", 1)])
/// |> filter(fn(key, value) { value != 0 })
/// // -> from_list([#("b", 1)])
/// ```
///
/// ```gleam
/// from_list([#("a", 0), #("b", 1)])
/// |> filter(fn(key, value) { True })
/// // -> from_list([#("a", 0), #("b", 1)])
/// ```
///
pub fn filter(
in dict: Dict(k, v),
keeping predicate: fn(k, v) -> Bool,
) -> Dict(k, v) {
do_filter(predicate, dict)
}
@external(erlang, "maps", "filter")
fn do_filter(f: fn(k, v) -> Bool, dict: Dict(k, v)) -> Dict(k, v) {
let insert = fn(dict, k, v) {
case f(k, v) {
True -> insert(dict, k, v)
False -> dict
}
}
fold(dict, from: new(), with: insert)
}
/// Creates a new dict from a given dict, only including any entries for which the
/// keys are in a given list.
///
/// ## Examples
///
/// ```gleam
/// from_list([#("a", 0), #("b", 1)])
/// |> take(["b"])
/// // -> from_list([#("b", 1)])
/// ```
///
/// ```gleam
/// from_list([#("a", 0), #("b", 1)])
/// |> take(["a", "b", "c"])
/// // -> from_list([#("a", 0), #("b", 1)])
/// ```
///
pub fn take(from dict: Dict(k, v), keeping desired_keys: List(k)) -> Dict(k, v) {
do_take(desired_keys, dict)
}
@external(erlang, "maps", "with")
fn do_take(desired_keys: List(k), dict: Dict(k, v)) -> Dict(k, v) {
do_take_loop(dict, desired_keys, new())
}
fn do_take_loop(
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)
Error(_) -> taken
}
}
case desired_keys {
[] -> acc
[first, ..rest] -> do_take_loop(dict, rest, insert(acc, first))
}
}
/// 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)])
/// ```
///
@external(erlang, "maps", "merge")
pub fn merge(into dict: Dict(k, v), from new_entries: Dict(k, v)) -> Dict(k, v) {
new_entries
|> to_list
|> fold_inserts(dict)
}
fn fold_inserts(new_entries: List(#(k, v)), dict: Dict(k, v)) -> Dict(k, v) {
case new_entries {
[] -> dict
[first, ..rest] -> fold_inserts(rest, insert_pair(dict, first))
}
}
fn insert_pair(dict: Dict(k, v), pair: #(k, v)) -> Dict(k, v) {
insert(dict, pair.0, pair.1)
}
/// 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
/// from_list([#("a", 0), #("b", 1)]) |> delete("a")
/// // -> from_list([#("b", 1)])
/// ```
///
/// ```gleam
/// from_list([#("a", 0), #("b", 1)]) |> delete("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
/// from_list([#("a", 0), #("b", 1)]) |> drop(["a"])
/// // -> from_list([#("b", 1)])
/// ```
///
/// ```gleam
/// from_list([#("a", 0), #("b", 1)]) |> drop(["c"])
/// // -> from_list([#("a", 0), #("b", 1)])
/// ```
///
/// ```gleam
/// from_list([#("a", 0), #("b", 1)]) |> drop(["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
[first, ..rest] -> drop(delete(dict, first), rest)
}
}
/// Creates a new dict with one entry inserted or 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 dict = from_list([#("a", 0)])
/// let increment = fn(x) {
/// case x {
/// Some(i) -> i + 1
/// None -> 0
/// }
/// }
///
/// upsert(dict, "a", increment)
/// // -> from_list([#("a", 1)])
///
/// upsert(dict, "b", increment)
/// // -> from_list([#("a", 0), #("b", 0)])
/// ```
///
pub fn upsert(
in dict: Dict(k, v),
update key: k,
with fun: fn(Option(v)) -> v,
) -> Dict(k, v) {
case get(dict, key) {
Ok(value) -> insert(dict, key, fun(option.Some(value)))
Error(_) -> insert(dict, key, fun(option.None))
}
}
/// 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
///
/// let dict = from_list([#("a", 1), #("b", 3), #("c", 9)])
/// fold(dict, "", fn(accumulator, key, value) {
/// string.append(accumulator, key)
/// })
/// // -> "abc"
/// ```
///
pub fn fold(
over dict: Dict(k, v),
from initial: acc,
with fun: fn(acc, k, v) -> acc,
) -> acc {
fold_loop(to_list(dict), initial, fun)
}
fn fold_loop(
list: List(#(k, v)),
initial: acc,
fun: fn(acc, k, v) -> acc,
) -> acc {
case list {
[] -> initial
[#(k, v), ..rest] -> fold_loop(rest, fun(initial, k, v), fun)
}
}
/// Calls a function for each key and value in a dict, discarding the return
/// value.
///
/// Useful for producing a side effect for every item of a dict.
///
/// ```gleam
/// import gleam/io
///
/// let dict = from_list([#("a", "apple"), #("b", "banana"), #("c", "cherry")])
///
/// each(dict, fn(k, v) {
/// io.println(key <> " => " <> value)
/// })
/// // -> Nil
/// // a => apple
/// // b => banana
/// // c => cherry
/// ```
///
/// The order of elements in the iteration is an implementation detail that
/// should not be relied upon.
///
pub fn each(dict: Dict(k, v), fun: fn(k, v) -> a) -> Nil {
fold(dict, Nil, fn(nil, k, v) {
fun(k, v)
nil
})
}
/// 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 given function is
/// used to determine the new value to use in the resulting dict.
///
/// ## Examples
///
/// ```gleam
/// let a = from_list([#("a", 0), #("b", 1)])
/// let b = from_list([#("a", 2), #("c", 3)])
/// combine(a, b, fn(one, other) { one + other })
/// // -> from_list([#("a", 2), #("b", 1), #("c", 3)])
/// ```
///
pub fn combine(
dict: Dict(k, v),
other: Dict(k, v),
with fun: fn(v, v) -> v,
) -> Dict(k, v) {
use acc, key, value <- fold(over: dict, from: other)
case get(acc, key) {
Ok(other_value) -> insert(acc, key, fun(value, other_value))
Error(_) -> insert(acc, key, value)
}
}

View file

@ -0,0 +1,100 @@
import gleam/dict
/// `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.
///
/// This module contains code for forming dynamic data, and the
/// `gleam/dynamic/decode` module contains code for turning dynamic data back
/// into Gleam data with known types. You will likely mostly use the other
/// module in your projects.
///
/// The exact runtime representation of dynamic values will depend on the
/// compilation target used.
///
pub type Dynamic
/// Return a string indicating the type of the dynamic value.
///
/// This function may be useful for constructing error messages or logs. If you
/// want to turn dynamic data into well typed data then you want the
/// `gleam/dynamic/decode` module.
///
/// ```gleam
/// classify(string("Hello"))
/// // -> "String"
/// ```
///
@external(erlang, "gleam_stdlib", "classify_dynamic")
@external(javascript, "../gleam_stdlib.mjs", "classify_dynamic")
pub fn classify(data: Dynamic) -> String
/// Create a dynamic value from a bool.
///
@external(erlang, "gleam_stdlib", "identity")
@external(javascript, "../gleam_stdlib.mjs", "identity")
pub fn bool(a: Bool) -> Dynamic
/// Create a dynamic value from a string.
///
/// On Erlang this will be a binary string rather than a character list.
///
@external(erlang, "gleam_stdlib", "identity")
@external(javascript, "../gleam_stdlib.mjs", "identity")
pub fn string(a: String) -> Dynamic
/// Create a dynamic value from a float.
///
@external(erlang, "gleam_stdlib", "identity")
@external(javascript, "../gleam_stdlib.mjs", "identity")
pub fn float(a: Float) -> Dynamic
/// Create a dynamic value from an int.
///
@external(erlang, "gleam_stdlib", "identity")
@external(javascript, "../gleam_stdlib.mjs", "identity")
pub fn int(a: Int) -> Dynamic
/// Create a dynamic value from a bit array.
///
@external(erlang, "gleam_stdlib", "identity")
@external(javascript, "../gleam_stdlib.mjs", "identity")
pub fn bit_array(a: BitArray) -> Dynamic
/// Create a dynamic value from a list.
///
@external(erlang, "gleam_stdlib", "identity")
@external(javascript, "../gleam_stdlib.mjs", "identity")
pub fn list(a: List(Dynamic)) -> Dynamic
/// Create a dynamic value from a list, converting it to a sequential runtime
/// format rather than the regular list format.
///
/// On Erlang this will be a tuple, on JavaScript this will be an array.
///
@external(erlang, "erlang", "list_to_tuple")
@external(javascript, "../gleam_stdlib.mjs", "list_to_array")
pub fn array(a: List(Dynamic)) -> Dynamic
/// Create a dynamic value made an unordered series of keys and values, where
/// the keys are unique.
///
/// On Erlang this will be a map, on JavaScript this will be a Gleam dict
/// object.
///
pub fn properties(entries: List(#(Dynamic, Dynamic))) -> Dynamic {
cast(dict.from_list(entries))
}
/// A dynamic value representing nothing.
///
/// On Erlang this will be the atom `nil`, on JavaScript this will be
/// `undefined`.
///
pub fn nil() -> Dynamic {
cast(Nil)
}
@external(erlang, "gleam_stdlib", "identity")
@external(javascript, "../gleam_stdlib.mjs", "identity")
fn cast(a: anything) -> Dynamic

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,661 @@
//// Functions for working with floats.
////
//// ## Float representation
////
//// Floats are represented as 64 bit floating point numbers on both the Erlang
//// and JavaScript runtimes. The floating point behaviour is native to their
//// respective runtimes, so their exact behaviour will be slightly different on
//// the two runtimes.
////
//// ### Infinity and NaN
////
//// Under the JavaScript runtime, exceeding the maximum (or minimum)
//// representable value for a floating point value will result in Infinity (or
//// -Infinity). Should you try to divide two infinities you will get NaN as a
//// result.
////
//// When running on BEAM, exceeding the maximum (or minimum) representable
//// value for a floating point value will raise an error.
////
//// ## 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)
/// ```
///
@external(erlang, "gleam_stdlib", "parse_float")
@external(javascript, "../gleam_stdlib.mjs", "parse_float")
pub fn parse(string: String) -> Result(Float, Nil)
/// Returns the string representation of the provided `Float`.
///
/// ## Examples
///
/// ```gleam
/// to_string(2.3)
/// // -> "2.3"
/// ```
///
@external(erlang, "gleam_stdlib", "float_to_string")
@external(javascript, "../gleam_stdlib.mjs", "float_to_string")
pub fn to_string(x: 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
/// ```
///
@external(erlang, "math", "ceil")
@external(javascript, "../gleam_stdlib.mjs", "ceiling")
pub fn ceiling(x: Float) -> Float
/// Rounds the value to the next lowest whole number as a `Float`.
///
/// ## Examples
///
/// ```gleam
/// floor(2.3)
/// // -> 2.0
/// ```
///
@external(erlang, "math", "floor")
@external(javascript, "../gleam_stdlib.mjs", "floor")
pub fn floor(x: Float) -> Float
/// Rounds the value to the nearest whole number as an `Int`.
///
/// ## Examples
///
/// ```gleam
/// round(2.3)
/// // -> 2
/// ```
///
/// ```gleam
/// round(2.5)
/// // -> 3
/// ```
///
@external(erlang, "erlang", "round")
pub fn round(x: Float) -> Int {
case x >=. 0.0 {
True -> js_round(x)
False -> 0 - js_round(negate(x))
}
}
@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
/// ```
///
@external(erlang, "erlang", "trunc")
@external(javascript, "../gleam_stdlib.mjs", "truncate")
pub fn truncate(x: Float) -> Int
/// Converts the value to a given precision as a `Float`.
/// The precision is the number of allowed decimal places.
/// Negative precisions are allowed and force rounding
/// to the nearest tenth, hundredth, thousandth etc.
///
/// ## Examples
///
/// ```gleam
/// to_precision(2.43434348473, precision: 2)
/// // -> 2.43
/// ```
///
/// ```gleam
/// to_precision(547890.453444, precision: -3)
/// // -> 548000.0
/// ```
///
pub fn to_precision(x: Float, precision: Int) -> Float {
case precision <= 0 {
True -> {
let factor = do_power(10.0, do_to_float(-precision))
do_to_float(round(x /. factor)) *. factor
}
False -> {
let factor = do_power(10.0, do_to_float(precision))
do_to_float(round(x *. factor)) /. factor
}
}
}
@external(erlang, "erlang", "float")
@external(javascript, "../gleam_stdlib.mjs", "identity")
fn do_to_float(a: Int) -> Float
/// 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
False -> 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 {
sum_loop(numbers, 0.0)
}
fn sum_loop(numbers: List(Float), initial: Float) -> Float {
case numbers {
[first, ..rest] -> sum_loop(rest, first +. initial)
[] -> 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 {
product_loop(numbers, 1.0)
}
fn product_loop(numbers: List(Float), initial: Float) -> Float {
case numbers {
[first, ..rest] -> product_loop(rest, first *. initial)
[] -> initial
}
}
/// Generates a random float between the given zero (inclusive) and one
/// (exclusive).
///
/// On Erlang this updates the random state in the process dictionary.
/// See: <https://www.erlang.org/doc/man/rand.html#uniform-0>
///
/// ## Examples
///
/// ```gleam
/// random()
/// // -> 0.646355926896028
/// ```
///
@external(erlang, "rand", "uniform")
@external(javascript, "../gleam_stdlib.mjs", "random_uniform")
pub fn random() -> Float
/// Computes the modulo of an float 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`.
///
/// ## Examples
///
/// ```gleam
/// modulo(13.3, by: 3.3)
/// // -> Ok(0.1)
/// ```
///
/// ```gleam
/// modulo(-13.3, by: 3.3)
/// // -> Ok(3.2)
/// ```
///
/// ```gleam
/// modulo(13.3, by: -3.3)
/// // -> Ok(-3.2)
/// ```
///
/// ```gleam
/// modulo(-13.3, by: -3.3)
/// // -> Ok(-0.1)
/// ```
///
pub fn modulo(dividend: Float, by divisor: Float) -> Result(Float, Nil) {
case divisor {
0.0 -> Error(Nil)
_ -> Ok(dividend -. floor(dividend /. divisor) *. divisor)
}
}
/// Returns division of the inputs as a `Result`.
///
/// ## Examples
///
/// ```gleam
/// divide(0.0, 1.0)
/// // -> Ok(0.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
}
/// Returns the natural logarithm (base e) of the given as a `Result`. If the
/// input is less than or equal to 0, returns `Error(Nil)`.
///
/// ## Examples
///
/// ```gleam
/// logarithm(1.0)
/// // -> Ok(0.0)
/// ```
///
/// ```gleam
/// logarithm(2.718281828459045) // e
/// // -> Ok(1.0)
/// ```
///
/// ```gleam
/// logarithm(0.0)
/// // -> Error(Nil)
/// ```
///
/// ```gleam
/// logarithm(-1.0)
/// // -> Error(Nil)
/// ```
///
pub fn logarithm(x: Float) -> Result(Float, Nil) {
// In the following check:
// 1. If x is negative then return an error as the natural logarithm
// of a negative number is undefined (would be a complex number)
// 2. If x is 0 then return an error as the natural logarithm of 0
// approaches negative infinity
case x <=. 0.0 {
True -> Error(Nil)
False -> Ok(do_log(x))
}
}
@external(erlang, "math", "log")
@external(javascript, "../gleam_stdlib.mjs", "log")
fn do_log(x: Float) -> Float
/// Returns e (Euler's number) raised to the power of the given exponent, as
/// a `Float`.
///
/// ## Examples
///
/// ```gleam
/// exponential(0.0)
/// // -> Ok(1.0)
/// ```
///
/// ```gleam
/// exponential(1.0)
/// // -> Ok(2.718281828459045)
/// ```
///
/// ```gleam
/// exponential(-1.0)
/// // -> Ok(0.36787944117144233)
/// ```
///
@external(erlang, "math", "exp")
@external(javascript, "../gleam_stdlib.mjs", "exp")
pub fn exponential(x: Float) -> Float

View file

@ -0,0 +1,15 @@
/// Takes a single argument and always returns its input value.
///
pub fn identity(x: a) -> a {
x
}
/// 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
}

View file

@ -0,0 +1,828 @@
//// 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)
/// ```
///
@external(erlang, "gleam_stdlib", "parse_int")
@external(javascript, "../gleam_stdlib.mjs", "parse_int")
pub fn parse(string: 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)
/// ```
///
/// ```gleam
/// base_parse("30", 16)
/// // -> Ok(48)
/// ```
///
/// ```gleam
/// base_parse("1C", 36)
/// // -> Ok(48)
/// ```
///
/// ```gleam
/// base_parse("48", 1)
/// // -> Error(Nil)
/// ```
///
/// ```gleam
/// 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"
/// ```
///
@external(erlang, "erlang", "integer_to_binary")
@external(javascript, "../gleam_stdlib.mjs", "to_string")
pub fn to_string(x: Int) -> String
/// 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(Nil)`.
/// 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(Nil)
/// ```
///
/// ```gleam
/// to_base_string(48, 37)
/// // -> Error(Nil)
/// ```
///
pub fn to_base_string(x: Int, base: Int) -> Result(String, Nil) {
case base >= 2 && base <= 36 {
True -> Ok(do_to_base_string(x, base))
False -> Error(Nil)
}
}
@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
/// ```
///
@external(erlang, "erlang", "float")
@external(javascript, "../gleam_stdlib.mjs", "identity")
pub fn to_float(x: 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 {
sum_loop(numbers, 0)
}
fn sum_loop(numbers: List(Int), initial: Int) -> Int {
case numbers {
[first, ..rest] -> sum_loop(rest, first + initial)
[] -> initial
}
}
/// Multiplies a list of ints and returns the product.
///
/// ## Example
///
/// ```gleam
/// product([2, 3, 4])
/// // -> 24
/// ```
///
pub fn product(numbers: List(Int)) -> Int {
product_loop(numbers, 1)
}
fn product_loop(numbers: List(Int), initial: Int) -> Int {
case numbers {
[first, ..rest] -> product_loop(rest, first * initial)
[] -> initial
}
}
@deprecated("Vendor this function into your codebase")
pub fn digits(x: Int, base: Int) -> Result(List(Int), Nil) {
case base < 2 {
True -> Error(Nil)
False -> Ok(digits_loop(x, base, []))
}
}
fn digits_loop(x: Int, base: Int, acc: List(Int)) -> List(Int) {
case absolute_value(x) < base {
True -> [x, ..acc]
False -> digits_loop(x / base, base, [x % base, ..acc])
}
}
@deprecated("Vendor this function into your codebase")
pub fn undigits(numbers: List(Int), base: Int) -> Result(Int, Nil) {
case base < 2 {
True -> Error(Nil)
False -> undigits_loop(numbers, base, 0)
}
}
fn undigits_loop(numbers: List(Int), base: Int, acc: Int) -> Result(Int, Nil) {
case numbers {
[] -> Ok(acc)
[digit, ..] if digit >= base -> Error(Nil)
[digit, ..rest] -> undigits_loop(rest, base, acc * base + digit)
}
}
/// Generates a random int between zero and the given maximum.
///
/// The lower number is inclusive, the upper number is exclusive.
///
/// ## Examples
///
/// ```gleam
/// random(10)
/// // -> 4
/// ```
///
/// ```gleam
/// random(1)
/// // -> 0
/// ```
///
/// ```gleam
/// random(-1)
/// // -> -1
/// ```
///
pub fn random(max: Int) -> Int {
{ float.random() *. to_float(max) }
|> float.floor
|> float.round
}
/// Performs a truncated integer division.
///
/// Returns division of the inputs as a `Result`: If the given divisor equals
/// `0`, this function returns an `Error`.
///
/// ## Examples
///
/// ```gleam
/// divide(0, 1)
/// // -> Ok(0)
/// ```
///
/// ```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)
/// ```
///
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
/// ```
///
/// ```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

View file

@ -0,0 +1,59 @@
/// Writes a string to standard output (stdout).
///
/// If you want your output to be printed on its own line see `println`.
///
/// ## Example
///
/// ```gleam
/// io.print("Hi mum")
/// // -> Nil
/// // Hi mum
/// ```
///
@external(erlang, "gleam_stdlib", "print")
@external(javascript, "../gleam_stdlib.mjs", "print")
pub fn print(string: String) -> Nil
/// Writes a string to standard error (stderr).
///
/// If you want your output to be printed on its own line see `println_error`.
///
/// ## Example
///
/// ```
/// io.print_error("Hi pop")
/// // -> Nil
/// // Hi pop
/// ```
///
@external(erlang, "gleam_stdlib", "print_error")
@external(javascript, "../gleam_stdlib.mjs", "print_error")
pub fn print_error(string: String) -> Nil
/// Writes a string to standard output (stdout), appending a newline to the end.
///
/// ## Example
///
/// ```gleam
/// io.println("Hi mum")
/// // -> Nil
/// // Hi mum
/// ```
///
@external(erlang, "gleam_stdlib", "println")
@external(javascript, "../gleam_stdlib.mjs", "console_log")
pub fn println(string: String) -> Nil
/// Writes a string to standard error (stderr), appending a newline to the end.
///
/// ## Example
///
/// ```gleam
/// io.println_error("Hi pop")
/// // -> Nil
/// // Hi pop
/// ```
///
@external(erlang, "gleam_stdlib", "println_error")
@external(javascript, "../gleam_stdlib.mjs", "console_error")
pub fn println_error(string: String) -> Nil

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,358 @@
/// `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.
///
/// ## `Option` and `Result`
///
/// In other languages fallible functions may return either `Result` or
/// `Option` depending on whether there is more information to be given about the
/// failure. In Gleam all fallible functions return `Result`, and `Nil` is used
/// as the error if there is no extra detail to give. This consistency removes
/// the boilerplate that would otherwise be needed to convert between `Option`
/// and `Result` types, and makes APIs more predictable.
///
/// The `Option` type should only be used for taking optional values as
/// function arguments, or for storing them in other data structures.
///
pub type Option(a) {
Some(a)
None
}
/// 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)) {
all_loop(list, [])
}
fn all_loop(list: List(Option(a)), acc: List(a)) -> Option(List(a)) {
case list {
[] -> Some(reverse(acc))
[None, ..] -> None
[Some(first), ..rest] -> all_loop(rest, [first, ..acc])
}
}
// This is copied from the list module and not imported as importing it would
// result in a circular dependency!
@external(erlang, "lists", "reverse")
fn reverse(list: List(a)) -> List(a) {
reverse_and_prepend(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])
}
}
/// 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)
None -> 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)
Error(_) -> 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()
}
}
/// 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) {
values_loop(options, [])
}
fn values_loop(list: List(Option(a)), acc: List(a)) -> List(a) {
case list {
[] -> reverse(acc)
[None, ..rest] -> values_loop(rest, acc)
[Some(first), ..rest] -> values_loop(rest, [first, ..acc])
}
}

View file

@ -0,0 +1,156 @@
/// 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(Gt)
/// // -> Lt
/// ```
///
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
}
}
/// Inverts an ordering function, so less-than becomes greater-than and greater-than
/// becomes less-than.
///
/// ## Examples
///
/// ```gleam
/// import gleam/int
/// import gleam/list
///
/// 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) }
}
/// Return a fallback `Order` in case the first argument is `Eq`.
///
/// ## Examples
///
/// ```gleam
/// import gleam/int
///
/// break_tie(in: int.compare(1, 1), with: Lt)
/// // -> Lt
/// ```
///
/// ```gleam
/// import gleam/int
///
/// break_tie(in: int.compare(1, 0), with: Eq)
/// // -> Gt
/// ```
///
pub fn break_tie(in order: Order, with other: Order) -> Order {
case order {
Lt | Gt -> order
Eq -> other
}
}
/// Invokes a fallback function returning an `Order` in case the first argument
/// is `Eq`.
///
/// This can be useful when the fallback comparison might be expensive and it
/// needs to be delayed until strictly necessary.
///
/// ## Examples
///
/// ```gleam
/// import gleam/int
///
/// lazy_break_tie(in: int.compare(1, 1), with: fn() { Lt })
/// // -> Lt
/// ```
///
/// ```gleam
/// import gleam/int
///
/// lazy_break_tie(in: int.compare(1, 0), with: fn() { Eq })
/// // -> Gt
/// ```
///
pub fn lazy_break_tie(in order: Order, with comparison: fn() -> Order) -> Order {
case order {
Lt | Gt -> order
Eq -> comparison()
}
}

View file

@ -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)
}

View file

@ -0,0 +1,453 @@
//// 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)
}
}
@deprecated("This function is an alias of result.try, use that instead")
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
}
}
@deprecated("Use a case expression instead of this function")
pub fn unwrap_both(result: Result(a, a)) -> a {
case result {
Ok(a) -> a
Error(a) -> a
}
}
/// 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.
///
/// If you need access to the initial error value, use `result.try_recover`.
///
/// ## 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(result) { result })
}
/// 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)) {
partition_loop(results, [], [])
}
fn partition_loop(results: List(Result(a, e)), oks: List(a), errors: List(e)) {
case results {
[] -> #(oks, errors)
[Ok(a), ..rest] -> partition_loop(rest, [a, ..oks], errors)
[Error(e), ..rest] -> partition_loop(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, e), error: f) -> Result(a, f) {
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(result) { result })
}
/// 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.
///
/// If you do not need access to the initial error value, use `result.lazy_or`.
///
/// ## 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)
}
}

View file

@ -0,0 +1,407 @@
import gleam/dict.{type Dict}
import gleam/list
import gleam/result
// A list is used as the dict 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/dict` module, so inserts and lookups have
/// logarithmic time complexity.
///
pub opaque type Set(member) {
Set(dict: 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.dict)
}
/// Determines whether or not the set is empty.
///
/// ## Examples
///
/// ```gleam
/// new() |> is_empty
/// // -> True
/// ```
///
/// ```gleam
/// new() |> insert(1) |> is_empty
/// // -> False
/// ```
///
pub fn is_empty(set: Set(member)) -> Bool {
set == new()
}
/// 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(dict: dict.insert(set.dict, 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.dict
|> 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(dict: dict.delete(set.dict, 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.dict)
}
/// Creates a new set of the members in a given list.
///
/// This function runs in loglinear time.
///
/// ## Examples
///
/// ```gleam
/// import gleam/int
/// import gleam/list
///
/// [1, 1, 2, 4, 3, 2] |> from_list |> to_list |> list.sort(by: int.compare)
/// // -> [1, 2, 3, 4]
/// ```
///
pub fn from_list(members: List(member)) -> Set(member) {
let dict =
list.fold(over: members, from: dict.new(), with: fn(m, k) {
dict.insert(m, k, token)
})
Set(dict)
}
/// 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(accumulator, member) { accumulator + member })
/// // -> 13
/// ```
///
pub fn fold(
over set: Set(member),
from initial: acc,
with reducer: fn(acc, member) -> acc,
) -> acc {
dict.fold(over: set.dict, 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(keeping: 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.dict, keeping: fn(m, _) { predicate(m) }))
}
/// Creates a new set from a given set with the result of applying the given
/// function to each member.
///
/// ## Examples
///
/// ```gleam
/// from_list([1, 2, 3, 4])
/// |> map(with: fn(x) { x * 2 })
/// |> to_list
/// // -> [2, 4, 6, 8]
/// ```
pub fn map(set: Set(member), with fun: fn(member) -> mapped) -> Set(mapped) {
fold(over: set, from: new(), with: fn(acc, member) {
insert(acc, fun(member))
})
}
/// Creates a new set from a given set with all the same entries except any
/// entry found on the given list.
///
/// ## Examples
///
/// ```gleam
/// from_list([1, 2, 3, 4])
/// |> drop([1, 3])
/// |> to_list
/// // -> [2, 4]
/// ```
pub fn drop(from set: Set(member), drop disallowed: List(member)) -> Set(member) {
list.fold(over: disallowed, from: set, with: delete)
}
/// Creates a new set from a given set, 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.dict, keeping: desired))
}
/// 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)
}
fn order(first: Set(member), second: Set(member)) -> #(Set(member), Set(member)) {
case dict.size(first.dict) > dict.size(second.dict) {
True -> #(first, second)
False -> #(second, first)
}
}
/// 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))
}
/// Creates a new set that contains members that are present in the first set
/// but not the second.
///
/// ## Examples
///
/// ```gleam
/// difference(from_list([1, 2]), from_list([2, 3, 4])) |> to_list
/// // -> [1]
/// ```
///
pub fn difference(
from first: Set(member),
minus second: Set(member),
) -> Set(member) {
drop(from: first, drop: to_list(second))
}
/// Determines if a set is fully contained by another.
///
/// ## Examples
///
/// ```gleam
/// is_subset(from_list([1]), from_list([1, 2]))
/// // -> True
/// ```
///
/// ```gleam
/// is_subset(from_list([1, 2, 3]), from_list([3, 4, 5]))
/// // -> False
/// ```
///
pub fn is_subset(first: Set(member), of second: Set(member)) -> Bool {
intersection(of: first, and: second) == first
}
/// Determines if two sets contain no common members
///
/// ## Examples
///
/// ```gleam
/// is_disjoint(from_list([1, 2, 3]), from_list([4, 5, 6]))
/// // -> True
/// ```
///
/// ```gleam
/// is_disjoint(from_list([1, 2, 3]), from_list([3, 4, 5]))
/// // -> False
/// ```
///
pub fn is_disjoint(first: Set(member), from second: Set(member)) -> Bool {
intersection(of: first, and: second) == new()
}
/// Creates a new set that contains members that are present in either set, but
/// not both.
///
/// ```gleam
/// symmetric_difference(from_list([1, 2, 3]), from_list([3, 4])) |> to_list
/// // -> [1, 2, 4]
/// ```
///
pub fn symmetric_difference(
of first: Set(member),
and second: Set(member),
) -> Set(member) {
difference(
from: union(of: first, and: second),
minus: intersection(of: first, and: second),
)
}
/// Calls a function for each member in a set, discarding the return
/// value.
///
/// Useful for producing a side effect for every item of a set.
///
/// ```gleam
/// let set = from_list(["apple", "banana", "cherry"])
///
/// each(set, io.println)
/// // -> Nil
/// // apple
/// // banana
/// // cherry
/// ```
///
/// The order of elements in the iteration is an implementation detail that
/// should not be relied upon.
///
pub fn each(set: Set(member), fun: fn(member) -> a) -> Nil {
fold(set, Nil, fn(nil, member) {
fun(member)
nil
})
}

View file

@ -0,0 +1,900 @@
//// Strings in Gleam are UTF-8 binaries. They can be written in your code as
//// text surrounded by `"double quotes"`.
import gleam/list
import gleam/option.{type Option, None, Some}
import gleam/order
import gleam/string_tree.{type StringTree}
/// 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. Avoid using this in a loop.
///
/// ## Examples
///
/// ```gleam
/// length("Gleam")
/// // -> 5
/// ```
///
/// ```gleam
/// length("ß↑e̊")
/// // -> 3
/// ```
///
/// ```gleam
/// length("")
/// // -> 0
/// ```
///
@external(erlang, "string", "length")
@external(javascript, "../gleam_stdlib.mjs", "string_length")
pub fn length(string: String) -> Int
/// Reverses a `String`.
///
/// This function has to iterate across the whole `String` so it runs in linear
/// time. Avoid using this in a loop.
///
/// ## Examples
///
/// ```gleam
/// reverse("stressed")
/// // -> "desserts"
/// ```
///
pub fn reverse(string: String) -> String {
string
|> string_tree.from_string
|> string_tree.reverse
|> string_tree.to_string
}
/// 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_tree.from_string
|> string_tree.replace(each: pattern, with: substitute)
|> string_tree.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"
/// ```
///
@external(erlang, "string", "lowercase")
@external(javascript, "../gleam_stdlib.mjs", "lowercase")
pub fn lowercase(string: 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"
/// ```
///
@external(erlang, "string", "uppercase")
@external(javascript, "../gleam_stdlib.mjs", "uppercase")
pub fn uppercase(string: 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
False -> 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.
///
/// This function runs in linear time with the size of the index and the
/// length. Negative indexes are linear with the size of the input string in
/// addition to the other costs.
///
/// ## 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 -> grapheme_slice(string, translated_idx, len)
}
}
False -> grapheme_slice(string, idx, len)
}
}
}
@external(erlang, "gleam_stdlib", "slice")
@external(javascript, "../gleam_stdlib.mjs", "string_grapheme_slice")
fn grapheme_slice(string: String, index: Int, length: Int) -> String
@external(erlang, "binary", "part")
@external(javascript, "../gleam_stdlib.mjs", "string_byte_slice")
fn unsafe_byte_slice(string: String, index: Int, length: Int) -> String
/// 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 start of a `String`.
///
/// This function runs in linear time with the number of graphemes to drop.
///
/// ## Examples
///
/// ```gleam
/// drop_start(from: "The Lone Gunmen", up_to: 2)
/// // -> "e Lone Gunmen"
/// ```
///
pub fn drop_start(from string: String, up_to num_graphemes: Int) -> String {
case num_graphemes <= 0 {
True -> string
False -> {
let prefix = grapheme_slice(string, 0, num_graphemes)
let prefix_size = byte_size(prefix)
unsafe_byte_slice(string, prefix_size, byte_size(string) - prefix_size)
}
}
}
/// Drops *n* graphemes from the end of a `String`.
///
/// This function traverses the full string, so it runs in linear time with the
/// size of the string. Avoid using this in a loop.
///
/// ## Examples
///
/// ```gleam
/// drop_end(from: "Cigarette Smoking Man", up_to: 2)
/// // -> "Cigarette Smoking M"
/// ```
///
pub fn drop_end(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
/// ```
///
@external(erlang, "gleam_stdlib", "string_starts_with")
@external(javascript, "../gleam_stdlib.mjs", "starts_with")
pub fn starts_with(string: String, prefix: String) -> Bool
/// Checks whether the first `String` ends with the second one.
///
/// ## Examples
///
/// ```gleam
/// ends_with("theory", "ory")
/// // -> True
/// ```
///
@external(erlang, "gleam_stdlib", "string_ends_with")
@external(javascript, "../gleam_stdlib.mjs", "ends_with")
pub fn ends_with(string: String, suffix: 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_tree.from_string
|> string_tree.split(on: substring)
|> list.map(with: string_tree.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)
/// ```
///
@external(javascript, "../gleam_stdlib.mjs", "split_once")
pub fn split_once(
string: String,
on substring: String,
) -> Result(#(String, String), Nil) {
case erl_split(string, substring) {
[first, rest] -> Ok(#(first, rest))
_ -> Error(Nil)
}
}
@external(erlang, "string", "split")
fn erl_split(a: String, b: String) -> List(String)
/// Creates a new `String` by joining two `String`s together.
///
/// This function typically copies both `String`s and runs in linear time, but
/// the exact behaviour will depend on how the runtime you are using optimises
/// your code. Benchmark and profile your code if you need to understand its
/// performance better.
///
/// If you are joining together large string and want to avoid copying any data
/// you may want to investigate using the [`string_tree`](../gleam/string_tree.html)
/// module.
///
/// ## Examples
///
/// ```gleam
/// append(to: "butter", suffix: "fly")
/// // -> "butterfly"
/// ```
///
pub fn append(to first: String, suffix second: String) -> String {
first <> second
}
/// Creates a new `String` by joining many `String`s together.
///
/// This function copies all the `String`s and runs in linear time.
///
/// ## Examples
///
/// ```gleam
/// concat(["never", "the", "less"])
/// // -> "nevertheless"
/// ```
///
@external(erlang, "erlang", "list_to_binary")
pub fn concat(strings: List(String)) -> String {
concat_loop(strings, "")
}
fn concat_loop(strings: List(String), accumulator: String) -> String {
case strings {
[string, ..strings] -> concat_loop(strings, accumulator <> string)
[] -> accumulator
}
}
/// Creates a new `String` by repeating a `String` a given number of times.
///
/// This function runs in loglinear time.
///
/// ## Examples
///
/// ```gleam
/// repeat("ha", times: 3)
/// // -> "hahaha"
/// ```
///
pub fn repeat(string: String, times times: Int) -> String {
case times <= 0 {
True -> ""
False -> repeat_loop(times, string, "")
}
}
fn repeat_loop(times: Int, doubling_acc: String, acc: String) -> String {
let acc = case times % 2 {
0 -> acc
_ -> acc <> doubling_acc
}
let times = times / 2
case times <= 0 {
True -> acc
False -> repeat_loop(times, doubling_acc <> doubling_acc, acc)
}
}
/// 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 {
case strings {
[] -> ""
[first, ..rest] -> join_loop(rest, separator, first)
}
}
fn join_loop(
strings: List(String),
separator: String,
accumulator: String,
) -> String {
case strings {
[] -> accumulator
[string, ..strings] ->
join_loop(strings, separator, accumulator <> separator <> string)
}
}
/// Pads the start of a `String` until it has a given length.
///
/// ## Examples
///
/// ```gleam
/// pad_start("121", to: 5, with: ".")
/// // -> "..121"
/// ```
///
/// ```gleam
/// pad_start("121", to: 3, with: ".")
/// // -> "121"
/// ```
///
/// ```gleam
/// pad_start("121", to: 2, with: ".")
/// // -> "121"
/// ```
///
pub fn pad_start(
string: String,
to desired_length: Int,
with pad_string: String,
) -> String {
let current_length = length(string)
let to_pad_length = desired_length - current_length
case to_pad_length <= 0 {
True -> string
False -> padding(to_pad_length, pad_string) <> string
}
}
/// Pads the end of a `String` until it has a given length.
///
/// ## Examples
///
/// ```gleam
/// pad_end("123", to: 5, with: ".")
/// // -> "123.."
/// ```
///
/// ```gleam
/// pad_end("123", to: 3, with: ".")
/// // -> "123"
/// ```
///
/// ```gleam
/// pad_end("123", to: 2, with: ".")
/// // -> "123"
/// ```
///
pub fn pad_end(
string: String,
to desired_length: Int,
with pad_string: String,
) -> String {
let current_length = length(string)
let to_pad_length = desired_length - current_length
case to_pad_length <= 0 {
True -> string
False -> string <> padding(to_pad_length, pad_string)
}
}
fn padding(size: Int, pad_string: String) -> String {
let pad_string_length = length(pad_string)
let num_pads = size / pad_string_length
let extra = size % pad_string_length
repeat(pad_string, num_pads) <> slice(pad_string, 0, extra)
}
/// Removes whitespace on both sides of a `String`.
///
/// Whitespace in this function is the set of nonbreakable whitespace
/// codepoints, defined as Pattern_White_Space in [Unicode Standard Annex #31][1].
///
/// [1]: https://unicode.org/reports/tr31/
///
/// ## Examples
///
/// ```gleam
/// trim(" hats \n")
/// // -> "hats"
/// ```
///
pub fn trim(string: String) -> String {
string |> trim_start |> trim_end
}
@external(erlang, "string", "trim")
fn erl_trim(a: String, b: Direction) -> String
type Direction {
Leading
Trailing
}
/// Removes whitespace at the start of a `String`.
///
/// ## Examples
///
/// ```gleam
/// trim_start(" hats \n")
/// // -> "hats \n"
/// ```
///
@external(javascript, "../gleam_stdlib.mjs", "trim_start")
pub fn trim_start(string: String) -> String {
erl_trim(string, Leading)
}
/// Removes whitespace at the end of a `String`.
///
/// ## Examples
///
/// ```gleam
/// trim_end(" hats \n")
/// // -> " hats"
/// ```
///
@external(javascript, "../gleam_stdlib.mjs", "trim_end")
pub fn trim_end(string: String) -> String {
erl_trim(string, Trailing)
}
/// 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.
///
/// ## Performance
///
/// There is a notable overhead to using this function, so you may not want to
/// use it in a tight loop. If you wish to efficiently parse a string you may
/// want to use alternatives such as the [splitter package](https://hex.pm/packages/splitter).
///
/// ## Examples
///
/// ```gleam
/// pop_grapheme("gleam")
/// // -> Ok(#("g", "leam"))
/// ```
///
/// ```gleam
/// pop_grapheme("")
/// // -> Error(Nil)
/// ```
///
@external(erlang, "gleam_stdlib", "string_pop_grapheme")
@external(javascript, "../gleam_stdlib.mjs", "pop_grapheme")
pub fn pop_grapheme(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) {
string
|> to_graphemes_loop([])
|> list.reverse
}
fn to_graphemes_loop(string: String, acc: List(String)) -> List(String) {
case pop_grapheme(string) {
Ok(#(grapheme, rest)) -> to_graphemes_loop(rest, [grapheme, ..acc])
Error(_) -> 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) {
to_utf_codepoints_loop(<<string:utf8>>, [])
}
@target(erlang)
fn to_utf_codepoints_loop(
bit_array: BitArray,
acc: List(UtfCodepoint),
) -> List(UtfCodepoint) {
case bit_array {
<<first:utf8_codepoint, rest:bytes>> ->
to_utf_codepoints_loop(rest, [first, ..acc])
_ -> list.reverse(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(string: 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) = utf_codepoint(97)
/// let assert Ok(b) = utf_codepoint(98)
/// let assert Ok(c) = utf_codepoint(99)
/// from_utf_codepoints([a, b, c])
/// // -> "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)
i if i >= 55_296 && i <= 57_343 -> Error(Nil)
i if i < 0 -> 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
/// ```
///
@external(erlang, "gleam_stdlib", "identity")
@external(javascript, "../gleam_stdlib.mjs", "utf_codepoint_to_int")
pub fn utf_codepoint_to_int(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(string: String) -> Option(String) {
case string {
"" -> None
_ -> Some(string)
}
}
/// 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(string: String) -> Result(String, Nil) {
case pop_grapheme(string) {
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)`.
///
/// This function traverses the full string, so it runs in linear time with the
/// length of the string. Avoid using this in a loop.
///
/// ## Examples
///
/// ```gleam
/// last("")
/// // -> Error(Nil)
/// ```
///
/// ```gleam
/// last("icecream")
/// // -> Ok("m")
/// ```
///
pub fn last(string: String) -> Result(String, Nil) {
case pop_grapheme(string) {
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(string: String) -> String {
case pop_grapheme(string) {
Ok(#(first, rest)) -> append(to: uppercase(first), suffix: lowercase(rest))
Error(_) -> ""
}
}
/// Returns a `String` representation of a term in Gleam syntax.
///
/// This may be occasionally useful for quick-and-dirty printing of values in
/// scripts. For error reporting and other uses prefer constructing strings by
/// pattern matching on the values.
///
/// ## Limitations
///
/// The output format of this function is not stable and could change at any
/// time. The output is not suitable for parsing.
///
/// This function works using runtime reflection, so the output may not be
/// perfectly accurate for data structures where the runtime structure doesn't
/// hold enough information to determine the original syntax. For example,
/// tuples with an Erlang atom in the first position will be mistaken for Gleam
/// records.
///
/// ## Security and safety
///
/// There is no limit to how large the strings that this function can produce.
/// Be careful not to call this function with large data structures or you
/// could use very large amounts of memory, potentially causing runtime
/// problems.
///
pub fn inspect(term: anything) -> String {
term
|> do_inspect
|> string_tree.to_string
}
@external(erlang, "gleam_stdlib", "inspect")
@external(javascript, "../gleam_stdlib.mjs", "inspect")
fn do_inspect(term: anything) -> StringTree
/// Returns the number of bytes in a `String`.
///
/// This function runs in constant time on Erlang and in linear time on
/// JavaScript.
///
/// ## Examples
///
/// ```gleam
/// byte_size("🏳️‍⚧️🏳️‍🌈👩🏾‍❤️‍👨🏻")
/// // -> 58
/// ```
///
@external(erlang, "erlang", "byte_size")
@external(javascript, "../gleam_stdlib.mjs", "byte_size")
pub fn byte_size(string: String) -> Int

View file

@ -0,0 +1,208 @@
import gleam/list
/// `StringTree` is a type used for efficiently building text content to be
/// written to a file or a socket. Internally it is represented as tree so to
/// append or prepend to a string tree is a constant time operation that
/// allocates a new node in the tree without copying any of the content. When
/// writing to an output stream the tree is traversed and the content is sent
/// directly rather than copying it into a single buffer beforehand.
///
/// On Erlang this type is compatible with Erlang's iodata. On JavaScript this
/// type is compatible with normal strings.
///
/// The BEAM virtual machine has an optimisation for appending strings, where it
/// will mutate the string buffer when safe to do so, so if you are looking to
/// build a string through appending many small strings then you may get better
/// performance by not using a string tree. Always benchmark your performance
/// sensitive code.
///
pub type StringTree
/// Create an empty `StringTree`. Useful as the start of a pipe chaining many
/// trees together.
///
pub fn new() -> StringTree {
from_strings([])
}
/// Prepends a `String` onto the start of some `StringTree`.
///
/// Runs in constant time.
///
pub fn prepend(to tree: StringTree, prefix prefix: String) -> StringTree {
append_tree(from_string(prefix), tree)
}
/// Appends a `String` onto the end of some `StringTree`.
///
/// Runs in constant time.
///
pub fn append(to tree: StringTree, suffix second: String) -> StringTree {
append_tree(tree, from_string(second))
}
/// Prepends some `StringTree` onto the start of another.
///
/// Runs in constant time.
///
pub fn prepend_tree(
to tree: StringTree,
prefix prefix: StringTree,
) -> StringTree {
append_tree(prefix, tree)
}
/// Appends some `StringTree` onto the end of another.
///
/// Runs in constant time.
///
@external(erlang, "gleam_stdlib", "iodata_append")
@external(javascript, "../gleam_stdlib.mjs", "add")
pub fn append_tree(to tree: StringTree, suffix suffix: StringTree) -> StringTree
/// Converts a list of strings into a `StringTree`.
///
/// Runs in constant time.
///
@external(erlang, "gleam_stdlib", "identity")
@external(javascript, "../gleam_stdlib.mjs", "concat")
pub fn from_strings(strings: List(String)) -> StringTree
/// Joins a list of trees into a single tree.
///
/// Runs in constant time.
///
@external(erlang, "gleam_stdlib", "identity")
@external(javascript, "../gleam_stdlib.mjs", "concat")
pub fn concat(trees: List(StringTree)) -> StringTree
/// Converts a string into a `StringTree`.
///
/// Runs in constant time.
///
@external(erlang, "gleam_stdlib", "identity")
@external(javascript, "../gleam_stdlib.mjs", "identity")
pub fn from_string(string: String) -> StringTree
/// Turns a `StringTree` into a `String`
///
/// This function is implemented natively by the virtual machine and is highly
/// optimised.
///
@external(erlang, "unicode", "characters_to_binary")
@external(javascript, "../gleam_stdlib.mjs", "identity")
pub fn to_string(tree: StringTree) -> String
/// Returns the size of the `StringTree` in bytes.
///
@external(erlang, "erlang", "iolist_size")
@external(javascript, "../gleam_stdlib.mjs", "length")
pub fn byte_size(tree: StringTree) -> Int
/// Joins the given trees into a new tree separated with the given string.
///
pub fn join(trees: List(StringTree), with sep: String) -> StringTree {
trees
|> list.intersperse(from_string(sep))
|> concat
}
/// Converts a `StringTree` to a new one where the contents have been
/// lowercased.
///
@external(erlang, "string", "lowercase")
@external(javascript, "../gleam_stdlib.mjs", "lowercase")
pub fn lowercase(tree: StringTree) -> StringTree
/// Converts a `StringTree` to a new one where the contents have been
/// uppercased.
///
@external(erlang, "string", "uppercase")
@external(javascript, "../gleam_stdlib.mjs", "uppercase")
pub fn uppercase(tree: StringTree) -> StringTree
/// Converts a `StringTree` to a new one with the contents reversed.
///
@external(erlang, "string", "reverse")
pub fn reverse(tree: StringTree) -> StringTree {
tree
|> to_string
|> do_to_graphemes
|> list.reverse
|> from_strings
}
@external(javascript, "../gleam_stdlib.mjs", "graphemes")
fn do_to_graphemes(string: String) -> List(String)
type Direction {
All
}
/// Splits a `StringTree` on a given pattern into a list of trees.
///
@external(javascript, "../gleam_stdlib.mjs", "split")
pub fn split(tree: StringTree, on pattern: String) -> List(StringTree) {
erl_split(tree, pattern, All)
}
@external(erlang, "string", "split")
fn erl_split(a: StringTree, b: String, c: Direction) -> List(StringTree)
/// Replaces all instances of a pattern with a given string substitute.
///
@external(erlang, "gleam_stdlib", "string_replace")
@external(javascript, "../gleam_stdlib.mjs", "string_replace")
pub fn replace(
in tree: StringTree,
each pattern: String,
with substitute: String,
) -> StringTree
/// Compares two string trees to determine if they have the same textual
/// content.
///
/// Comparing two string trees 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
/// ```
///
@external(erlang, "string", "equal")
pub fn is_equal(a: StringTree, b: StringTree) -> Bool {
a == b
}
/// Inspects a `StringTree` 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
/// ```
///
@external(erlang, "string", "is_empty")
pub fn is_empty(tree: StringTree) -> Bool {
from_string("") == tree
}

View file

@ -0,0 +1,770 @@
//// 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_tree.{type StringTree}
/// 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),
)
}
/// Constant representing an empty URI, equivalent to "".
///
/// ## Examples
///
/// ```gleam
/// let uri = Uri(..empty, scheme: Some("https"), host: Some("example.com"))
/// // -> Uri(
/// // scheme: Some("https"),
/// // userinfo: None,
/// // host: Some("example.com"),
/// // port: None,
/// // path: "",
/// // query: None,
/// // fragment: None,
/// // )
/// ```
///
pub const empty = Uri(
scheme: None,
userinfo: None,
host: None,
port: None,
path: "",
query: None,
fragment: None,
)
/// 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")
/// // )
/// // )
/// ```
///
@external(erlang, "gleam_stdlib", "uri_parse")
pub fn parse(uri_string: String) -> Result(Uri, Nil) {
// This parses a uri_string following the regex defined in
// https://tools.ietf.org/html/rfc3986#appendix-B
//
// TODO: This is not perfect and will be more permissive than its Erlang
// counterpart, ideally we want to replicate Erlang's implementation on the js
// target as well.
parse_scheme_loop(uri_string, uri_string, empty, 0)
}
fn parse_scheme_loop(
original: String,
uri_string: String,
pieces: Uri,
size: Int,
) -> Result(Uri, Nil) {
case uri_string {
// `/` is not allowed to appear in a scheme so we know it's over and we can
// start parsing the authority with slashes.
"/" <> _ if size == 0 -> parse_authority_with_slashes(uri_string, pieces)
"/" <> _ -> {
let scheme = codeunit_slice(original, at_index: 0, length: size)
let pieces = Uri(..pieces, scheme: Some(string.lowercase(scheme)))
parse_authority_with_slashes(uri_string, pieces)
}
// `?` is not allowed to appear in a schemem, in an authority, or in a path;
// so if we see it we know it marks the beginning of the query part.
"?" <> rest if size == 0 -> parse_query_with_question_mark(rest, pieces)
"?" <> rest -> {
let scheme = codeunit_slice(original, at_index: 0, length: size)
let pieces = Uri(..pieces, scheme: Some(string.lowercase(scheme)))
parse_query_with_question_mark(rest, pieces)
}
// `#` is not allowed to appear in a scheme, in an authority, in a path or
// in a query; so if we see it we know it marks the beginning of the final
// fragment.
"#" <> rest if size == 0 -> parse_fragment(rest, pieces)
"#" <> rest -> {
let scheme = codeunit_slice(original, at_index: 0, length: size)
let pieces = Uri(..pieces, scheme: Some(string.lowercase(scheme)))
parse_fragment(rest, pieces)
}
// A colon marks the end of a uri scheme, but if it is not preceded by any
// character then it's not a valid URI.
":" <> _ if size == 0 -> Error(Nil)
":" <> rest -> {
let scheme = codeunit_slice(original, at_index: 0, length: size)
let pieces = Uri(..pieces, scheme: Some(string.lowercase(scheme)))
parse_authority_with_slashes(rest, pieces)
}
// If we could get to the end of the string and we've met no special
// chars whatsoever, that means the entire string is just a long path.
"" -> Ok(Uri(..pieces, path: original))
// In all other cases the first character is just a valid URI scheme
// character and we just keep munching characters until we reach the end of
// the uri scheme (or the end of the string and that would mean this is not
// a valid uri scheme since we found no `:`).
_ -> {
let #(_, rest) = pop_codeunit(uri_string)
parse_scheme_loop(original, rest, pieces, size + 1)
}
}
}
fn parse_authority_with_slashes(
uri_string: String,
pieces: Uri,
) -> Result(Uri, Nil) {
case uri_string {
// To be a valid authority the string must start with a `//`, otherwise
// there's no authority and we just skip ahead to parsing the path.
"//" -> Ok(Uri(..pieces, host: Some("")))
"//" <> rest -> parse_authority_pieces(rest, pieces)
_ -> parse_path(uri_string, pieces)
}
}
fn parse_authority_pieces(string: String, pieces: Uri) -> Result(Uri, Nil) {
parse_userinfo_loop(string, string, pieces, 0)
}
fn parse_userinfo_loop(
original: String,
uri_string: String,
pieces: Uri,
size: Int,
) -> Result(Uri, Nil) {
case uri_string {
// `@` marks the end of the userinfo and the start of the host part in the
// authority string.
"@" <> rest if size == 0 -> parse_host(rest, pieces)
"@" <> rest -> {
let userinfo = codeunit_slice(original, at_index: 0, length: size)
let pieces = Uri(..pieces, userinfo: Some(userinfo))
parse_host(rest, pieces)
}
// If we reach the end of the authority string without finding an `@`
// special character, then we know that the authority doesn't actually
// contain the userinfo part.
// The entire string we just went through was a host! So we parse it as
// such.
"" | "/" <> _ | "?" <> _ | "#" <> _ -> parse_host(original, pieces)
// In all other cases we just keep munching characters increasing the size
// of the userinfo bit.
_ -> {
let #(_, rest) = pop_codeunit(uri_string)
parse_userinfo_loop(original, rest, pieces, size + 1)
}
}
}
fn parse_host(uri_string: String, pieces: Uri) -> Result(Uri, Nil) {
// A host string can be in two formats:
// - \[[:.a-zA-Z0-9]*\]
// - [^:]
case uri_string {
// If we find an opening bracket we know it's the first format.
"[" <> _ -> parse_host_within_brackets(uri_string, pieces)
// A `:` marks the beginning of the port part of the authority string.
":" <> _ -> {
let pieces = Uri(..pieces, host: Some(""))
parse_port(uri_string, pieces)
}
// If the string is empty then there's no need to keep going. The host is
// empty.
"" -> Ok(Uri(..pieces, host: Some("")))
// Otherwise it's the second format
_ -> parse_host_outside_of_brackets(uri_string, pieces)
}
}
fn parse_host_within_brackets(
uri_string: String,
pieces: Uri,
) -> Result(Uri, Nil) {
parse_host_within_brackets_loop(uri_string, uri_string, pieces, 0)
}
fn parse_host_within_brackets_loop(
original: String,
uri_string: String,
pieces: Uri,
size: Int,
) -> Result(Uri, Nil) {
case uri_string {
// If the string is over the entire string we were iterating through is the
// host part.
"" -> Ok(Uri(..pieces, host: Some(uri_string)))
// A `]` marks the end of the host and the start of the port part.
"]" <> rest if size == 0 -> parse_port(rest, pieces)
"]" <> rest -> {
let host = codeunit_slice(original, at_index: 0, length: size + 1)
let pieces = Uri(..pieces, host: Some(host))
parse_port(rest, pieces)
}
// `/` marks the beginning of a path.
"/" <> _ if size == 0 -> parse_path(uri_string, pieces)
"/" <> _ -> {
let host = codeunit_slice(original, at_index: 0, length: size)
let pieces = Uri(..pieces, host: Some(host))
parse_path(uri_string, pieces)
}
// `?` marks the beginning of the query with question mark.
"?" <> rest if size == 0 -> parse_query_with_question_mark(rest, pieces)
"?" <> rest -> {
let host = codeunit_slice(original, at_index: 0, length: size)
let pieces = Uri(..pieces, host: Some(host))
parse_query_with_question_mark(rest, pieces)
}
// `#` marks the beginning of the fragment part.
"#" <> rest if size == 0 -> parse_fragment(rest, pieces)
"#" <> rest -> {
let host = codeunit_slice(original, at_index: 0, length: size)
let pieces = Uri(..pieces, host: Some(host))
parse_fragment(rest, pieces)
}
// In all other cases we just keep iterating.
_ -> {
let #(char, rest) = pop_codeunit(uri_string)
// Inside `[...]` there can only be some characters, if we find a special
// one then we know that we're actually parsing the other format for the
// host and we switch to that!
case is_valid_host_within_brackets_char(char) {
True ->
parse_host_within_brackets_loop(original, rest, pieces, size + 1)
False ->
parse_host_outside_of_brackets_loop(original, original, pieces, 0)
}
}
}
}
fn is_valid_host_within_brackets_char(char: Int) -> Bool {
// [0-9]
{ 48 >= char && char <= 57 }
// [A-Z]
|| { 65 >= char && char <= 90 }
// [a-z]
|| { 97 >= char && char <= 122 }
// :
|| char == 58
// .
|| char == 46
}
fn parse_host_outside_of_brackets(
uri_string: String,
pieces: Uri,
) -> Result(Uri, Nil) {
parse_host_outside_of_brackets_loop(uri_string, uri_string, pieces, 0)
}
fn parse_host_outside_of_brackets_loop(
original: String,
uri_string: String,
pieces: Uri,
size: Int,
) -> Result(Uri, Nil) {
case uri_string {
"" -> Ok(Uri(..pieces, host: Some(original)))
// `:` marks the beginning of the port.
":" <> _ -> {
let host = codeunit_slice(original, at_index: 0, length: size)
let pieces = Uri(..pieces, host: Some(host))
parse_port(uri_string, pieces)
}
// `/` marks the beginning of a path.
"/" <> _ -> {
let host = codeunit_slice(original, at_index: 0, length: size)
let pieces = Uri(..pieces, host: Some(host))
parse_path(uri_string, pieces)
}
// `?` marks the beginning of the query with question mark.
"?" <> rest -> {
let host = codeunit_slice(original, at_index: 0, length: size)
let pieces = Uri(..pieces, host: Some(host))
parse_query_with_question_mark(rest, pieces)
}
// `#` marks the beginning of the fragment part.
"#" <> rest -> {
let host = codeunit_slice(original, at_index: 0, length: size)
let pieces = Uri(..pieces, host: Some(host))
parse_fragment(rest, pieces)
}
_ -> {
let #(_, rest) = pop_codeunit(uri_string)
parse_host_outside_of_brackets_loop(original, rest, pieces, size + 1)
}
}
}
fn parse_port(uri_string: String, pieces: Uri) -> Result(Uri, Nil) {
case uri_string {
":0" <> rest -> parse_port_loop(rest, pieces, 0)
":1" <> rest -> parse_port_loop(rest, pieces, 1)
":2" <> rest -> parse_port_loop(rest, pieces, 2)
":3" <> rest -> parse_port_loop(rest, pieces, 3)
":4" <> rest -> parse_port_loop(rest, pieces, 4)
":5" <> rest -> parse_port_loop(rest, pieces, 5)
":6" <> rest -> parse_port_loop(rest, pieces, 6)
":7" <> rest -> parse_port_loop(rest, pieces, 7)
":8" <> rest -> parse_port_loop(rest, pieces, 8)
":9" <> rest -> parse_port_loop(rest, pieces, 9)
// The port could be empty and be followed by any of the next delimiters.
// Like `:#`, `:?` or `:/`
":" | "" -> Ok(pieces)
// `?` marks the beginning of the query with question mark.
"?" <> rest | ":?" <> rest -> parse_query_with_question_mark(rest, pieces)
// `#` marks the beginning of the fragment part.
"#" <> rest | ":#" <> rest -> parse_fragment(rest, pieces)
// `/` marks the beginning of a path.
"/" <> _ -> parse_path(uri_string, pieces)
":" <> rest ->
case rest {
"/" <> _ -> parse_path(rest, pieces)
_ -> Error(Nil)
}
_ -> Error(Nil)
}
}
fn parse_port_loop(
uri_string: String,
pieces: Uri,
port: Int,
) -> Result(Uri, Nil) {
case uri_string {
// As long as we find port numbers we keep accumulating those.
"0" <> rest -> parse_port_loop(rest, pieces, port * 10)
"1" <> rest -> parse_port_loop(rest, pieces, port * 10 + 1)
"2" <> rest -> parse_port_loop(rest, pieces, port * 10 + 2)
"3" <> rest -> parse_port_loop(rest, pieces, port * 10 + 3)
"4" <> rest -> parse_port_loop(rest, pieces, port * 10 + 4)
"5" <> rest -> parse_port_loop(rest, pieces, port * 10 + 5)
"6" <> rest -> parse_port_loop(rest, pieces, port * 10 + 6)
"7" <> rest -> parse_port_loop(rest, pieces, port * 10 + 7)
"8" <> rest -> parse_port_loop(rest, pieces, port * 10 + 8)
"9" <> rest -> parse_port_loop(rest, pieces, port * 10 + 9)
// `?` marks the beginning of the query with question mark.
"?" <> rest -> {
let pieces = Uri(..pieces, port: Some(port))
parse_query_with_question_mark(rest, pieces)
}
// `#` marks the beginning of the fragment part.
"#" <> rest -> {
let pieces = Uri(..pieces, port: Some(port))
parse_fragment(rest, pieces)
}
// `/` marks the beginning of a path.
"/" <> _ -> {
let pieces = Uri(..pieces, port: Some(port))
parse_path(uri_string, pieces)
}
// The string (and so the port) is over, we return what we parsed so far.
"" -> Ok(Uri(..pieces, port: Some(port)))
// In all other cases we've ran into some invalid character inside the port
// so the uri is invalid!
_ -> Error(Nil)
}
}
fn parse_path(uri_string: String, pieces: Uri) -> Result(Uri, Nil) {
parse_path_loop(uri_string, uri_string, pieces, 0)
}
fn parse_path_loop(
original: String,
uri_string: String,
pieces: Uri,
size: Int,
) -> Result(Uri, Nil) {
case uri_string {
// `?` marks the beginning of the query with question mark.
"?" <> rest -> {
let path = codeunit_slice(original, at_index: 0, length: size)
let pieces = Uri(..pieces, path: path)
parse_query_with_question_mark(rest, pieces)
}
// `#` marks the beginning of the fragment part.
"#" <> rest -> {
let path = codeunit_slice(original, at_index: 0, length: size)
let pieces = Uri(..pieces, path: path)
parse_fragment(rest, pieces)
}
// If the string is over that means the entirety of the string was the path
// and it has an empty query and fragment.
"" -> Ok(Uri(..pieces, path: original))
// In all other cases the character is allowed to be part of the path so we
// just keep munching until we reach to its end.
_ -> {
let #(_, rest) = pop_codeunit(uri_string)
parse_path_loop(original, rest, pieces, size + 1)
}
}
}
fn parse_query_with_question_mark(
uri_string: String,
pieces: Uri,
) -> Result(Uri, Nil) {
parse_query_with_question_mark_loop(uri_string, uri_string, pieces, 0)
}
fn parse_query_with_question_mark_loop(
original: String,
uri_string: String,
pieces: Uri,
size: Int,
) -> Result(Uri, Nil) {
case uri_string {
// `#` marks the beginning of the fragment part.
"#" <> rest if size == 0 -> parse_fragment(rest, pieces)
"#" <> rest -> {
let query = codeunit_slice(original, at_index: 0, length: size)
let pieces = Uri(..pieces, query: Some(query))
parse_fragment(rest, pieces)
}
// If the string is over that means the entirety of the string was the query
// and it has an empty fragment.
"" -> Ok(Uri(..pieces, query: Some(original)))
// In all other cases the character is allowed to be part of the query so we
// just keep munching until we reach to its end.
_ -> {
let #(_, rest) = pop_codeunit(uri_string)
parse_query_with_question_mark_loop(original, rest, pieces, size + 1)
}
}
}
fn parse_fragment(rest: String, pieces: Uri) -> Result(Uri, Nil) {
Ok(Uri(..pieces, fragment: Some(rest)))
}
// WARN: this function returns invalid strings!
// We need to return a String anyways to have this as the representation on the
// JavaScript target.
// Alternatively, we could rewrite the entire code to use a single
// `fold_codeunits`-style loop and a state machine.
@external(erlang, "gleam_stdlib", "string_pop_codeunit")
@external(javascript, "../gleam_stdlib.mjs", "pop_codeunit")
fn pop_codeunit(str: String) -> #(Int, String)
@external(erlang, "binary", "part")
@external(javascript, "../gleam_stdlib.mjs", "string_codeunit_slice")
fn codeunit_slice(str: String, at_index from: Int, length length: Int) -> String
/// 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")])
/// ```
///
@external(erlang, "gleam_stdlib", "parse_query")
@external(javascript, "../gleam_stdlib.mjs", "parse_query")
pub fn parse_query(query: 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_tree.from_string("&"))
|> string_tree.concat
|> string_tree.to_string
}
fn query_pair(pair: #(String, String)) -> StringTree {
string_tree.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"
/// ```
///
@external(erlang, "gleam_stdlib", "percent_encode")
@external(javascript, "../gleam_stdlib.mjs", "percent_encode")
pub fn percent_encode(value: String) -> String
/// Decodes a percent encoded string.
///
/// ## Examples
///
/// ```gleam
/// percent_decode("100%25%20great+fun")
/// // -> Ok("100% great+fun")
/// ```
///
@external(erlang, "gleam_stdlib", "percent_decode")
@external(javascript, "../gleam_stdlib.mjs", "percent_decode")
pub fn percent_decode(value: String) -> Result(String, Nil)
/// 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, "/"))
}
fn remove_dot_segments(input: List(String)) -> List(String) {
remove_dot_segments_loop(input, [])
}
fn remove_dot_segments_loop(
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]
}
remove_dot_segments_loop(rest, accumulator)
}
}
}
/// Encodes a `Uri` value as a URI string.
///
/// The opposite operation is `uri.parse`.
///
/// ## Examples
///
/// ```gleam
/// let uri = Uri(..empty, scheme: Some("https"), host: Some("example.com"))
/// to_string(uri)
/// // -> "https://example.com"
/// ```
///
pub fn to_string(uri: Uri) -> String {
let parts = case uri.fragment {
Some(fragment) -> ["#", fragment]
None -> []
}
let parts = case uri.query {
Some(query) -> ["?", query, ..parts]
None -> 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("https://example.com/path?foo#bar")
/// origin(uri)
/// // -> Ok("https://example.com")
/// ```
///
pub fn origin(uri: Uri) -> Result(String, Nil) {
let Uri(scheme: scheme, host: host, port: port, ..) = uri
case host, scheme {
Some(h), Some("https") if port == Some(443) ->
Ok(string.concat(["https://", h]))
Some(h), Some("http") if port == Some(80) ->
Ok(string.concat(["http://", h]))
Some(h), Some(s) if s == "http" || s == "https" -> {
case port {
Some(p) -> Ok(string.concat([s, "://", h, ":", int.to_string(p)]))
None -> Ok(string.concat([s, "://", h]))
}
}
_, _ -> Error(Nil)
}
}
/// 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 =
relative.path
|> string.split("/")
|> 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 ->
base.path
|> string.split("/")
|> 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)
}
}
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], "/")
}

View file

@ -0,0 +1,347 @@
-module(gleam@bit_array).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/gleam/bit_array.gleam").
-export([from_string/1, bit_size/1, byte_size/1, pad_to_bytes/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, inspect/1, compare/2, starts_with/2]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(" BitArrays are a sequence of binary data of any length.\n").
-file("src/gleam/bit_array.gleam", 11).
?DOC(" Converts a UTF-8 `String` type into a `BitArray`.\n").
-spec from_string(binary()) -> bitstring().
from_string(X) ->
gleam_stdlib:identity(X).
-file("src/gleam/bit_array.gleam", 17).
?DOC(" Returns an integer which is the number of bits in the bit array.\n").
-spec bit_size(bitstring()) -> integer().
bit_size(X) ->
erlang:bit_size(X).
-file("src/gleam/bit_array.gleam", 23).
?DOC(" Returns an integer which is the number of bytes in the bit array.\n").
-spec byte_size(bitstring()) -> integer().
byte_size(X) ->
erlang:byte_size(X).
-file("src/gleam/bit_array.gleam", 29).
?DOC(" Pads a bit array with zeros so that it is a whole number of bytes.\n").
-spec pad_to_bytes(bitstring()) -> bitstring().
pad_to_bytes(X) ->
gleam_stdlib:bit_array_pad_to_bytes(X).
-file("src/gleam/bit_array.gleam", 54).
?DOC(
" Extracts a sub-section of a bit array.\n"
"\n"
" The slice will start at given position and continue up to specified\n"
" length.\n"
" A negative length can be used to extract bytes at the end of a bit array.\n"
"\n"
" This function runs in constant time.\n"
).
-spec slice(bitstring(), integer(), integer()) -> {ok, bitstring()} |
{error, nil}.
slice(String, Position, Length) ->
gleam_stdlib:bit_array_slice(String, Position, Length).
-file("src/gleam/bit_array.gleam", 67).
-spec is_utf8_loop(bitstring()) -> boolean().
is_utf8_loop(Bits) ->
case Bits of
<<>> ->
true;
<<_/utf8, Rest/binary>> ->
is_utf8_loop(Rest);
_ ->
false
end.
-file("src/gleam/bit_array.gleam", 62).
?DOC(" Tests to see whether a bit array is valid UTF-8.\n").
-spec is_utf8(bitstring()) -> boolean().
is_utf8(Bits) ->
is_utf8_loop(Bits).
-file("src/gleam/bit_array.gleam", 88).
?DOC(
" Converts a bit array to a string.\n"
"\n"
" Returns an error if the bit array is invalid UTF-8 data.\n"
).
-spec to_string(bitstring()) -> {ok, binary()} | {error, nil}.
to_string(Bits) ->
case is_utf8(Bits) of
true ->
{ok, gleam_stdlib:identity(Bits)};
false ->
{error, nil}
end.
-file("src/gleam/bit_array.gleam", 109).
?DOC(
" Creates a new bit array by joining multiple binaries.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" concat([from_string(\"butter\"), from_string(\"fly\")])\n"
" // -> from_string(\"butterfly\")\n"
" ```\n"
).
-spec concat(list(bitstring())) -> bitstring().
concat(Bit_arrays) ->
gleam_stdlib:bit_array_concat(Bit_arrays).
-file("src/gleam/bit_array.gleam", 40).
?DOC(
" Creates a new bit array by joining two bit arrays.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" append(to: from_string(\"butter\"), suffix: from_string(\"fly\"))\n"
" // -> from_string(\"butterfly\")\n"
" ```\n"
).
-spec append(bitstring(), bitstring()) -> bitstring().
append(First, Second) ->
gleam_stdlib:bit_array_concat([First, Second]).
-file("src/gleam/bit_array.gleam", 118).
?DOC(
" Encodes a BitArray into a base 64 encoded string.\n"
"\n"
" If the bit array does not contain a whole number of bytes then it is padded\n"
" with zero bits prior to being encoded.\n"
).
-spec base64_encode(bitstring(), boolean()) -> binary().
base64_encode(Input, Padding) ->
gleam_stdlib:base64_encode(Input, Padding).
-file("src/gleam/bit_array.gleam", 122).
?DOC(" Decodes a base 64 encoded string into a `BitArray`.\n").
-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:base64_decode(Padded).
-file("src/gleam/bit_array.gleam", 140).
?DOC(
" Encodes a `BitArray` into a base 64 encoded string with URL and filename\n"
" safe alphabet.\n"
"\n"
" If the bit array does not contain a whole number of bytes then it is padded\n"
" with zero bits prior to being encoded.\n"
).
-spec base64_url_encode(bitstring(), boolean()) -> binary().
base64_url_encode(Input, Padding) ->
_pipe = Input,
_pipe@1 = gleam_stdlib:base64_encode(_pipe, Padding),
_pipe@2 = gleam@string:replace(_pipe@1, <<"+"/utf8>>, <<"-"/utf8>>),
gleam@string:replace(_pipe@2, <<"/"/utf8>>, <<"_"/utf8>>).
-file("src/gleam/bit_array.gleam", 150).
?DOC(
" Decodes a base 64 encoded string with URL and filename safe alphabet into a\n"
" `BitArray`.\n"
).
-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).
-file("src/gleam/bit_array.gleam", 164).
?DOC(
" Encodes a `BitArray` into a base 16 encoded string.\n"
"\n"
" If the bit array does not contain a whole number of bytes then it is padded\n"
" with zero bits prior to being encoded.\n"
).
-spec base16_encode(bitstring()) -> binary().
base16_encode(Input) ->
gleam_stdlib:base16_encode(Input).
-file("src/gleam/bit_array.gleam", 170).
?DOC(" Decodes a base 16 encoded string into a `BitArray`.\n").
-spec base16_decode(binary()) -> {ok, bitstring()} | {error, nil}.
base16_decode(Input) ->
gleam_stdlib:base16_decode(Input).
-file("src/gleam/bit_array.gleam", 191).
-spec inspect_loop(bitstring(), binary()) -> binary().
inspect_loop(Input, Accumulator) ->
case Input of
<<>> ->
Accumulator;
<<X:1>> ->
<<<<Accumulator/binary, (erlang:integer_to_binary(X))/binary>>/binary,
":size(1)"/utf8>>;
<<X@1:2>> ->
<<<<Accumulator/binary, (erlang:integer_to_binary(X@1))/binary>>/binary,
":size(2)"/utf8>>;
<<X@2:3>> ->
<<<<Accumulator/binary, (erlang:integer_to_binary(X@2))/binary>>/binary,
":size(3)"/utf8>>;
<<X@3:4>> ->
<<<<Accumulator/binary, (erlang:integer_to_binary(X@3))/binary>>/binary,
":size(4)"/utf8>>;
<<X@4:5>> ->
<<<<Accumulator/binary, (erlang:integer_to_binary(X@4))/binary>>/binary,
":size(5)"/utf8>>;
<<X@5:6>> ->
<<<<Accumulator/binary, (erlang:integer_to_binary(X@5))/binary>>/binary,
":size(6)"/utf8>>;
<<X@6:7>> ->
<<<<Accumulator/binary, (erlang:integer_to_binary(X@6))/binary>>/binary,
":size(7)"/utf8>>;
<<X@7, Rest/bitstring>> ->
Suffix = case Rest of
<<>> ->
<<""/utf8>>;
_ ->
<<", "/utf8>>
end,
Accumulator@1 = <<<<Accumulator/binary,
(erlang:integer_to_binary(X@7))/binary>>/binary,
Suffix/binary>>,
inspect_loop(Rest, Accumulator@1);
_ ->
Accumulator
end.
-file("src/gleam/bit_array.gleam", 187).
?DOC(
" Converts a bit array to a string containing the decimal value of each byte.\n"
"\n"
" Use this over `string.inspect` when you have a bit array you want printed\n"
" in the array syntax even if it is valid UTF-8.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" inspect(<<0, 20, 0x20, 255>>)\n"
" // -> \"<<0, 20, 32, 255>>\"\n"
"\n"
" inspect(<<100, 5:3>>)\n"
" // -> \"<<100, 5:size(3)>>\"\n"
" ```\n"
).
-spec inspect(bitstring()) -> binary().
inspect(Input) ->
<<(inspect_loop(Input, <<"<<"/utf8>>))/binary, ">>"/utf8>>.
-file("src/gleam/bit_array.gleam", 232).
?DOC(
" Compare two bit arrays as sequences of bytes.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" compare(<<1>>, <<2>>)\n"
" // -> Lt\n"
"\n"
" compare(<<\"AB\":utf8>>, <<\"AA\":utf8>>)\n"
" // -> Gt\n"
"\n"
" compare(<<1, 2:size(2)>>, with: <<1, 2:size(2)>>)\n"
" // -> Eq\n"
" ```\n"
).
-spec compare(bitstring(), bitstring()) -> gleam@order:order().
compare(A, B) ->
case {A, B} of
{<<First_byte, First_rest/bitstring>>,
<<Second_byte, Second_rest/bitstring>>} ->
case {First_byte, Second_byte} of
{F, S} when F > S ->
gt;
{F@1, S@1} when F@1 < S@1 ->
lt;
{_, _} ->
compare(First_rest, Second_rest)
end;
{<<>>, <<>>} ->
eq;
{_, <<>>} ->
gt;
{<<>>, _} ->
lt;
{First, Second} ->
case {gleam_stdlib:bit_array_to_int_and_size(First),
gleam_stdlib:bit_array_to_int_and_size(Second)} of
{{A@1, _}, {B@1, _}} when A@1 > B@1 ->
gt;
{{A@2, _}, {B@2, _}} when A@2 < B@2 ->
lt;
{{_, Size_a}, {_, Size_b}} when Size_a > Size_b ->
gt;
{{_, Size_a@1}, {_, Size_b@1}} when Size_a@1 < Size_b@1 ->
lt;
{_, _} ->
eq
end
end.
-file("src/gleam/bit_array.gleam", 273).
?DOC(
" Checks whether the first `BitArray` starts with the second one.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" starts_with(<<1, 2, 3, 4>>, <<1, 2>>)\n"
" // -> True\n"
" ```\n"
).
-spec starts_with(bitstring(), bitstring()) -> boolean().
starts_with(Bits, Prefix) ->
Prefix_size = erlang:bit_size(Prefix),
case Bits of
<<Pref:Prefix_size/bitstring, _/bitstring>> when Pref =:= Prefix ->
true;
_ ->
false
end.

View file

@ -0,0 +1,352 @@
-module(gleam@bool).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/gleam/bool.gleam").
-export(['and'/2, 'or'/2, negate/1, nor/2, nand/2, exclusive_or/2, exclusive_nor/2, to_string/1, guard/3, lazy_guard/3]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" A type with two possible values, `True` and `False`. Used to indicate whether\n"
" things are... true or false!\n"
"\n"
" Often is it clearer and offers more type safety to define a custom type\n"
" than to use `Bool`. For example, rather than having a `is_teacher: Bool`\n"
" field consider having a `role: SchoolRole` field where `SchoolRole` is a custom\n"
" type that can be either `Student` or `Teacher`.\n"
).
-file("src/gleam/bool.gleam", 31).
?DOC(
" Returns the and of two bools, but it evaluates both arguments.\n"
"\n"
" It's the function equivalent of the `&&` operator.\n"
" This function is useful in higher order functions or pipes.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" and(True, True)\n"
" // -> True\n"
" ```\n"
"\n"
" ```gleam\n"
" and(False, True)\n"
" // -> False\n"
" ```\n"
"\n"
" ```gleam\n"
" False |> and(True)\n"
" // -> False\n"
" ```\n"
).
-spec 'and'(boolean(), boolean()) -> boolean().
'and'(A, B) ->
A andalso B.
-file("src/gleam/bool.gleam", 57).
?DOC(
" Returns the or of two bools, but it evaluates both arguments.\n"
"\n"
" It's the function equivalent of the `||` operator.\n"
" This function is useful in higher order functions or pipes.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" or(True, True)\n"
" // -> True\n"
" ```\n"
"\n"
" ```gleam\n"
" or(False, True)\n"
" // -> True\n"
" ```\n"
"\n"
" ```gleam\n"
" False |> or(True)\n"
" // -> True\n"
" ```\n"
).
-spec 'or'(boolean(), boolean()) -> boolean().
'or'(A, B) ->
A orelse B.
-file("src/gleam/bool.gleam", 77).
?DOC(
" Returns the opposite bool value.\n"
"\n"
" This is the same as the `!` or `not` operators in some other languages.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" negate(True)\n"
" // -> False\n"
" ```\n"
"\n"
" ```gleam\n"
" negate(False)\n"
" // -> True\n"
" ```\n"
).
-spec negate(boolean()) -> boolean().
negate(Bool) ->
not Bool.
-file("src/gleam/bool.gleam", 105).
?DOC(
" Returns the nor of two bools.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" nor(False, False)\n"
" // -> True\n"
" ```\n"
"\n"
" ```gleam\n"
" nor(False, True)\n"
" // -> False\n"
" ```\n"
"\n"
" ```gleam\n"
" nor(True, False)\n"
" // -> False\n"
" ```\n"
"\n"
" ```gleam\n"
" nor(True, True)\n"
" // -> False\n"
" ```\n"
).
-spec nor(boolean(), boolean()) -> boolean().
nor(A, B) ->
not (A orelse B).
-file("src/gleam/bool.gleam", 133).
?DOC(
" Returns the nand of two bools.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" nand(False, False)\n"
" // -> True\n"
" ```\n"
"\n"
" ```gleam\n"
" nand(False, True)\n"
" // -> True\n"
" ```\n"
"\n"
" ```gleam\n"
" nand(True, False)\n"
" // -> True\n"
" ```\n"
"\n"
" ```gleam\n"
" nand(True, True)\n"
" // -> False\n"
" ```\n"
).
-spec nand(boolean(), boolean()) -> boolean().
nand(A, B) ->
not (A andalso B).
-file("src/gleam/bool.gleam", 161).
?DOC(
" Returns the exclusive or of two bools.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" exclusive_or(False, False)\n"
" // -> False\n"
" ```\n"
"\n"
" ```gleam\n"
" exclusive_or(False, True)\n"
" // -> True\n"
" ```\n"
"\n"
" ```gleam\n"
" exclusive_or(True, False)\n"
" // -> True\n"
" ```\n"
"\n"
" ```gleam\n"
" exclusive_or(True, True)\n"
" // -> False\n"
" ```\n"
).
-spec exclusive_or(boolean(), boolean()) -> boolean().
exclusive_or(A, B) ->
A /= B.
-file("src/gleam/bool.gleam", 189).
?DOC(
" Returns the exclusive nor of two bools.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" exclusive_nor(False, False)\n"
" // -> True\n"
" ```\n"
"\n"
" ```gleam\n"
" exclusive_nor(False, True)\n"
" // -> False\n"
" ```\n"
"\n"
" ```gleam\n"
" exclusive_nor(True, False)\n"
" // -> False\n"
" ```\n"
"\n"
" ```gleam\n"
" exclusive_nor(True, True)\n"
" // -> True\n"
" ```\n"
).
-spec exclusive_nor(boolean(), boolean()) -> boolean().
exclusive_nor(A, B) ->
A =:= B.
-file("src/gleam/bool.gleam", 207).
?DOC(
" Returns a string representation of the given bool.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" to_string(True)\n"
" // -> \"True\"\n"
" ```\n"
"\n"
" ```gleam\n"
" to_string(False)\n"
" // -> \"False\"\n"
" ```\n"
).
-spec to_string(boolean()) -> binary().
to_string(Bool) ->
case Bool of
false ->
<<"False"/utf8>>;
true ->
<<"True"/utf8>>
end.
-file("src/gleam/bool.gleam", 266).
?DOC(
" Run a callback function if the given bool is `False`, otherwise return a\n"
" default value.\n"
"\n"
" With a `use` expression this function can simulate the early-return pattern\n"
" found in some other programming languages.\n"
"\n"
" In a procedural language:\n"
"\n"
" ```js\n"
" if (predicate) return value;\n"
" // ...\n"
" ```\n"
"\n"
" In Gleam with a `use` expression:\n"
"\n"
" ```gleam\n"
" use <- guard(when: predicate, return: value)\n"
" // ...\n"
" ```\n"
"\n"
" Like everything in Gleam `use` is an expression, so it short circuits the\n"
" current block, not the entire function. As a result you can assign the value\n"
" to a variable:\n"
"\n"
" ```gleam\n"
" let x = {\n"
" use <- guard(when: predicate, return: value)\n"
" // ...\n"
" }\n"
" ```\n"
"\n"
" Note that unlike in procedural languages the `return` value is evaluated\n"
" even when the predicate is `False`, so it is advisable not to perform\n"
" expensive computation nor side-effects there.\n"
"\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" let name = \"\"\n"
" use <- guard(when: name == \"\", return: \"Welcome!\")\n"
" \"Hello, \" <> name\n"
" // -> \"Welcome!\"\n"
" ```\n"
"\n"
" ```gleam\n"
" let name = \"Kamaka\"\n"
" use <- guard(when: name == \"\", return: \"Welcome!\")\n"
" \"Hello, \" <> name\n"
" // -> \"Hello, Kamaka\"\n"
" ```\n"
).
-spec guard(boolean(), BSY, fun(() -> BSY)) -> BSY.
guard(Requirement, Consequence, Alternative) ->
case Requirement of
true ->
Consequence;
false ->
Alternative()
end.
-file("src/gleam/bool.gleam", 307).
?DOC(
" Runs a callback function if the given bool is `True`, otherwise runs an\n"
" alternative callback function.\n"
"\n"
" Useful when further computation should be delayed regardless of the given\n"
" bool's value.\n"
"\n"
" See [`guard`](#guard) for more info.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" let name = \"Kamaka\"\n"
" let inquiry = fn() { \"How may we address you?\" }\n"
" use <- lazy_guard(when: name == \"\", return: inquiry)\n"
" \"Hello, \" <> name\n"
" // -> \"Hello, Kamaka\"\n"
" ```\n"
"\n"
" ```gleam\n"
" import gleam/int\n"
"\n"
" let name = \"\"\n"
" let greeting = fn() { \"Hello, \" <> name }\n"
" use <- lazy_guard(when: name == \"\", otherwise: greeting)\n"
" let number = int.random(99)\n"
" let name = \"User \" <> int.to_string(number)\n"
" \"Welcome, \" <> name\n"
" // -> \"Welcome, User 54\"\n"
" ```\n"
).
-spec lazy_guard(boolean(), fun(() -> BSZ), fun(() -> BSZ)) -> BSZ.
lazy_guard(Requirement, Consequence, Alternative) ->
case Requirement of
true ->
Consequence();
false ->
Alternative()
end.

View file

@ -0,0 +1,211 @@
-module(gleam@bytes_tree).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/gleam/bytes_tree.gleam").
-export([append_tree/2, prepend_tree/2, concat/1, new/0, from_string/1, prepend_string/2, append_string/2, from_string_tree/1, from_bit_array/1, prepend/2, append/2, concat_bit_arrays/1, to_bit_array/1, byte_size/1]).
-export_type([bytes_tree/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" `BytesTree` is a type used for efficiently building binary content to be\n"
" written to a file or a socket. Internally it is represented as tree so to\n"
" append or prepend to a bytes tree is a constant time operation that\n"
" allocates a new node in the tree without copying any of the content. When\n"
" writing to an output stream the tree is traversed and the content is sent\n"
" directly rather than copying it into a single buffer beforehand.\n"
"\n"
" If we append one bit array to another the bit arrays must be copied to a\n"
" new location in memory so that they can sit together. This behaviour\n"
" enables efficient reading of the data but copying can be expensive,\n"
" especially if we want to join many bit arrays together.\n"
"\n"
" BytesTree is different in that it can be joined together in constant\n"
" time using minimal memory, and then can be efficiently converted to a\n"
" bit array using the `to_bit_array` function.\n"
"\n"
" Byte trees are always byte aligned, so that a number of bits that is not\n"
" divisible by 8 will be padded with 0s.\n"
"\n"
" On Erlang this type is compatible with Erlang's iolists.\n"
).
-opaque bytes_tree() :: {bytes, bitstring()} |
{text, gleam@string_tree:string_tree()} |
{many, list(bytes_tree())}.
-file("src/gleam/bytes_tree.gleam", 68).
?DOC(
" Appends a bytes tree onto the end of another.\n"
"\n"
" Runs in constant time.\n"
).
-spec append_tree(bytes_tree(), bytes_tree()) -> bytes_tree().
append_tree(First, Second) ->
gleam_stdlib:iodata_append(First, Second).
-file("src/gleam/bytes_tree.gleam", 59).
?DOC(
" Prepends a bytes tree onto the start of another.\n"
"\n"
" Runs in constant time.\n"
).
-spec prepend_tree(bytes_tree(), bytes_tree()) -> bytes_tree().
prepend_tree(Second, First) ->
gleam_stdlib:iodata_append(First, Second).
-file("src/gleam/bytes_tree.gleam", 98).
?DOC(
" Joins a list of bytes trees into a single one.\n"
"\n"
" Runs in constant time.\n"
).
-spec concat(list(bytes_tree())) -> bytes_tree().
concat(Trees) ->
gleam_stdlib:identity(Trees).
-file("src/gleam/bytes_tree.gleam", 35).
?DOC(
" Create an empty `BytesTree`. Useful as the start of a pipe chaining many\n"
" trees together.\n"
).
-spec new() -> bytes_tree().
new() ->
gleam_stdlib:identity([]).
-file("src/gleam/bytes_tree.gleam", 118).
?DOC(
" Creates a new bytes tree from a string.\n"
"\n"
" Runs in constant time when running on Erlang.\n"
" Runs in linear time otherwise.\n"
).
-spec from_string(binary()) -> bytes_tree().
from_string(String) ->
gleam_stdlib:wrap_list(String).
-file("src/gleam/bytes_tree.gleam", 80).
?DOC(
" Prepends a string onto the start of a bytes tree.\n"
"\n"
" Runs in constant time when running on Erlang.\n"
" Runs in linear time with the length of the string otherwise.\n"
).
-spec prepend_string(bytes_tree(), binary()) -> bytes_tree().
prepend_string(Second, First) ->
gleam_stdlib:iodata_append(gleam_stdlib:wrap_list(First), Second).
-file("src/gleam/bytes_tree.gleam", 89).
?DOC(
" Appends a string onto the end of a bytes tree.\n"
"\n"
" Runs in constant time when running on Erlang.\n"
" Runs in linear time with the length of the string otherwise.\n"
).
-spec append_string(bytes_tree(), binary()) -> bytes_tree().
append_string(First, Second) ->
gleam_stdlib:iodata_append(First, gleam_stdlib:wrap_list(Second)).
-file("src/gleam/bytes_tree.gleam", 128).
?DOC(
" Creates a new bytes tree from a string tree.\n"
"\n"
" Runs in constant time when running on Erlang.\n"
" Runs in linear time otherwise.\n"
).
-spec from_string_tree(gleam@string_tree:string_tree()) -> bytes_tree().
from_string_tree(Tree) ->
gleam_stdlib:wrap_list(Tree).
-file("src/gleam/bytes_tree.gleam", 136).
?DOC(
" Creates a new bytes tree from a bit array.\n"
"\n"
" Runs in constant time.\n"
).
-spec from_bit_array(bitstring()) -> bytes_tree().
from_bit_array(Bits) ->
_pipe = Bits,
_pipe@1 = gleam_stdlib:bit_array_pad_to_bytes(_pipe),
gleam_stdlib:wrap_list(_pipe@1).
-file("src/gleam/bytes_tree.gleam", 43).
?DOC(
" Prepends a bit array to the start of a bytes tree.\n"
"\n"
" Runs in constant time.\n"
).
-spec prepend(bytes_tree(), bitstring()) -> bytes_tree().
prepend(Second, First) ->
gleam_stdlib:iodata_append(from_bit_array(First), Second).
-file("src/gleam/bytes_tree.gleam", 51).
?DOC(
" Appends a bit array to the end of a bytes tree.\n"
"\n"
" Runs in constant time.\n"
).
-spec append(bytes_tree(), bitstring()) -> bytes_tree().
append(First, Second) ->
gleam_stdlib:iodata_append(First, from_bit_array(Second)).
-file("src/gleam/bytes_tree.gleam", 106).
?DOC(
" Joins a list of bit arrays into a single bytes tree.\n"
"\n"
" Runs in constant time.\n"
).
-spec concat_bit_arrays(list(bitstring())) -> bytes_tree().
concat_bit_arrays(Bits) ->
_pipe = Bits,
_pipe@1 = gleam@list:map(_pipe, fun from_bit_array/1),
gleam_stdlib:identity(_pipe@1).
-file("src/gleam/bytes_tree.gleam", 162).
-spec to_list(list(list(bytes_tree())), 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, Tree} | Rest@1] | Remaining_stack@2] ->
Bits@1 = gleam_stdlib:identity(unicode:characters_to_binary(Tree)),
to_list([Rest@1 | Remaining_stack@2], [Bits@1 | Acc]);
[[{many, Trees} | Rest@2] | Remaining_stack@3] ->
to_list([Trees, Rest@2 | Remaining_stack@3], Acc)
end.
-file("src/gleam/bytes_tree.gleam", 155).
?DOC(
" Turns a bytes tree into a bit array.\n"
"\n"
" Runs in linear time.\n"
"\n"
" When running on Erlang this function is implemented natively by the\n"
" virtual machine and is highly optimised.\n"
).
-spec to_bit_array(bytes_tree()) -> bitstring().
to_bit_array(Tree) ->
erlang:list_to_bitstring(Tree).
-file("src/gleam/bytes_tree.gleam", 186).
?DOC(
" Returns the size of the bytes tree's content in bytes.\n"
"\n"
" Runs in linear time.\n"
).
-spec byte_size(bytes_tree()) -> integer().
byte_size(Tree) ->
erlang:iolist_size(Tree).

View file

@ -0,0 +1,561 @@
-module(gleam@dict).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/gleam/dict.gleam").
-export([size/1, is_empty/1, to_list/1, new/0, get/2, has_key/2, insert/3, from_list/1, keys/1, values/1, take/2, merge/2, delete/2, drop/2, upsert/3, fold/3, map_values/2, filter/2, each/2, combine/3]).
-export_type([dict/2]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
-type dict(KG, KH) :: any() | {gleam_phantom, KG, KH}.
-file("src/gleam/dict.gleam", 36).
?DOC(
" Determines the number of key-value pairs in the dict.\n"
" This function runs in constant time and does not need to iterate the dict.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" new() |> size\n"
" // -> 0\n"
" ```\n"
"\n"
" ```gleam\n"
" new() |> insert(\"key\", \"value\") |> size\n"
" // -> 1\n"
" ```\n"
).
-spec size(dict(any(), any())) -> integer().
size(Dict) ->
maps:size(Dict).
-file("src/gleam/dict.gleam", 52).
?DOC(
" Determines whether or not the dict is empty.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" new() |> is_empty\n"
" // -> True\n"
" ```\n"
"\n"
" ```gleam\n"
" new() |> insert(\"b\", 1) |> is_empty\n"
" // -> False\n"
" ```\n"
).
-spec is_empty(dict(any(), any())) -> boolean().
is_empty(Dict) ->
maps:size(Dict) =:= 0.
-file("src/gleam/dict.gleam", 80).
?DOC(
" Converts the dict to a list of 2-element tuples `#(key, value)`, one for\n"
" each key-value pair in the dict.\n"
"\n"
" The tuples in the list have no specific order.\n"
"\n"
" ## Examples\n"
"\n"
" Calling `to_list` on an empty `dict` returns an empty list.\n"
"\n"
" ```gleam\n"
" new() |> to_list\n"
" // -> []\n"
" ```\n"
"\n"
" The ordering of elements in the resulting list is an implementation detail\n"
" that should not be relied upon.\n"
"\n"
" ```gleam\n"
" new() |> insert(\"b\", 1) |> insert(\"a\", 0) |> insert(\"c\", 2) |> to_list\n"
" // -> [#(\"a\", 0), #(\"b\", 1), #(\"c\", 2)]\n"
" ```\n"
).
-spec to_list(dict(KQ, KR)) -> list({KQ, KR}).
to_list(Dict) ->
maps:to_list(Dict).
-file("src/gleam/dict.gleam", 129).
?DOC(" Creates a fresh dict that contains no values.\n").
-spec new() -> dict(any(), any()).
new() ->
maps:new().
-file("src/gleam/dict.gleam", 150).
?DOC(
" Fetches a value from a dict for a given key.\n"
"\n"
" The dict may not have a value for the key, so the value is wrapped in a\n"
" `Result`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" new() |> insert(\"a\", 0) |> get(\"a\")\n"
" // -> Ok(0)\n"
" ```\n"
"\n"
" ```gleam\n"
" new() |> insert(\"a\", 0) |> get(\"b\")\n"
" // -> Error(Nil)\n"
" ```\n"
).
-spec get(dict(LT, LU), LT) -> {ok, LU} | {error, nil}.
get(From, Get) ->
gleam_stdlib:map_get(From, Get).
-file("src/gleam/dict.gleam", 116).
?DOC(
" Determines whether or not a value present in the dict for a given key.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" new() |> insert(\"a\", 0) |> has_key(\"a\")\n"
" // -> True\n"
" ```\n"
"\n"
" ```gleam\n"
" new() |> insert(\"a\", 0) |> has_key(\"b\")\n"
" // -> False\n"
" ```\n"
).
-spec has_key(dict(LH, any()), LH) -> boolean().
has_key(Dict, Key) ->
maps:is_key(Key, Dict).
-file("src/gleam/dict.gleam", 169).
?DOC(
" Inserts a value into the dict with the given key.\n"
"\n"
" If the dict already has a value for the given key then the value is\n"
" replaced with the new value.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" new() |> insert(\"a\", 0)\n"
" // -> from_list([#(\"a\", 0)])\n"
" ```\n"
"\n"
" ```gleam\n"
" new() |> insert(\"a\", 0) |> insert(\"a\", 5)\n"
" // -> from_list([#(\"a\", 5)])\n"
" ```\n"
).
-spec insert(dict(LZ, MA), LZ, MA) -> dict(LZ, MA).
insert(Dict, Key, Value) ->
maps:put(Key, Value, Dict).
-file("src/gleam/dict.gleam", 92).
-spec from_list_loop(list({LA, LB}), dict(LA, LB)) -> dict(LA, LB).
from_list_loop(List, Initial) ->
case List of
[] ->
Initial;
[{Key, Value} | Rest] ->
from_list_loop(Rest, insert(Initial, Key, Value))
end.
-file("src/gleam/dict.gleam", 88).
?DOC(
" Converts a list of 2-element tuples `#(key, value)` to a dict.\n"
"\n"
" If two tuples have the same key the last one in the list will be the one\n"
" that is present in the dict.\n"
).
-spec from_list(list({KV, KW})) -> dict(KV, KW).
from_list(List) ->
maps:from_list(List).
-file("src/gleam/dict.gleam", 223).
-spec reverse_and_concat(list(NJ), list(NJ)) -> list(NJ).
reverse_and_concat(Remaining, Accumulator) ->
case Remaining of
[] ->
Accumulator;
[First | Rest] ->
reverse_and_concat(Rest, [First | Accumulator])
end.
-file("src/gleam/dict.gleam", 216).
-spec do_keys_loop(list({NE, any()}), list(NE)) -> list(NE).
do_keys_loop(List, Acc) ->
case List of
[] ->
reverse_and_concat(Acc, []);
[{Key, _} | Rest] ->
do_keys_loop(Rest, [Key | Acc])
end.
-file("src/gleam/dict.gleam", 212).
?DOC(
" Gets a list of all keys in a given dict.\n"
"\n"
" Dicts are not ordered so the keys are not returned in any specific order. Do\n"
" not write code that relies on the order keys are returned by this function\n"
" as it may change in later versions of Gleam or Erlang.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" from_list([#(\"a\", 0), #(\"b\", 1)]) |> keys\n"
" // -> [\"a\", \"b\"]\n"
" ```\n"
).
-spec keys(dict(MZ, any())) -> list(MZ).
keys(Dict) ->
maps:keys(Dict).
-file("src/gleam/dict.gleam", 249).
-spec do_values_loop(list({any(), NT}), list(NT)) -> list(NT).
do_values_loop(List, Acc) ->
case List of
[] ->
reverse_and_concat(Acc, []);
[{_, Value} | Rest] ->
do_values_loop(Rest, [Value | Acc])
end.
-file("src/gleam/dict.gleam", 244).
?DOC(
" Gets a list of all values in a given dict.\n"
"\n"
" Dicts are not ordered so the values are not returned in any specific order. Do\n"
" not write code that relies on the order values are returned by this function\n"
" as it may change in later versions of Gleam or Erlang.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" from_list([#(\"a\", 0), #(\"b\", 1)]) |> values\n"
" // -> [0, 1]\n"
" ```\n"
).
-spec values(dict(any(), NO)) -> list(NO).
values(Dict) ->
maps:values(Dict).
-file("src/gleam/dict.gleam", 318).
-spec do_take_loop(dict(OX, OY), list(OX), dict(OX, OY)) -> dict(OX, OY).
do_take_loop(Dict, Desired_keys, Acc) ->
Insert = fun(Taken, Key) -> case gleam_stdlib:map_get(Dict, Key) of
{ok, Value} ->
insert(Taken, Key, Value);
{error, _} ->
Taken
end end,
case Desired_keys of
[] ->
Acc;
[First | Rest] ->
do_take_loop(Dict, Rest, Insert(Acc, First))
end.
-file("src/gleam/dict.gleam", 309).
?DOC(
" Creates a new dict from a given dict, only including any entries for which the\n"
" keys are in a given list.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" from_list([#(\"a\", 0), #(\"b\", 1)])\n"
" |> take([\"b\"])\n"
" // -> from_list([#(\"b\", 1)])\n"
" ```\n"
"\n"
" ```gleam\n"
" from_list([#(\"a\", 0), #(\"b\", 1)])\n"
" |> take([\"a\", \"b\", \"c\"])\n"
" // -> from_list([#(\"a\", 0), #(\"b\", 1)])\n"
" ```\n"
).
-spec take(dict(OJ, OK), list(OJ)) -> dict(OJ, OK).
take(Dict, Desired_keys) ->
maps:with(Desired_keys, Dict).
-file("src/gleam/dict.gleam", 363).
-spec insert_pair(dict(PV, PW), {PV, PW}) -> dict(PV, PW).
insert_pair(Dict, Pair) ->
insert(Dict, erlang:element(1, Pair), erlang:element(2, Pair)).
-file("src/gleam/dict.gleam", 356).
-spec fold_inserts(list({PO, PP}), dict(PO, PP)) -> dict(PO, PP).
fold_inserts(New_entries, Dict) ->
case New_entries of
[] ->
Dict;
[First | Rest] ->
fold_inserts(Rest, insert_pair(Dict, First))
end.
-file("src/gleam/dict.gleam", 350).
?DOC(
" Creates a new dict from a pair of given dicts by combining their entries.\n"
"\n"
" If there are entries with the same keys in both dicts the entry from the\n"
" second dict takes precedence.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" let a = from_list([#(\"a\", 0), #(\"b\", 1)])\n"
" let b = from_list([#(\"b\", 2), #(\"c\", 3)])\n"
" merge(a, b)\n"
" // -> from_list([#(\"a\", 0), #(\"b\", 2), #(\"c\", 3)])\n"
" ```\n"
).
-spec merge(dict(PG, PH), dict(PG, PH)) -> dict(PG, PH).
merge(Dict, New_entries) ->
maps:merge(Dict, New_entries).
-file("src/gleam/dict.gleam", 382).
?DOC(
" Creates a new dict from a given dict with all the same entries except for the\n"
" one with a given key, if it exists.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" from_list([#(\"a\", 0), #(\"b\", 1)]) |> delete(\"a\")\n"
" // -> from_list([#(\"b\", 1)])\n"
" ```\n"
"\n"
" ```gleam\n"
" from_list([#(\"a\", 0), #(\"b\", 1)]) |> delete(\"c\")\n"
" // -> from_list([#(\"a\", 0), #(\"b\", 1)])\n"
" ```\n"
).
-spec delete(dict(QB, QC), QB) -> dict(QB, QC).
delete(Dict, Key) ->
maps:remove(Key, Dict).
-file("src/gleam/dict.gleam", 410).
?DOC(
" Creates a new dict from a given dict with all the same entries except any with\n"
" keys found in a given list.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" from_list([#(\"a\", 0), #(\"b\", 1)]) |> drop([\"a\"])\n"
" // -> from_list([#(\"b\", 1)])\n"
" ```\n"
"\n"
" ```gleam\n"
" from_list([#(\"a\", 0), #(\"b\", 1)]) |> drop([\"c\"])\n"
" // -> from_list([#(\"a\", 0), #(\"b\", 1)])\n"
" ```\n"
"\n"
" ```gleam\n"
" from_list([#(\"a\", 0), #(\"b\", 1)]) |> drop([\"a\", \"b\", \"c\"])\n"
" // -> from_list([])\n"
" ```\n"
).
-spec drop(dict(QN, QO), list(QN)) -> dict(QN, QO).
drop(Dict, Disallowed_keys) ->
case Disallowed_keys of
[] ->
Dict;
[First | Rest] ->
drop(delete(Dict, First), Rest)
end.
-file("src/gleam/dict.gleam", 440).
?DOC(
" Creates a new dict with one entry inserted or updated using a given function.\n"
"\n"
" If there was not an entry in the dict for the given key then the function\n"
" gets `None` as its argument, otherwise it gets `Some(value)`.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" let dict = from_list([#(\"a\", 0)])\n"
" let increment = fn(x) {\n"
" case x {\n"
" Some(i) -> i + 1\n"
" None -> 0\n"
" }\n"
" }\n"
"\n"
" upsert(dict, \"a\", increment)\n"
" // -> from_list([#(\"a\", 1)])\n"
"\n"
" upsert(dict, \"b\", increment)\n"
" // -> from_list([#(\"a\", 0), #(\"b\", 0)])\n"
" ```\n"
).
-spec upsert(dict(QU, QV), QU, fun((gleam@option:option(QV)) -> QV)) -> dict(QU, QV).
upsert(Dict, Key, Fun) ->
case gleam_stdlib:map_get(Dict, Key) of
{ok, Value} ->
insert(Dict, Key, Fun({some, Value}));
{error, _} ->
insert(Dict, Key, Fun(none))
end.
-file("src/gleam/dict.gleam", 484).
-spec fold_loop(list({RG, RH}), RJ, fun((RJ, RG, RH) -> RJ)) -> RJ.
fold_loop(List, Initial, Fun) ->
case List of
[] ->
Initial;
[{K, V} | Rest] ->
fold_loop(Rest, Fun(Initial, K, V), Fun)
end.
-file("src/gleam/dict.gleam", 476).
?DOC(
" Combines all entries into a single value by calling a given function on each\n"
" one.\n"
"\n"
" Dicts are not ordered so the values are not returned in any specific order. Do\n"
" not write code that relies on the order entries are used by this function\n"
" as it may change in later versions of Gleam or Erlang.\n"
"\n"
" # Examples\n"
"\n"
" ```gleam\n"
" let dict = from_list([#(\"a\", 1), #(\"b\", 3), #(\"c\", 9)])\n"
" fold(dict, 0, fn(accumulator, key, value) { accumulator + value })\n"
" // -> 13\n"
" ```\n"
"\n"
" ```gleam\n"
" import gleam/string\n"
"\n"
" let dict = from_list([#(\"a\", 1), #(\"b\", 3), #(\"c\", 9)])\n"
" fold(dict, \"\", fn(accumulator, key, value) {\n"
" string.append(accumulator, key)\n"
" })\n"
" // -> \"abc\"\n"
" ```\n"
).
-spec fold(dict(RB, RC), RF, fun((RF, RB, RC) -> RF)) -> RF.
fold(Dict, Initial, Fun) ->
fold_loop(maps:to_list(Dict), Initial, Fun).
-file("src/gleam/dict.gleam", 188).
?DOC(
" Updates all values in a given dict by calling a given function on each key\n"
" and value.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" from_list([#(3, 3), #(2, 4)])\n"
" |> map_values(fn(key, value) { key * value })\n"
" // -> from_list([#(3, 9), #(2, 8)])\n"
" ```\n"
).
-spec map_values(dict(ML, MM), fun((ML, MM) -> MP)) -> dict(ML, MP).
map_values(Dict, Fun) ->
maps:map(Fun, Dict).
-file("src/gleam/dict.gleam", 273).
?DOC(
" Creates a new dict from a given dict, minus any entries that a given function\n"
" returns `False` for.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" from_list([#(\"a\", 0), #(\"b\", 1)])\n"
" |> filter(fn(key, value) { value != 0 })\n"
" // -> from_list([#(\"b\", 1)])\n"
" ```\n"
"\n"
" ```gleam\n"
" from_list([#(\"a\", 0), #(\"b\", 1)])\n"
" |> filter(fn(key, value) { True })\n"
" // -> from_list([#(\"a\", 0), #(\"b\", 1)])\n"
" ```\n"
).
-spec filter(dict(NX, NY), fun((NX, NY) -> boolean())) -> dict(NX, NY).
filter(Dict, Predicate) ->
maps:filter(Predicate, Dict).
-file("src/gleam/dict.gleam", 517).
?DOC(
" Calls a function for each key and value in a dict, discarding the return\n"
" value.\n"
"\n"
" Useful for producing a side effect for every item of a dict.\n"
"\n"
" ```gleam\n"
" import gleam/io\n"
"\n"
" let dict = from_list([#(\"a\", \"apple\"), #(\"b\", \"banana\"), #(\"c\", \"cherry\")])\n"
"\n"
" each(dict, fn(k, v) {\n"
" io.println(key <> \" => \" <> value)\n"
" })\n"
" // -> Nil\n"
" // a => apple\n"
" // b => banana\n"
" // c => cherry\n"
" ```\n"
"\n"
" The order of elements in the iteration is an implementation detail that\n"
" should not be relied upon.\n"
).
-spec each(dict(RK, RL), fun((RK, RL) -> any())) -> nil.
each(Dict, Fun) ->
fold(
Dict,
nil,
fun(Nil, K, V) ->
Fun(K, V),
Nil
end
).
-file("src/gleam/dict.gleam", 538).
?DOC(
" Creates a new dict from a pair of given dicts by combining their entries.\n"
"\n"
" If there are entries with the same keys in both dicts the given function is\n"
" used to determine the new value to use in the resulting dict.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" let a = from_list([#(\"a\", 0), #(\"b\", 1)])\n"
" let b = from_list([#(\"a\", 2), #(\"c\", 3)])\n"
" combine(a, b, fn(one, other) { one + other })\n"
" // -> from_list([#(\"a\", 2), #(\"b\", 1), #(\"c\", 3)])\n"
" ```\n"
).
-spec combine(dict(RP, RQ), dict(RP, RQ), fun((RQ, RQ) -> RQ)) -> dict(RP, RQ).
combine(Dict, Other, Fun) ->
fold(
Dict,
Other,
fun(Acc, Key, Value) -> case gleam_stdlib:map_get(Acc, Key) of
{ok, Other_value} ->
insert(Acc, Key, Fun(Value, Other_value));
{error, _} ->
insert(Acc, Key, Value)
end end
).

View file

@ -0,0 +1,106 @@
-module(gleam@dynamic).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/gleam/dynamic.gleam").
-export([classify/1, bool/1, string/1, float/1, int/1, bit_array/1, list/1, array/1, properties/1, nil/0]).
-export_type([dynamic_/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
-type dynamic_() :: any().
-file("src/gleam/dynamic.gleam", 30).
?DOC(
" Return a string indicating the type of the dynamic value.\n"
"\n"
" This function may be useful for constructing error messages or logs. If you\n"
" want to turn dynamic data into well typed data then you want the\n"
" `gleam/dynamic/decode` module.\n"
"\n"
" ```gleam\n"
" classify(string(\"Hello\"))\n"
" // -> \"String\"\n"
" ```\n"
).
-spec classify(dynamic_()) -> binary().
classify(Data) ->
gleam_stdlib:classify_dynamic(Data).
-file("src/gleam/dynamic.gleam", 36).
?DOC(" Create a dynamic value from a bool.\n").
-spec bool(boolean()) -> dynamic_().
bool(A) ->
gleam_stdlib:identity(A).
-file("src/gleam/dynamic.gleam", 44).
?DOC(
" Create a dynamic value from a string.\n"
"\n"
" On Erlang this will be a binary string rather than a character list.\n"
).
-spec string(binary()) -> dynamic_().
string(A) ->
gleam_stdlib:identity(A).
-file("src/gleam/dynamic.gleam", 50).
?DOC(" Create a dynamic value from a float.\n").
-spec float(float()) -> dynamic_().
float(A) ->
gleam_stdlib:identity(A).
-file("src/gleam/dynamic.gleam", 56).
?DOC(" Create a dynamic value from an int.\n").
-spec int(integer()) -> dynamic_().
int(A) ->
gleam_stdlib:identity(A).
-file("src/gleam/dynamic.gleam", 62).
?DOC(" Create a dynamic value from a bit array.\n").
-spec bit_array(bitstring()) -> dynamic_().
bit_array(A) ->
gleam_stdlib:identity(A).
-file("src/gleam/dynamic.gleam", 68).
?DOC(" Create a dynamic value from a list.\n").
-spec list(list(dynamic_())) -> dynamic_().
list(A) ->
gleam_stdlib:identity(A).
-file("src/gleam/dynamic.gleam", 77).
?DOC(
" Create a dynamic value from a list, converting it to a sequential runtime\n"
" format rather than the regular list format.\n"
"\n"
" On Erlang this will be a tuple, on JavaScript this will be an array.\n"
).
-spec array(list(dynamic_())) -> dynamic_().
array(A) ->
erlang:list_to_tuple(A).
-file("src/gleam/dynamic.gleam", 85).
?DOC(
" Create a dynamic value made an unordered series of keys and values, where\n"
" the keys are unique.\n"
"\n"
" On Erlang this will be a map, on JavaScript this will be a Gleam dict\n"
" object.\n"
).
-spec properties(list({dynamic_(), dynamic_()})) -> dynamic_().
properties(Entries) ->
gleam_stdlib:identity(maps:from_list(Entries)).
-file("src/gleam/dynamic.gleam", 94).
?DOC(
" A dynamic value representing nothing.\n"
"\n"
" On Erlang this will be the atom `nil`, on JavaScript this will be\n"
" `undefined`.\n"
).
-spec nil() -> dynamic_().
nil() ->
gleam_stdlib:identity(nil).

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,744 @@
-module(gleam@float).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/gleam/float.gleam").
-export([parse/1, to_string/1, compare/2, min/2, max/2, clamp/3, ceiling/1, floor/1, truncate/1, absolute_value/1, loosely_compare/3, loosely_equals/3, power/2, square_root/1, negate/1, round/1, to_precision/2, sum/1, product/1, random/0, modulo/2, divide/2, add/2, multiply/2, subtract/2, logarithm/1, exponential/1]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" Functions for working with floats.\n"
"\n"
" ## Float representation\n"
"\n"
" Floats are represented as 64 bit floating point numbers on both the Erlang\n"
" and JavaScript runtimes. The floating point behaviour is native to their\n"
" respective runtimes, so their exact behaviour will be slightly different on\n"
" the two runtimes.\n"
"\n"
" ### Infinity and NaN\n"
"\n"
" Under the JavaScript runtime, exceeding the maximum (or minimum)\n"
" representable value for a floating point value will result in Infinity (or\n"
" -Infinity). Should you try to divide two infinities you will get NaN as a\n"
" result.\n"
"\n"
" When running on BEAM, exceeding the maximum (or minimum) representable\n"
" value for a floating point value will raise an error.\n"
"\n"
" ## Division by zero\n"
"\n"
" Gleam runs on the Erlang virtual machine, which does not follow the IEEE\n"
" 754 standard for floating point arithmetic and does not have an `Infinity`\n"
" value. In Erlang division by zero results in a crash, however Gleam does\n"
" not have partial functions and operators in core so instead division by zero\n"
" returns zero, a behaviour taken from Pony, Coq, and Lean.\n"
"\n"
" This may seem unexpected at first, but it is no less mathematically valid\n"
" than crashing or returning a special value. Division by zero is undefined\n"
" in mathematics.\n"
).
-file("src/gleam/float.gleam", 51).
?DOC(
" Attempts to parse a string as a `Float`, returning `Error(Nil)` if it was\n"
" not possible.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" parse(\"2.3\")\n"
" // -> Ok(2.3)\n"
" ```\n"
"\n"
" ```gleam\n"
" parse(\"ABC\")\n"
" // -> Error(Nil)\n"
" ```\n"
).
-spec parse(binary()) -> {ok, float()} | {error, nil}.
parse(String) ->
gleam_stdlib:parse_float(String).
-file("src/gleam/float.gleam", 64).
?DOC(
" Returns the string representation of the provided `Float`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" to_string(2.3)\n"
" // -> \"2.3\"\n"
" ```\n"
).
-spec to_string(float()) -> binary().
to_string(X) ->
gleam_stdlib:float_to_string(X).
-file("src/gleam/float.gleam", 95).
?DOC(
" Compares two `Float`s, returning an `Order`:\n"
" `Lt` for lower than, `Eq` for equals, or `Gt` for greater than.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" compare(2.0, 2.3)\n"
" // -> Lt\n"
" ```\n"
"\n"
" To handle\n"
" [Floating Point Imprecision](https://en.wikipedia.org/wiki/Floating-point_arithmetic#Accuracy_problems)\n"
" you may use [`loosely_compare`](#loosely_compare) instead.\n"
).
-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.
-file("src/gleam/float.gleam", 176).
?DOC(
" Compares two `Float`s, returning the smaller of the two.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" min(2.0, 2.3)\n"
" // -> 2.0\n"
" ```\n"
).
-spec min(float(), float()) -> float().
min(A, B) ->
case A < B of
true ->
A;
false ->
B
end.
-file("src/gleam/float.gleam", 192).
?DOC(
" Compares two `Float`s, returning the larger of the two.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" max(2.0, 2.3)\n"
" // -> 2.3\n"
" ```\n"
).
-spec max(float(), float()) -> float().
max(A, B) ->
case A > B of
true ->
A;
false ->
B
end.
-file("src/gleam/float.gleam", 75).
?DOC(
" Restricts a `Float` between a lower and upper bound.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" clamp(1.2, min: 1.4, max: 1.6)\n"
" // -> 1.4\n"
" ```\n"
).
-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).
-file("src/gleam/float.gleam", 210).
?DOC(
" Rounds the value to the next highest whole number as a `Float`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" ceiling(2.3)\n"
" // -> 3.0\n"
" ```\n"
).
-spec ceiling(float()) -> float().
ceiling(X) ->
math:ceil(X).
-file("src/gleam/float.gleam", 223).
?DOC(
" Rounds the value to the next lowest whole number as a `Float`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" floor(2.3)\n"
" // -> 2.0\n"
" ```\n"
).
-spec floor(float()) -> float().
floor(X) ->
math:floor(X).
-file("src/gleam/float.gleam", 261).
?DOC(
" Returns the value as an `Int`, truncating all decimal digits.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" truncate(2.4343434847383438)\n"
" // -> 2\n"
" ```\n"
).
-spec truncate(float()) -> integer().
truncate(X) ->
erlang:trunc(X).
-file("src/gleam/float.gleam", 311).
?DOC(
" Returns the absolute value of the input as a `Float`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" absolute_value(-12.5)\n"
" // -> 12.5\n"
" ```\n"
"\n"
" ```gleam\n"
" absolute_value(10.2)\n"
" // -> 10.2\n"
" ```\n"
).
-spec absolute_value(float()) -> float().
absolute_value(X) ->
case X >= +0.0 of
true ->
X;
false ->
+0.0 - X
end.
-file("src/gleam/float.gleam", 125).
?DOC(
" Compares two `Float`s within a tolerance, returning an `Order`:\n"
" `Lt` for lower than, `Eq` for equals, or `Gt` for greater than.\n"
"\n"
" This function allows Float comparison while handling\n"
" [Floating Point Imprecision](https://en.wikipedia.org/wiki/Floating-point_arithmetic#Accuracy_problems).\n"
"\n"
" Notice: For `Float`s the tolerance won't be exact:\n"
" `5.3 - 5.0` is not exactly `0.3`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" loosely_compare(5.0, with: 5.3, tolerating: 0.5)\n"
" // -> Eq\n"
" ```\n"
"\n"
" If you want to check only for equality you may use\n"
" [`loosely_equals`](#loosely_equals) instead.\n"
).
-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.
-file("src/gleam/float.gleam", 158).
?DOC(
" Checks for equality of two `Float`s within a tolerance,\n"
" returning an `Bool`.\n"
"\n"
" This function allows Float comparison while handling\n"
" [Floating Point Imprecision](https://en.wikipedia.org/wiki/Floating-point_arithmetic#Accuracy_problems).\n"
"\n"
" Notice: For `Float`s the tolerance won't be exact:\n"
" `5.3 - 5.0` is not exactly `0.3`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" loosely_equals(5.0, with: 5.3, tolerating: 0.5)\n"
" // -> True\n"
" ```\n"
"\n"
" ```gleam\n"
" loosely_equals(5.0, with: 5.1, tolerating: 0.1)\n"
" // -> False\n"
" ```\n"
).
-spec loosely_equals(float(), float(), float()) -> boolean().
loosely_equals(A, B, Tolerance) ->
Difference = absolute_value(A - B),
Difference =< Tolerance.
-file("src/gleam/float.gleam", 348).
?DOC(
" Returns the results of the base being raised to the power of the\n"
" exponent, as a `Float`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" power(2.0, -1.0)\n"
" // -> Ok(0.5)\n"
" ```\n"
"\n"
" ```gleam\n"
" power(2.0, 2.0)\n"
" // -> Ok(4.0)\n"
" ```\n"
"\n"
" ```gleam\n"
" power(8.0, 1.5)\n"
" // -> Ok(22.627416997969522)\n"
" ```\n"
"\n"
" ```gleam\n"
" 4.0 |> power(of: 2.0)\n"
" // -> Ok(16.0)\n"
" ```\n"
"\n"
" ```gleam\n"
" power(-1.0, 0.5)\n"
" // -> Error(Nil)\n"
" ```\n"
).
-spec power(float(), float()) -> {ok, float()} | {error, nil}.
power(Base, Exponent) ->
Fractional = (math:ceil(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.
-file("src/gleam/float.gleam", 380).
?DOC(
" Returns the square root of the input as a `Float`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" square_root(4.0)\n"
" // -> Ok(2.0)\n"
" ```\n"
"\n"
" ```gleam\n"
" square_root(-16.0)\n"
" // -> Error(Nil)\n"
" ```\n"
).
-spec square_root(float()) -> {ok, float()} | {error, nil}.
square_root(X) ->
power(X, 0.5).
-file("src/gleam/float.gleam", 393).
?DOC(
" Returns the negative of the value provided.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" negate(1.0)\n"
" // -> -1.0\n"
" ```\n"
).
-spec negate(float()) -> float().
negate(X) ->
-1.0 * X.
-file("src/gleam/float.gleam", 240).
?DOC(
" Rounds the value to the nearest whole number as an `Int`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" round(2.3)\n"
" // -> 2\n"
" ```\n"
"\n"
" ```gleam\n"
" round(2.5)\n"
" // -> 3\n"
" ```\n"
).
-spec round(float()) -> integer().
round(X) ->
erlang:round(X).
-file("src/gleam/float.gleam", 280).
?DOC(
" Converts the value to a given precision as a `Float`.\n"
" The precision is the number of allowed decimal places.\n"
" Negative precisions are allowed and force rounding\n"
" to the nearest tenth, hundredth, thousandth etc.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" to_precision(2.43434348473, precision: 2)\n"
" // -> 2.43\n"
" ```\n"
"\n"
" ```gleam\n"
" to_precision(547890.453444, precision: -3)\n"
" // -> 548000.0\n"
" ```\n"
).
-spec to_precision(float(), integer()) -> float().
to_precision(X, Precision) ->
case Precision =< 0 of
true ->
Factor = math:pow(10.0, erlang:float(- Precision)),
erlang:float(erlang:round(case Factor of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator -> X / Gleam@denominator
end)) * Factor;
false ->
Factor@1 = math:pow(10.0, erlang:float(Precision)),
case Factor@1 of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator@1 -> erlang:float(erlang:round(X * Factor@1))
/ Gleam@denominator@1
end
end.
-file("src/gleam/float.gleam", 410).
-spec sum_loop(list(float()), float()) -> float().
sum_loop(Numbers, Initial) ->
case Numbers of
[First | Rest] ->
sum_loop(Rest, First + Initial);
[] ->
Initial
end.
-file("src/gleam/float.gleam", 406).
?DOC(
" Sums a list of `Float`s.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" sum([1.0, 2.2, 3.3])\n"
" // -> 6.5\n"
" ```\n"
).
-spec sum(list(float())) -> float().
sum(Numbers) ->
sum_loop(Numbers, +0.0).
-file("src/gleam/float.gleam", 430).
-spec product_loop(list(float()), float()) -> float().
product_loop(Numbers, Initial) ->
case Numbers of
[First | Rest] ->
product_loop(Rest, First * Initial);
[] ->
Initial
end.
-file("src/gleam/float.gleam", 426).
?DOC(
" Multiplies a list of `Float`s and returns the product.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" product([2.5, 3.2, 4.2])\n"
" // -> 33.6\n"
" ```\n"
).
-spec product(list(float())) -> float().
product(Numbers) ->
product_loop(Numbers, 1.0).
-file("src/gleam/float.gleam", 452).
?DOC(
" Generates a random float between the given zero (inclusive) and one\n"
" (exclusive).\n"
"\n"
" On Erlang this updates the random state in the process dictionary.\n"
" See: <https://www.erlang.org/doc/man/rand.html#uniform-0>\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" random()\n"
" // -> 0.646355926896028\n"
" ```\n"
).
-spec random() -> float().
random() ->
rand:uniform().
-file("src/gleam/float.gleam", 481).
?DOC(
" Computes the modulo of an float division of inputs as a `Result`.\n"
"\n"
" Returns division of the inputs as a `Result`: If the given divisor equals\n"
" `0`, this function returns an `Error`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" modulo(13.3, by: 3.3)\n"
" // -> Ok(0.1)\n"
" ```\n"
"\n"
" ```gleam\n"
" modulo(-13.3, by: 3.3)\n"
" // -> Ok(3.2)\n"
" ```\n"
"\n"
" ```gleam\n"
" modulo(13.3, by: -3.3)\n"
" // -> Ok(-3.2)\n"
" ```\n"
"\n"
" ```gleam\n"
" modulo(-13.3, by: -3.3)\n"
" // -> Ok(-0.1)\n"
" ```\n"
).
-spec modulo(float(), float()) -> {ok, float()} | {error, nil}.
modulo(Dividend, Divisor) ->
case Divisor of
+0.0 ->
{error, nil};
_ ->
{ok, Dividend - (math:floor(case Divisor of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator -> Dividend / Gleam@denominator
end) * Divisor)}
end.
-file("src/gleam/float.gleam", 502).
?DOC(
" Returns division of the inputs as a `Result`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" divide(0.0, 1.0)\n"
" // -> Ok(0.0)\n"
" ```\n"
"\n"
" ```gleam\n"
" divide(1.0, 0.0)\n"
" // -> Error(Nil)\n"
" ```\n"
).
-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.
-file("src/gleam/float.gleam", 533).
?DOC(
" Adds two floats together.\n"
"\n"
" It's the function equivalent of the `+.` operator.\n"
" This function is useful in higher order functions or pipes.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" add(1.0, 2.0)\n"
" // -> 3.0\n"
" ```\n"
"\n"
" ```gleam\n"
" import gleam/list\n"
"\n"
" list.fold([1.0, 2.0, 3.0], 0.0, add)\n"
" // -> 6.0\n"
" ```\n"
"\n"
" ```gleam\n"
" 3.0 |> add(2.0)\n"
" // -> 5.0\n"
" ```\n"
).
-spec add(float(), float()) -> float().
add(A, B) ->
A + B.
-file("src/gleam/float.gleam", 561).
?DOC(
" Multiplies two floats together.\n"
"\n"
" It's the function equivalent of the `*.` operator.\n"
" This function is useful in higher order functions or pipes.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" multiply(2.0, 4.0)\n"
" // -> 8.0\n"
" ```\n"
"\n"
" ```gleam\n"
" import gleam/list\n"
"\n"
" list.fold([2.0, 3.0, 4.0], 1.0, multiply)\n"
" // -> 24.0\n"
" ```\n"
"\n"
" ```gleam\n"
" 3.0 |> multiply(2.0)\n"
" // -> 6.0\n"
" ```\n"
).
-spec multiply(float(), float()) -> float().
multiply(A, B) ->
A * B.
-file("src/gleam/float.gleam", 594).
?DOC(
" Subtracts one float from another.\n"
"\n"
" It's the function equivalent of the `-.` operator.\n"
" This function is useful in higher order functions or pipes.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" subtract(3.0, 1.0)\n"
" // -> 2.0\n"
" ```\n"
"\n"
" ```gleam\n"
" import gleam/list\n"
"\n"
" list.fold([1.0, 2.0, 3.0], 10.0, subtract)\n"
" // -> 4.0\n"
" ```\n"
"\n"
" ```gleam\n"
" 3.0 |> subtract(_, 2.0)\n"
" // -> 1.0\n"
" ```\n"
"\n"
" ```gleam\n"
" 3.0 |> subtract(2.0, _)\n"
" // -> -1.0\n"
" ```\n"
).
-spec subtract(float(), float()) -> float().
subtract(A, B) ->
A - B.
-file("src/gleam/float.gleam", 623).
?DOC(
" Returns the natural logarithm (base e) of the given as a `Result`. If the\n"
" input is less than or equal to 0, returns `Error(Nil)`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" logarithm(1.0)\n"
" // -> Ok(0.0)\n"
" ```\n"
"\n"
" ```gleam\n"
" logarithm(2.718281828459045) // e\n"
" // -> Ok(1.0)\n"
" ```\n"
"\n"
" ```gleam\n"
" logarithm(0.0)\n"
" // -> Error(Nil)\n"
" ```\n"
"\n"
" ```gleam\n"
" logarithm(-1.0)\n"
" // -> Error(Nil)\n"
" ```\n"
).
-spec logarithm(float()) -> {ok, float()} | {error, nil}.
logarithm(X) ->
case X =< +0.0 of
true ->
{error, nil};
false ->
{ok, math:log(X)}
end.
-file("src/gleam/float.gleam", 661).
?DOC(
" Returns e (Euler's number) raised to the power of the given exponent, as\n"
" a `Float`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" exponential(0.0)\n"
" // -> Ok(1.0)\n"
" ```\n"
"\n"
" ```gleam\n"
" exponential(1.0)\n"
" // -> Ok(2.718281828459045)\n"
" ```\n"
"\n"
" ```gleam\n"
" exponential(-1.0)\n"
" // -> Ok(0.36787944117144233)\n"
" ```\n"
).
-spec exponential(float()) -> float().
exponential(X) ->
math:exp(X).

View file

@ -0,0 +1,30 @@
-module(gleam@function).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/gleam/function.gleam").
-export([identity/1, tap/2]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
-file("src/gleam/function.gleam", 3).
?DOC(" Takes a single argument and always returns its input value.\n").
-spec identity(CLA) -> CLA.
identity(X) ->
X.
-file("src/gleam/function.gleam", 12).
?DOC(
" Takes an argument and a single function, calls that function with that\n"
" argument and returns that argument instead of the function return value.\n"
"\n"
" Useful for running synchronous side effects in a pipeline.\n"
).
-spec tap(CLB, fun((CLB) -> any())) -> CLB.
tap(Arg, Effect) ->
Effect(Arg),
Arg.

View file

@ -0,0 +1,986 @@
-module(gleam@int).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/gleam/int.gleam").
-export([absolute_value/1, parse/1, base_parse/2, to_string/1, to_base_string/2, to_base2/1, to_base8/1, to_base16/1, to_base36/1, to_float/1, power/2, square_root/1, compare/2, min/2, max/2, clamp/3, is_even/1, is_odd/1, negate/1, sum/1, product/1, digits/2, undigits/2, random/1, divide/2, remainder/2, modulo/2, floor_divide/2, add/2, multiply/2, subtract/2, bitwise_and/2, bitwise_not/1, bitwise_or/2, bitwise_exclusive_or/2, bitwise_shift_left/2, bitwise_shift_right/2]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" Functions for working with integers.\n"
"\n"
" ## Division by zero\n"
"\n"
" In Erlang division by zero results in a crash, however Gleam does not have\n"
" partial functions and operators in core so instead division by zero returns\n"
" zero, a behaviour taken from Pony, Coq, and Lean.\n"
"\n"
" This may seem unexpected at first, but it is no less mathematically valid\n"
" than crashing or returning a special value. Division by zero is undefined\n"
" in mathematics.\n"
).
-file("src/gleam/int.gleam", 30).
?DOC(
" Returns the absolute value of the input.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" absolute_value(-12)\n"
" // -> 12\n"
" ```\n"
"\n"
" ```gleam\n"
" absolute_value(10)\n"
" // -> 10\n"
" ```\n"
).
-spec absolute_value(integer()) -> integer().
absolute_value(X) ->
case X >= 0 of
true ->
X;
false ->
X * -1
end.
-file("src/gleam/int.gleam", 109).
?DOC(
" Parses a given string as an int if possible.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" parse(\"2\")\n"
" // -> Ok(2)\n"
" ```\n"
"\n"
" ```gleam\n"
" parse(\"ABC\")\n"
" // -> Error(Nil)\n"
" ```\n"
).
-spec parse(binary()) -> {ok, integer()} | {error, nil}.
parse(String) ->
gleam_stdlib:parse_int(String).
-file("src/gleam/int.gleam", 141).
?DOC(
" Parses a given string as an int in a given base if possible.\n"
" Supports only bases 2 to 36, for values outside of which this function returns an `Error(Nil)`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" base_parse(\"10\", 2)\n"
" // -> Ok(2)\n"
" ```\n"
"\n"
" ```gleam\n"
" base_parse(\"30\", 16)\n"
" // -> Ok(48)\n"
" ```\n"
"\n"
" ```gleam\n"
" base_parse(\"1C\", 36)\n"
" // -> Ok(48)\n"
" ```\n"
"\n"
" ```gleam\n"
" base_parse(\"48\", 1)\n"
" // -> Error(Nil)\n"
" ```\n"
"\n"
" ```gleam\n"
" base_parse(\"48\", 37)\n"
" // -> Error(Nil)\n"
" ```\n"
).
-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.
-file("src/gleam/int.gleam", 163).
?DOC(
" Prints a given int to a string.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" to_string(2)\n"
" // -> \"2\"\n"
" ```\n"
).
-spec to_string(integer()) -> binary().
to_string(X) ->
erlang:integer_to_binary(X).
-file("src/gleam/int.gleam", 196).
?DOC(
" Prints a given int to a string using the base number provided.\n"
" Supports only bases 2 to 36, for values outside of which this function returns an `Error(Nil)`.\n"
" For common bases (2, 8, 16, 36), use the `to_baseN` functions.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" to_base_string(2, 2)\n"
" // -> Ok(\"10\")\n"
" ```\n"
"\n"
" ```gleam\n"
" to_base_string(48, 16)\n"
" // -> Ok(\"30\")\n"
" ```\n"
"\n"
" ```gleam\n"
" to_base_string(48, 36)\n"
" // -> Ok(\"1C\")\n"
" ```\n"
"\n"
" ```gleam\n"
" to_base_string(48, 1)\n"
" // -> Error(Nil)\n"
" ```\n"
"\n"
" ```gleam\n"
" to_base_string(48, 37)\n"
" // -> Error(Nil)\n"
" ```\n"
).
-spec to_base_string(integer(), integer()) -> {ok, binary()} | {error, nil}.
to_base_string(X, Base) ->
case (Base >= 2) andalso (Base =< 36) of
true ->
{ok, erlang:integer_to_binary(X, Base)};
false ->
{error, nil}
end.
-file("src/gleam/int.gleam", 216).
?DOC(
" Prints a given int to a string using base-2.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" to_base2(2)\n"
" // -> \"10\"\n"
" ```\n"
).
-spec to_base2(integer()) -> binary().
to_base2(X) ->
erlang:integer_to_binary(X, 2).
-file("src/gleam/int.gleam", 229).
?DOC(
" Prints a given int to a string using base-8.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" to_base8(15)\n"
" // -> \"17\"\n"
" ```\n"
).
-spec to_base8(integer()) -> binary().
to_base8(X) ->
erlang:integer_to_binary(X, 8).
-file("src/gleam/int.gleam", 242).
?DOC(
" Prints a given int to a string using base-16.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" to_base16(48)\n"
" // -> \"30\"\n"
" ```\n"
).
-spec to_base16(integer()) -> binary().
to_base16(X) ->
erlang:integer_to_binary(X, 16).
-file("src/gleam/int.gleam", 255).
?DOC(
" Prints a given int to a string using base-36.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" to_base36(48)\n"
" // -> \"1C\"\n"
" ```\n"
).
-spec to_base36(integer()) -> binary().
to_base36(X) ->
erlang:integer_to_binary(X, 36).
-file("src/gleam/int.gleam", 280).
?DOC(
" Takes an int and returns its value as a float.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" to_float(5)\n"
" // -> 5.0\n"
" ```\n"
"\n"
" ```gleam\n"
" to_float(0)\n"
" // -> 0.0\n"
" ```\n"
"\n"
" ```gleam\n"
" to_float(-3)\n"
" // -> -3.0\n"
" ```\n"
).
-spec to_float(integer()) -> float().
to_float(X) ->
erlang:float(X).
-file("src/gleam/int.gleam", 67).
?DOC(
" Returns the results of the base being raised to the power of the\n"
" exponent, as a `Float`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" power(2, -1.0)\n"
" // -> Ok(0.5)\n"
" ```\n"
"\n"
" ```gleam\n"
" power(2, 2.0)\n"
" // -> Ok(4.0)\n"
" ```\n"
"\n"
" ```gleam\n"
" power(8, 1.5)\n"
" // -> Ok(22.627416997969522)\n"
" ```\n"
"\n"
" ```gleam\n"
" 4 |> power(of: 2.0)\n"
" // -> Ok(16.0)\n"
" ```\n"
"\n"
" ```gleam\n"
" power(-1, 0.5)\n"
" // -> Error(Nil)\n"
" ```\n"
).
-spec power(integer(), float()) -> {ok, float()} | {error, nil}.
power(Base, Exponent) ->
_pipe = Base,
_pipe@1 = erlang:float(_pipe),
gleam@float:power(_pipe@1, Exponent).
-file("src/gleam/int.gleam", 87).
?DOC(
" Returns the square root of the input as a `Float`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" square_root(4)\n"
" // -> Ok(2.0)\n"
" ```\n"
"\n"
" ```gleam\n"
" square_root(-16)\n"
" // -> Error(Nil)\n"
" ```\n"
).
-spec square_root(integer()) -> {ok, float()} | {error, nil}.
square_root(X) ->
_pipe = X,
_pipe@1 = erlang:float(_pipe),
gleam@float:square_root(_pipe@1).
-file("src/gleam/int.gleam", 316).
?DOC(
" Compares two ints, returning an order.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" compare(2, 3)\n"
" // -> Lt\n"
" ```\n"
"\n"
" ```gleam\n"
" compare(4, 3)\n"
" // -> Gt\n"
" ```\n"
"\n"
" ```gleam\n"
" compare(3, 3)\n"
" // -> Eq\n"
" ```\n"
).
-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.
-file("src/gleam/int.gleam", 336).
?DOC(
" Compares two ints, returning the smaller of the two.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" min(2, 3)\n"
" // -> 2\n"
" ```\n"
).
-spec min(integer(), integer()) -> integer().
min(A, B) ->
case A < B of
true ->
A;
false ->
B
end.
-file("src/gleam/int.gleam", 352).
?DOC(
" Compares two ints, returning the larger of the two.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" max(2, 3)\n"
" // -> 3\n"
" ```\n"
).
-spec max(integer(), integer()) -> integer().
max(A, B) ->
case A > B of
true ->
A;
false ->
B
end.
-file("src/gleam/int.gleam", 291).
?DOC(
" Restricts an int between a lower and upper bound.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" clamp(40, min: 50, max: 60)\n"
" // -> 50\n"
" ```\n"
).
-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).
-file("src/gleam/int.gleam", 373).
?DOC(
" Returns whether the value provided is even.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" is_even(2)\n"
" // -> True\n"
" ```\n"
"\n"
" ```gleam\n"
" is_even(3)\n"
" // -> False\n"
" ```\n"
).
-spec is_even(integer()) -> boolean().
is_even(X) ->
(X rem 2) =:= 0.
-file("src/gleam/int.gleam", 391).
?DOC(
" Returns whether the value provided is odd.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" is_odd(3)\n"
" // -> True\n"
" ```\n"
"\n"
" ```gleam\n"
" is_odd(2)\n"
" // -> False\n"
" ```\n"
).
-spec is_odd(integer()) -> boolean().
is_odd(X) ->
(X rem 2) /= 0.
-file("src/gleam/int.gleam", 404).
?DOC(
" Returns the negative of the value provided.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" negate(1)\n"
" // -> -1\n"
" ```\n"
).
-spec negate(integer()) -> integer().
negate(X) ->
-1 * X.
-file("src/gleam/int.gleam", 421).
-spec sum_loop(list(integer()), integer()) -> integer().
sum_loop(Numbers, Initial) ->
case Numbers of
[First | Rest] ->
sum_loop(Rest, First + Initial);
[] ->
Initial
end.
-file("src/gleam/int.gleam", 417).
?DOC(
" Sums a list of ints.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" sum([1, 2, 3])\n"
" // -> 6\n"
" ```\n"
).
-spec sum(list(integer())) -> integer().
sum(Numbers) ->
sum_loop(Numbers, 0).
-file("src/gleam/int.gleam", 441).
-spec product_loop(list(integer()), integer()) -> integer().
product_loop(Numbers, Initial) ->
case Numbers of
[First | Rest] ->
product_loop(Rest, First * Initial);
[] ->
Initial
end.
-file("src/gleam/int.gleam", 437).
?DOC(
" Multiplies a list of ints and returns the product.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" product([2, 3, 4])\n"
" // -> 24\n"
" ```\n"
).
-spec product(list(integer())) -> integer().
product(Numbers) ->
product_loop(Numbers, 1).
-file("src/gleam/int.gleam", 456).
-spec digits_loop(integer(), integer(), list(integer())) -> list(integer()).
digits_loop(X, Base, Acc) ->
case absolute_value(X) < Base of
true ->
[X | Acc];
false ->
digits_loop(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.
-file("src/gleam/int.gleam", 449).
-spec digits(integer(), integer()) -> {ok, list(integer())} | {error, nil}.
digits(X, Base) ->
case Base < 2 of
true ->
{error, nil};
false ->
{ok, digits_loop(X, Base, [])}
end.
-file("src/gleam/int.gleam", 471).
-spec undigits_loop(list(integer()), integer(), integer()) -> {ok, integer()} |
{error, nil}.
undigits_loop(Numbers, Base, Acc) ->
case Numbers of
[] ->
{ok, Acc};
[Digit | _] when Digit >= Base ->
{error, nil};
[Digit@1 | Rest] ->
undigits_loop(Rest, Base, (Acc * Base) + Digit@1)
end.
-file("src/gleam/int.gleam", 464).
-spec undigits(list(integer()), integer()) -> {ok, integer()} | {error, nil}.
undigits(Numbers, Base) ->
case Base < 2 of
true ->
{error, nil};
false ->
undigits_loop(Numbers, Base, 0)
end.
-file("src/gleam/int.gleam", 500).
?DOC(
" Generates a random int between zero and the given maximum.\n"
"\n"
" The lower number is inclusive, the upper number is exclusive.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" random(10)\n"
" // -> 4\n"
" ```\n"
"\n"
" ```gleam\n"
" random(1)\n"
" // -> 0\n"
" ```\n"
"\n"
" ```gleam\n"
" random(-1)\n"
" // -> -1\n"
" ```\n"
).
-spec random(integer()) -> integer().
random(Max) ->
_pipe = (rand:uniform() * erlang:float(Max)),
_pipe@1 = math:floor(_pipe),
erlang:round(_pipe@1).
-file("src/gleam/int.gleam", 533).
?DOC(
" Performs a truncated integer division.\n"
"\n"
" Returns division of the inputs as a `Result`: If the given divisor equals\n"
" `0`, this function returns an `Error`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" divide(0, 1)\n"
" // -> Ok(0)\n"
" ```\n"
"\n"
" ```gleam\n"
" divide(1, 0)\n"
" // -> Error(Nil)\n"
" ```\n"
"\n"
" ```gleam\n"
" divide(5, 2)\n"
" // -> Ok(2)\n"
" ```\n"
"\n"
" ```gleam\n"
" divide(-99, 2)\n"
" // -> Ok(-49)\n"
" ```\n"
).
-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.
-file("src/gleam/int.gleam", 585).
?DOC(
" Computes the remainder of an integer division of inputs as a `Result`.\n"
"\n"
" Returns division of the inputs as a `Result`: If the given divisor equals\n"
" `0`, this function returns an `Error`.\n"
"\n"
" Most the time you will want to use the `%` operator instead of this\n"
" function.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" remainder(3, 2)\n"
" // -> Ok(1)\n"
" ```\n"
"\n"
" ```gleam\n"
" remainder(1, 0)\n"
" // -> Error(Nil)\n"
" ```\n"
"\n"
" ```gleam\n"
" remainder(10, -1)\n"
" // -> Ok(0)\n"
" ```\n"
"\n"
" ```gleam\n"
" remainder(13, by: 3)\n"
" // -> Ok(1)\n"
" ```\n"
"\n"
" ```gleam\n"
" remainder(-13, by: 3)\n"
" // -> Ok(-1)\n"
" ```\n"
"\n"
" ```gleam\n"
" remainder(13, by: -3)\n"
" // -> Ok(1)\n"
" ```\n"
"\n"
" ```gleam\n"
" remainder(-13, by: -3)\n"
" // -> Ok(-1)\n"
" ```\n"
).
-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.
-file("src/gleam/int.gleam", 627).
?DOC(
" Computes the modulo of an integer division of inputs as a `Result`.\n"
"\n"
" Returns division of the inputs as a `Result`: If the given divisor equals\n"
" `0`, this function returns an `Error`.\n"
"\n"
" Most the time you will want to use the `%` operator instead of this\n"
" function.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" modulo(3, 2)\n"
" // -> Ok(1)\n"
" ```\n"
"\n"
" ```gleam\n"
" modulo(1, 0)\n"
" // -> Error(Nil)\n"
" ```\n"
"\n"
" ```gleam\n"
" modulo(10, -1)\n"
" // -> Ok(0)\n"
" ```\n"
"\n"
" ```gleam\n"
" modulo(13, by: 3)\n"
" // -> Ok(1)\n"
" ```\n"
"\n"
" ```gleam\n"
" modulo(-13, by: 3)\n"
" // -> Ok(2)\n"
" ```\n"
).
-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.
-file("src/gleam/int.gleam", 671).
?DOC(
" Performs a *floored* integer division, which means that the result will\n"
" always be rounded towards negative infinity.\n"
"\n"
" If you want to perform truncated integer division (rounding towards zero),\n"
" use `int.divide()` or the `/` operator instead.\n"
"\n"
" Returns division of the inputs as a `Result`: If the given divisor equals\n"
" `0`, this function returns an `Error`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" floor_divide(1, 0)\n"
" // -> Error(Nil)\n"
" ```\n"
"\n"
" ```gleam\n"
" floor_divide(5, 2)\n"
" // -> Ok(2)\n"
" ```\n"
"\n"
" ```gleam\n"
" floor_divide(6, -4)\n"
" // -> Ok(-2)\n"
" ```\n"
"\n"
" ```gleam\n"
" floor_divide(-99, 2)\n"
" // -> Ok(-50)\n"
" ```\n"
).
-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.
-file("src/gleam/int.gleam", 705).
?DOC(
" Adds two integers together.\n"
"\n"
" It's the function equivalent of the `+` operator.\n"
" This function is useful in higher order functions or pipes.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" add(1, 2)\n"
" // -> 3\n"
" ```\n"
"\n"
" ```gleam\n"
" import gleam/list\n"
" list.fold([1, 2, 3], 0, add)\n"
" // -> 6\n"
" ```\n"
"\n"
" ```gleam\n"
" 3 |> add(2)\n"
" // -> 5\n"
" ```\n"
).
-spec add(integer(), integer()) -> integer().
add(A, B) ->
A + B.
-file("src/gleam/int.gleam", 733).
?DOC(
" Multiplies two integers together.\n"
"\n"
" It's the function equivalent of the `*` operator.\n"
" This function is useful in higher order functions or pipes.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" multiply(2, 4)\n"
" // -> 8\n"
" ```\n"
"\n"
" ```gleam\n"
" import gleam/list\n"
"\n"
" list.fold([2, 3, 4], 1, multiply)\n"
" // -> 24\n"
" ```\n"
"\n"
" ```gleam\n"
" 3 |> multiply(2)\n"
" // -> 6\n"
" ```\n"
).
-spec multiply(integer(), integer()) -> integer().
multiply(A, B) ->
A * B.
-file("src/gleam/int.gleam", 766).
?DOC(
" Subtracts one int from another.\n"
"\n"
" It's the function equivalent of the `-` operator.\n"
" This function is useful in higher order functions or pipes.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" subtract(3, 1)\n"
" // -> 2\n"
" ```\n"
"\n"
" ```gleam\n"
" import gleam/list\n"
"\n"
" list.fold([1, 2, 3], 10, subtract)\n"
" // -> 4\n"
" ```\n"
"\n"
" ```gleam\n"
" 3 |> subtract(2)\n"
" // -> 1\n"
" ```\n"
"\n"
" ```gleam\n"
" 3 |> subtract(2, _)\n"
" // -> -1\n"
" ```\n"
).
-spec subtract(integer(), integer()) -> integer().
subtract(A, B) ->
A - B.
-file("src/gleam/int.gleam", 778).
?DOC(
" Calculates the bitwise AND of its arguments.\n"
"\n"
" The exact behaviour of this function depends on the target platform.\n"
" On Erlang it is equivalent to bitwise operations on ints, on JavaScript it\n"
" is equivalent to bitwise operations on big-ints.\n"
).
-spec bitwise_and(integer(), integer()) -> integer().
bitwise_and(X, Y) ->
erlang:'band'(X, Y).
-file("src/gleam/int.gleam", 788).
?DOC(
" Calculates the bitwise NOT of its argument.\n"
"\n"
" The exact behaviour of this function depends on the target platform.\n"
" On Erlang it is equivalent to bitwise operations on ints, on JavaScript it\n"
" is equivalent to bitwise operations on big-ints.\n"
).
-spec bitwise_not(integer()) -> integer().
bitwise_not(X) ->
erlang:'bnot'(X).
-file("src/gleam/int.gleam", 798).
?DOC(
" Calculates the bitwise OR of its arguments.\n"
"\n"
" The exact behaviour of this function depends on the target platform.\n"
" On Erlang it is equivalent to bitwise operations on ints, on JavaScript it\n"
" is equivalent to bitwise operations on big-ints.\n"
).
-spec bitwise_or(integer(), integer()) -> integer().
bitwise_or(X, Y) ->
erlang:'bor'(X, Y).
-file("src/gleam/int.gleam", 808).
?DOC(
" Calculates the bitwise XOR of its arguments.\n"
"\n"
" The exact behaviour of this function depends on the target platform.\n"
" On Erlang it is equivalent to bitwise operations on ints, on JavaScript it\n"
" is equivalent to bitwise operations on big-ints.\n"
).
-spec bitwise_exclusive_or(integer(), integer()) -> integer().
bitwise_exclusive_or(X, Y) ->
erlang:'bxor'(X, Y).
-file("src/gleam/int.gleam", 818).
?DOC(
" Calculates the result of an arithmetic left bitshift.\n"
"\n"
" The exact behaviour of this function depends on the target platform.\n"
" On Erlang it is equivalent to bitwise operations on ints, on JavaScript it\n"
" is equivalent to bitwise operations on big-ints.\n"
).
-spec bitwise_shift_left(integer(), integer()) -> integer().
bitwise_shift_left(X, Y) ->
erlang:'bsl'(X, Y).
-file("src/gleam/int.gleam", 828).
?DOC(
" Calculates the result of an arithmetic right bitshift.\n"
"\n"
" The exact behaviour of this function depends on the target platform.\n"
" On Erlang it is equivalent to bitwise operations on ints, on JavaScript it\n"
" is equivalent to bitwise operations on big-ints.\n"
).
-spec bitwise_shift_right(integer(), integer()) -> integer().
bitwise_shift_right(X, Y) ->
erlang:'bsr'(X, Y).

View file

@ -0,0 +1,80 @@
-module(gleam@io).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/gleam/io.gleam").
-export([print/1, print_error/1, println/1, println_error/1]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
-file("src/gleam/io.gleam", 15).
?DOC(
" Writes a string to standard output (stdout).\n"
"\n"
" If you want your output to be printed on its own line see `println`.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" io.print(\"Hi mum\")\n"
" // -> Nil\n"
" // Hi mum\n"
" ```\n"
).
-spec print(binary()) -> nil.
print(String) ->
gleam_stdlib:print(String).
-file("src/gleam/io.gleam", 31).
?DOC(
" Writes a string to standard error (stderr).\n"
"\n"
" If you want your output to be printed on its own line see `println_error`.\n"
"\n"
" ## Example\n"
"\n"
" ```\n"
" io.print_error(\"Hi pop\")\n"
" // -> Nil\n"
" // Hi pop\n"
" ```\n"
).
-spec print_error(binary()) -> nil.
print_error(String) ->
gleam_stdlib:print_error(String).
-file("src/gleam/io.gleam", 45).
?DOC(
" Writes a string to standard output (stdout), appending a newline to the end.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" io.println(\"Hi mum\")\n"
" // -> Nil\n"
" // Hi mum\n"
" ```\n"
).
-spec println(binary()) -> nil.
println(String) ->
gleam_stdlib:println(String).
-file("src/gleam/io.gleam", 59).
?DOC(
" Writes a string to standard error (stderr), appending a newline to the end.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" io.println_error(\"Hi pop\")\n"
" // -> Nil\n"
" // Hi pop\n"
" ```\n"
).
-spec println_error(binary()) -> nil.
println_error(String) ->
gleam_stdlib:println_error(String).

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,413 @@
-module(gleam@option).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/gleam/option.gleam").
-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]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
-type option(FG) :: {some, FG} | none.
-file("src/gleam/option.gleam", 59).
-spec reverse_and_prepend(list(FV), list(FV)) -> list(FV).
reverse_and_prepend(Prefix, Suffix) ->
case Prefix of
[] ->
Suffix;
[First | Rest] ->
reverse_and_prepend(Rest, [First | Suffix])
end.
-file("src/gleam/option.gleam", 44).
-spec all_loop(list(option(FM)), list(FM)) -> option(list(FM)).
all_loop(List, Acc) ->
case List of
[] ->
{some, lists:reverse(Acc)};
[none | _] ->
none;
[{some, First} | Rest] ->
all_loop(Rest, [First | Acc])
end.
-file("src/gleam/option.gleam", 40).
?DOC(
" Combines a list of `Option`s into a single `Option`.\n"
" If all elements in the list are `Some` then returns a `Some` holding the list of values.\n"
" If any element is `None` then returns`None`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" all([Some(1), Some(2)])\n"
" // -> Some([1, 2])\n"
" ```\n"
"\n"
" ```gleam\n"
" all([Some(1), None])\n"
" // -> None\n"
" ```\n"
).
-spec all(list(option(FH))) -> option(list(FH)).
all(List) ->
all_loop(List, []).
-file("src/gleam/option.gleam", 80).
?DOC(
" Checks whether the `Option` is a `Some` value.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" is_some(Some(1))\n"
" // -> True\n"
" ```\n"
"\n"
" ```gleam\n"
" is_some(None)\n"
" // -> False\n"
" ```\n"
).
-spec is_some(option(any())) -> boolean().
is_some(Option) ->
Option /= none.
-file("src/gleam/option.gleam", 98).
?DOC(
" Checks whether the `Option` is a `None` value.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" is_none(Some(1))\n"
" // -> False\n"
" ```\n"
"\n"
" ```gleam\n"
" is_none(None)\n"
" // -> True\n"
" ```\n"
).
-spec is_none(option(any())) -> boolean().
is_none(Option) ->
Option =:= none.
-file("src/gleam/option.gleam", 116).
?DOC(
" Converts an `Option` type to a `Result` type.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" to_result(Some(1), \"some_error\")\n"
" // -> Ok(1)\n"
" ```\n"
"\n"
" ```gleam\n"
" to_result(None, \"some_error\")\n"
" // -> Error(\"some_error\")\n"
" ```\n"
).
-spec to_result(option(GD), GG) -> {ok, GD} | {error, GG}.
to_result(Option, E) ->
case Option of
{some, A} ->
{ok, A};
none ->
{error, E}
end.
-file("src/gleam/option.gleam", 137).
?DOC(
" Converts a `Result` type to an `Option` type.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" from_result(Ok(1))\n"
" // -> Some(1)\n"
" ```\n"
"\n"
" ```gleam\n"
" from_result(Error(\"some_error\"))\n"
" // -> None\n"
" ```\n"
).
-spec from_result({ok, GJ} | {error, any()}) -> option(GJ).
from_result(Result) ->
case Result of
{ok, A} ->
{some, A};
{error, _} ->
none
end.
-file("src/gleam/option.gleam", 158).
?DOC(
" Extracts the value from an `Option`, returning a default value if there is none.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" unwrap(Some(1), 0)\n"
" // -> 1\n"
" ```\n"
"\n"
" ```gleam\n"
" unwrap(None, 0)\n"
" // -> 0\n"
" ```\n"
).
-spec unwrap(option(GO), GO) -> GO.
unwrap(Option, Default) ->
case Option of
{some, X} ->
X;
none ->
Default
end.
-file("src/gleam/option.gleam", 179).
?DOC(
" Extracts the value from an `Option`, evaluating the default function if the option is `None`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" lazy_unwrap(Some(1), fn() { 0 })\n"
" // -> 1\n"
" ```\n"
"\n"
" ```gleam\n"
" lazy_unwrap(None, fn() { 0 })\n"
" // -> 0\n"
" ```\n"
).
-spec lazy_unwrap(option(GQ), fun(() -> GQ)) -> GQ.
lazy_unwrap(Option, Default) ->
case Option of
{some, X} ->
X;
none ->
Default()
end.
-file("src/gleam/option.gleam", 204).
?DOC(
" Updates a value held within the `Some` of an `Option` by calling a given function\n"
" on it.\n"
"\n"
" If the `Option` is a `None` rather than `Some`, the function is not called and the\n"
" `Option` stays the same.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" map(over: Some(1), with: fn(x) { x + 1 })\n"
" // -> Some(2)\n"
" ```\n"
"\n"
" ```gleam\n"
" map(over: None, with: fn(x) { x + 1 })\n"
" // -> None\n"
" ```\n"
).
-spec map(option(GS), fun((GS) -> GU)) -> option(GU).
map(Option, Fun) ->
case Option of
{some, X} ->
{some, Fun(X)};
none ->
none
end.
-file("src/gleam/option.gleam", 230).
?DOC(
" Merges a nested `Option` into a single layer.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" flatten(Some(Some(1)))\n"
" // -> Some(1)\n"
" ```\n"
"\n"
" ```gleam\n"
" flatten(Some(None))\n"
" // -> None\n"
" ```\n"
"\n"
" ```gleam\n"
" flatten(None)\n"
" // -> None\n"
" ```\n"
).
-spec flatten(option(option(GW))) -> option(GW).
flatten(Option) ->
case Option of
{some, X} ->
X;
none ->
none
end.
-file("src/gleam/option.gleam", 269).
?DOC(
" Updates a value held within the `Some` of an `Option` by calling a given function\n"
" on it, where the given function also returns an `Option`. The two options are\n"
" then merged together into one `Option`.\n"
"\n"
" If the `Option` is a `None` rather than `Some` the function is not called and the\n"
" option stays the same.\n"
"\n"
" This function is the equivalent of calling `map` followed by `flatten`, and\n"
" it is useful for chaining together multiple functions that return `Option`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" then(Some(1), fn(x) { Some(x + 1) })\n"
" // -> Some(2)\n"
" ```\n"
"\n"
" ```gleam\n"
" then(Some(1), fn(x) { Some(#(\"a\", x)) })\n"
" // -> Some(#(\"a\", 1))\n"
" ```\n"
"\n"
" ```gleam\n"
" then(Some(1), fn(_) { None })\n"
" // -> None\n"
" ```\n"
"\n"
" ```gleam\n"
" then(None, fn(x) { Some(x + 1) })\n"
" // -> None\n"
" ```\n"
).
-spec then(option(HA), fun((HA) -> option(HC))) -> option(HC).
then(Option, Fun) ->
case Option of
{some, X} ->
Fun(X);
none ->
none
end.
-file("src/gleam/option.gleam", 300).
?DOC(
" Returns the first value if it is `Some`, otherwise returns the second value.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" or(Some(1), Some(2))\n"
" // -> Some(1)\n"
" ```\n"
"\n"
" ```gleam\n"
" or(Some(1), None)\n"
" // -> Some(1)\n"
" ```\n"
"\n"
" ```gleam\n"
" or(None, Some(2))\n"
" // -> Some(2)\n"
" ```\n"
"\n"
" ```gleam\n"
" or(None, None)\n"
" // -> None\n"
" ```\n"
).
-spec 'or'(option(HF), option(HF)) -> option(HF).
'or'(First, Second) ->
case First of
{some, _} ->
First;
none ->
Second
end.
-file("src/gleam/option.gleam", 331).
?DOC(
" Returns the first value if it is `Some`, otherwise evaluates the given function for a fallback value.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" lazy_or(Some(1), fn() { Some(2) })\n"
" // -> Some(1)\n"
" ```\n"
"\n"
" ```gleam\n"
" lazy_or(Some(1), fn() { None })\n"
" // -> Some(1)\n"
" ```\n"
"\n"
" ```gleam\n"
" lazy_or(None, fn() { Some(2) })\n"
" // -> Some(2)\n"
" ```\n"
"\n"
" ```gleam\n"
" lazy_or(None, fn() { None })\n"
" // -> None\n"
" ```\n"
).
-spec lazy_or(option(HJ), fun(() -> option(HJ))) -> option(HJ).
lazy_or(First, Second) ->
case First of
{some, _} ->
First;
none ->
Second()
end.
-file("src/gleam/option.gleam", 352).
-spec values_loop(list(option(HR)), list(HR)) -> list(HR).
values_loop(List, Acc) ->
case List of
[] ->
lists:reverse(Acc);
[none | Rest] ->
values_loop(Rest, Acc);
[{some, First} | Rest@1] ->
values_loop(Rest@1, [First | Acc])
end.
-file("src/gleam/option.gleam", 348).
?DOC(
" Given a list of `Option`s,\n"
" returns only the values inside `Some`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" values([Some(1), None, Some(3)])\n"
" // -> [1, 3]\n"
" ```\n"
).
-spec values(list(option(HN))) -> list(HN).
values(Options) ->
values_loop(Options, []).

View file

@ -0,0 +1,200 @@
-module(gleam@order).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/gleam/order.gleam").
-export([negate/1, to_int/1, compare/2, reverse/1, break_tie/2, lazy_break_tie/2]).
-export_type([order/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
-type order() :: lt | eq | gt.
-file("src/gleam/order.gleam", 35).
?DOC(
" Inverts an order, so less-than becomes greater-than and greater-than\n"
" becomes less-than.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" negate(Lt)\n"
" // -> Gt\n"
" ```\n"
"\n"
" ```gleam\n"
" negate(Eq)\n"
" // -> Eq\n"
" ```\n"
"\n"
" ```gleam\n"
" negate(Gt)\n"
" // -> Lt\n"
" ```\n"
).
-spec negate(order()) -> order().
negate(Order) ->
case Order of
lt ->
gt;
eq ->
eq;
gt ->
lt
end.
-file("src/gleam/order.gleam", 62).
?DOC(
" Produces a numeric representation of the order.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" to_int(Lt)\n"
" // -> -1\n"
" ```\n"
"\n"
" ```gleam\n"
" to_int(Eq)\n"
" // -> 0\n"
" ```\n"
"\n"
" ```gleam\n"
" to_int(Gt)\n"
" // -> 1\n"
" ```\n"
).
-spec to_int(order()) -> integer().
to_int(Order) ->
case Order of
lt ->
-1;
eq ->
0;
gt ->
1
end.
-file("src/gleam/order.gleam", 79).
?DOC(
" Compares two `Order` values to one another, producing a new `Order`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" compare(Eq, with: Lt)\n"
" // -> Gt\n"
" ```\n"
).
-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.
-file("src/gleam/order.gleam", 100).
?DOC(
" Inverts an ordering function, so less-than becomes greater-than and greater-than\n"
" becomes less-than.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" import gleam/int\n"
" import gleam/list\n"
"\n"
" list.sort([1, 5, 4], by: reverse(int.compare))\n"
" // -> [5, 4, 1]\n"
" ```\n"
).
-spec reverse(fun((I, I) -> order())) -> fun((I, I) -> order()).
reverse(Orderer) ->
fun(A, B) -> Orderer(B, A) end.
-file("src/gleam/order.gleam", 122).
?DOC(
" Return a fallback `Order` in case the first argument is `Eq`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" import gleam/int\n"
"\n"
" break_tie(in: int.compare(1, 1), with: Lt)\n"
" // -> Lt\n"
" ```\n"
"\n"
" ```gleam\n"
" import gleam/int\n"
"\n"
" break_tie(in: int.compare(1, 0), with: Eq)\n"
" // -> Gt\n"
" ```\n"
).
-spec break_tie(order(), order()) -> order().
break_tie(Order, Other) ->
case Order of
lt ->
Order;
gt ->
Order;
eq ->
Other
end.
-file("src/gleam/order.gleam", 151).
?DOC(
" Invokes a fallback function returning an `Order` in case the first argument\n"
" is `Eq`.\n"
"\n"
" This can be useful when the fallback comparison might be expensive and it\n"
" needs to be delayed until strictly necessary.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" import gleam/int\n"
"\n"
" lazy_break_tie(in: int.compare(1, 1), with: fn() { Lt })\n"
" // -> Lt\n"
" ```\n"
"\n"
" ```gleam\n"
" import gleam/int\n"
"\n"
" lazy_break_tie(in: int.compare(1, 0), with: fn() { Eq })\n"
" // -> Gt\n"
" ```\n"
).
-spec lazy_break_tie(order(), fun(() -> order())) -> order().
lazy_break_tie(Order, Comparison) ->
case Order of
lt ->
Order;
gt ->
Order;
eq ->
Comparison()
end.

View file

@ -0,0 +1,110 @@
-module(gleam@pair).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/gleam/pair.gleam").
-export([first/1, second/1, swap/1, map_first/2, map_second/2, new/2]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
-file("src/gleam/pair.gleam", 10).
?DOC(
" Returns the first element in a pair.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" first(#(1, 2))\n"
" // -> 1\n"
" ```\n"
).
-spec first({CLF, any()}) -> CLF.
first(Pair) ->
{A, _} = Pair,
A.
-file("src/gleam/pair.gleam", 24).
?DOC(
" Returns the second element in a pair.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" second(#(1, 2))\n"
" // -> 2\n"
" ```\n"
).
-spec second({any(), CLI}) -> CLI.
second(Pair) ->
{_, A} = Pair,
A.
-file("src/gleam/pair.gleam", 38).
?DOC(
" Returns a new pair with the elements swapped.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" swap(#(1, 2))\n"
" // -> #(2, 1)\n"
" ```\n"
).
-spec swap({CLJ, CLK}) -> {CLK, CLJ}.
swap(Pair) ->
{A, B} = Pair,
{B, A}.
-file("src/gleam/pair.gleam", 53).
?DOC(
" Returns a new pair with the first element having had `with` applied to\n"
" it.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" #(1, 2) |> map_first(fn(n) { n * 2 })\n"
" // -> #(2, 2)\n"
" ```\n"
).
-spec map_first({CLL, CLM}, fun((CLL) -> CLN)) -> {CLN, CLM}.
map_first(Pair, Fun) ->
{A, B} = Pair,
{Fun(A), B}.
-file("src/gleam/pair.gleam", 68).
?DOC(
" Returns a new pair with the second element having had `with` applied to\n"
" it.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" #(1, 2) |> map_second(fn(n) { n * 2 })\n"
" // -> #(1, 4)\n"
" ```\n"
).
-spec map_second({CLO, CLP}, fun((CLP) -> CLQ)) -> {CLO, CLQ}.
map_second(Pair, Fun) ->
{A, B} = Pair,
{A, Fun(B)}.
-file("src/gleam/pair.gleam", 83).
?DOC(
" Returns a new pair with the given elements. This can also be done using the dedicated\n"
" syntax instead: `new(1, 2) == #(1, 2)`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" new(1, 2)\n"
" // -> #(1, 2)\n"
" ```\n"
).
-spec new(CLR, CLS) -> {CLR, CLS}.
new(First, Second) ->
{First, Second}.

View file

@ -0,0 +1,550 @@
-module(gleam@result).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/gleam/result.gleam").
-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, 'or'/2, lazy_or/2, all/1, partition/1, replace/2, replace_error/2, values/1, try_recover/2]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" Result represents the result of something that may succeed or not.\n"
" `Ok` means it was successful, `Error` means it was not successful.\n"
).
-file("src/gleam/result.gleam", 20).
?DOC(
" Checks whether the result is an `Ok` value.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" is_ok(Ok(1))\n"
" // -> True\n"
" ```\n"
"\n"
" ```gleam\n"
" is_ok(Error(Nil))\n"
" // -> False\n"
" ```\n"
).
-spec is_ok({ok, any()} | {error, any()}) -> boolean().
is_ok(Result) ->
case Result of
{error, _} ->
false;
{ok, _} ->
true
end.
-file("src/gleam/result.gleam", 41).
?DOC(
" Checks whether the result is an `Error` value.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" is_error(Ok(1))\n"
" // -> False\n"
" ```\n"
"\n"
" ```gleam\n"
" is_error(Error(Nil))\n"
" // -> True\n"
" ```\n"
).
-spec is_error({ok, any()} | {error, any()}) -> boolean().
is_error(Result) ->
case Result of
{ok, _} ->
false;
{error, _} ->
true
end.
-file("src/gleam/result.gleam", 66).
?DOC(
" Updates a value held within the `Ok` of a result by calling a given function\n"
" on it.\n"
"\n"
" If the result is an `Error` rather than `Ok` the function is not called and the\n"
" result stays the same.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" map(over: Ok(1), with: fn(x) { x + 1 })\n"
" // -> Ok(2)\n"
" ```\n"
"\n"
" ```gleam\n"
" map(over: Error(1), with: fn(x) { x + 1 })\n"
" // -> Error(1)\n"
" ```\n"
).
-spec map({ok, CMC} | {error, CMD}, fun((CMC) -> CMG)) -> {ok, CMG} |
{error, CMD}.
map(Result, Fun) ->
case Result of
{ok, X} ->
{ok, Fun(X)};
{error, E} ->
{error, E}
end.
-file("src/gleam/result.gleam", 91).
?DOC(
" Updates a value held within the `Error` of a result by calling a given function\n"
" on it.\n"
"\n"
" If the result is `Ok` rather than `Error` the function is not called and the\n"
" result stays the same.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" map_error(over: Error(1), with: fn(x) { x + 1 })\n"
" // -> Error(2)\n"
" ```\n"
"\n"
" ```gleam\n"
" map_error(over: Ok(1), with: fn(x) { x + 1 })\n"
" // -> Ok(1)\n"
" ```\n"
).
-spec map_error({ok, CMJ} | {error, CMK}, fun((CMK) -> CMN)) -> {ok, CMJ} |
{error, CMN}.
map_error(Result, Fun) ->
case Result of
{ok, X} ->
{ok, X};
{error, Error} ->
{error, Fun(Error)}
end.
-file("src/gleam/result.gleam", 120).
?DOC(
" Merges a nested `Result` into a single layer.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" flatten(Ok(Ok(1)))\n"
" // -> Ok(1)\n"
" ```\n"
"\n"
" ```gleam\n"
" flatten(Ok(Error(\"\")))\n"
" // -> Error(\"\")\n"
" ```\n"
"\n"
" ```gleam\n"
" flatten(Error(Nil))\n"
" // -> Error(Nil)\n"
" ```\n"
).
-spec flatten({ok, {ok, CMQ} | {error, CMR}} | {error, CMR}) -> {ok, CMQ} |
{error, CMR}.
flatten(Result) ->
case Result of
{ok, X} ->
X;
{error, Error} ->
{error, Error}
end.
-file("src/gleam/result.gleam", 158).
?DOC(
" \"Updates\" an `Ok` result by passing its value to a function that yields a result,\n"
" and returning the yielded result. (This may \"replace\" the `Ok` with an `Error`.)\n"
"\n"
" If the input is an `Error` rather than an `Ok`, the function is not called and\n"
" the original `Error` is returned.\n"
"\n"
" This function is the equivalent of calling `map` followed by `flatten`, and\n"
" it is useful for chaining together multiple functions that may fail.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" try(Ok(1), fn(x) { Ok(x + 1) })\n"
" // -> Ok(2)\n"
" ```\n"
"\n"
" ```gleam\n"
" try(Ok(1), fn(x) { Ok(#(\"a\", x)) })\n"
" // -> Ok(#(\"a\", 1))\n"
" ```\n"
"\n"
" ```gleam\n"
" try(Ok(1), fn(_) { Error(\"Oh no\") })\n"
" // -> Error(\"Oh no\")\n"
" ```\n"
"\n"
" ```gleam\n"
" try(Error(Nil), fn(x) { Ok(x + 1) })\n"
" // -> Error(Nil)\n"
" ```\n"
).
-spec 'try'({ok, CMY} | {error, CMZ}, fun((CMY) -> {ok, CNC} | {error, CMZ})) -> {ok,
CNC} |
{error, CMZ}.
'try'(Result, Fun) ->
case Result of
{ok, X} ->
Fun(X);
{error, E} ->
{error, E}
end.
-file("src/gleam/result.gleam", 169).
-spec then({ok, CNH} | {error, CNI}, fun((CNH) -> {ok, CNL} | {error, CNI})) -> {ok,
CNL} |
{error, CNI}.
then(Result, Fun) ->
'try'(Result, Fun).
-file("src/gleam/result.gleam", 191).
?DOC(
" Extracts the `Ok` value from a result, returning a default value if the result\n"
" is an `Error`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" unwrap(Ok(1), 0)\n"
" // -> 1\n"
" ```\n"
"\n"
" ```gleam\n"
" unwrap(Error(\"\"), 0)\n"
" // -> 0\n"
" ```\n"
).
-spec unwrap({ok, CNQ} | {error, any()}, CNQ) -> CNQ.
unwrap(Result, Default) ->
case Result of
{ok, V} ->
V;
{error, _} ->
Default
end.
-file("src/gleam/result.gleam", 213).
?DOC(
" Extracts the `Ok` value from a result, evaluating the default function if the result\n"
" is an `Error`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" lazy_unwrap(Ok(1), fn() { 0 })\n"
" // -> 1\n"
" ```\n"
"\n"
" ```gleam\n"
" lazy_unwrap(Error(\"\"), fn() { 0 })\n"
" // -> 0\n"
" ```\n"
).
-spec lazy_unwrap({ok, CNU} | {error, any()}, fun(() -> CNU)) -> CNU.
lazy_unwrap(Result, Default) ->
case Result of
{ok, V} ->
V;
{error, _} ->
Default()
end.
-file("src/gleam/result.gleam", 235).
?DOC(
" Extracts the `Error` value from a result, returning a default value if the result\n"
" is an `Ok`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" unwrap_error(Error(1), 0)\n"
" // -> 1\n"
" ```\n"
"\n"
" ```gleam\n"
" unwrap_error(Ok(\"\"), 0)\n"
" // -> 0\n"
" ```\n"
).
-spec unwrap_error({ok, any()} | {error, CNZ}, CNZ) -> CNZ.
unwrap_error(Result, Default) ->
case Result of
{ok, _} ->
Default;
{error, E} ->
E
end.
-file("src/gleam/result.gleam", 243).
-spec unwrap_both({ok, COC} | {error, COC}) -> COC.
unwrap_both(Result) ->
case Result of
{ok, A} ->
A;
{error, A@1} ->
A@1
end.
-file("src/gleam/result.gleam", 274).
?DOC(
" Returns the first value if it is `Ok`, otherwise returns the second value.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" or(Ok(1), Ok(2))\n"
" // -> Ok(1)\n"
" ```\n"
"\n"
" ```gleam\n"
" or(Ok(1), Error(\"Error 2\"))\n"
" // -> Ok(1)\n"
" ```\n"
"\n"
" ```gleam\n"
" or(Error(\"Error 1\"), Ok(2))\n"
" // -> Ok(2)\n"
" ```\n"
"\n"
" ```gleam\n"
" or(Error(\"Error 1\"), Error(\"Error 2\"))\n"
" // -> Error(\"Error 2\")\n"
" ```\n"
).
-spec 'or'({ok, COF} | {error, COG}, {ok, COF} | {error, COG}) -> {ok, COF} |
{error, COG}.
'or'(First, Second) ->
case First of
{ok, _} ->
First;
{error, _} ->
Second
end.
-file("src/gleam/result.gleam", 307).
?DOC(
" Returns the first value if it is `Ok`, otherwise evaluates the given function for a fallback value.\n"
"\n"
" If you need access to the initial error value, use `result.try_recover`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" lazy_or(Ok(1), fn() { Ok(2) })\n"
" // -> Ok(1)\n"
" ```\n"
"\n"
" ```gleam\n"
" lazy_or(Ok(1), fn() { Error(\"Error 2\") })\n"
" // -> Ok(1)\n"
" ```\n"
"\n"
" ```gleam\n"
" lazy_or(Error(\"Error 1\"), fn() { Ok(2) })\n"
" // -> Ok(2)\n"
" ```\n"
"\n"
" ```gleam\n"
" lazy_or(Error(\"Error 1\"), fn() { Error(\"Error 2\") })\n"
" // -> Error(\"Error 2\")\n"
" ```\n"
).
-spec lazy_or({ok, CON} | {error, COO}, fun(() -> {ok, CON} | {error, COO})) -> {ok,
CON} |
{error, COO}.
lazy_or(First, Second) ->
case First of
{ok, _} ->
First;
{error, _} ->
Second()
end.
-file("src/gleam/result.gleam", 333).
?DOC(
" Combines a list of results into a single result.\n"
" If all elements in the list are `Ok` then returns an `Ok` holding the list of values.\n"
" If any element is `Error` then returns the first error.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" all([Ok(1), Ok(2)])\n"
" // -> Ok([1, 2])\n"
" ```\n"
"\n"
" ```gleam\n"
" all([Ok(1), Error(\"e\")])\n"
" // -> Error(\"e\")\n"
" ```\n"
).
-spec all(list({ok, COV} | {error, COW})) -> {ok, list(COV)} | {error, COW}.
all(Results) ->
gleam@list:try_map(Results, fun(Result) -> Result end).
-file("src/gleam/result.gleam", 353).
-spec partition_loop(list({ok, CPK} | {error, CPL}), list(CPK), list(CPL)) -> {list(CPK),
list(CPL)}.
partition_loop(Results, Oks, Errors) ->
case Results of
[] ->
{Oks, Errors};
[{ok, A} | Rest] ->
partition_loop(Rest, [A | Oks], Errors);
[{error, E} | Rest@1] ->
partition_loop(Rest@1, Oks, [E | Errors])
end.
-file("src/gleam/result.gleam", 349).
?DOC(
" Given a list of results, returns a pair where the first element is a list\n"
" of all the values inside `Ok` and the second element is a list with all the\n"
" values inside `Error`. The values in both lists appear in reverse order with\n"
" respect to their position in the original list of results.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" partition([Ok(1), Error(\"a\"), Error(\"b\"), Ok(2)])\n"
" // -> #([2, 1], [\"b\", \"a\"])\n"
" ```\n"
).
-spec partition(list({ok, CPD} | {error, CPE})) -> {list(CPD), list(CPE)}.
partition(Results) ->
partition_loop(Results, [], []).
-file("src/gleam/result.gleam", 375).
?DOC(
" Replace the value within a result\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" replace(Ok(1), Nil)\n"
" // -> Ok(Nil)\n"
" ```\n"
"\n"
" ```gleam\n"
" replace(Error(1), Nil)\n"
" // -> Error(1)\n"
" ```\n"
).
-spec replace({ok, any()} | {error, CPT}, CPW) -> {ok, CPW} | {error, CPT}.
replace(Result, Value) ->
case Result of
{ok, _} ->
{ok, Value};
{error, Error} ->
{error, Error}
end.
-file("src/gleam/result.gleam", 396).
?DOC(
" Replace the error within a result\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" replace_error(Error(1), Nil)\n"
" // -> Error(Nil)\n"
" ```\n"
"\n"
" ```gleam\n"
" replace_error(Ok(1), Nil)\n"
" // -> Ok(1)\n"
" ```\n"
).
-spec replace_error({ok, CPZ} | {error, any()}, CQD) -> {ok, CPZ} | {error, CQD}.
replace_error(Result, Error) ->
case Result of
{ok, X} ->
{ok, X};
{error, _} ->
{error, Error}
end.
-file("src/gleam/result.gleam", 412).
?DOC(
" Given a list of results, returns only the values inside `Ok`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" values([Ok(1), Error(\"a\"), Ok(3)])\n"
" // -> [1, 3]\n"
" ```\n"
).
-spec values(list({ok, CQG} | {error, any()})) -> list(CQG).
values(Results) ->
gleam@list:filter_map(Results, fun(Result) -> Result end).
-file("src/gleam/result.gleam", 445).
?DOC(
" Updates a value held within the `Error` of a result by calling a given function\n"
" on it, where the given function also returns a result. The two results are\n"
" then merged together into one result.\n"
"\n"
" If the result is an `Ok` rather than `Error` the function is not called and the\n"
" result stays the same.\n"
"\n"
" This function is useful for chaining together computations that may fail\n"
" and trying to recover from possible errors.\n"
"\n"
" If you do not need access to the initial error value, use `result.lazy_or`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" Ok(1) |> try_recover(with: fn(_) { Error(\"failed to recover\") })\n"
" // -> Ok(1)\n"
" ```\n"
"\n"
" ```gleam\n"
" Error(1) |> try_recover(with: fn(error) { Ok(error + 1) })\n"
" // -> Ok(2)\n"
" ```\n"
"\n"
" ```gleam\n"
" Error(1) |> try_recover(with: fn(error) { Error(\"failed to recover\") })\n"
" // -> Error(\"failed to recover\")\n"
" ```\n"
).
-spec try_recover(
{ok, CQM} | {error, CQN},
fun((CQN) -> {ok, CQM} | {error, CQQ})
) -> {ok, CQM} | {error, CQQ}.
try_recover(Result, Fun) ->
case Result of
{ok, Value} ->
{ok, Value};
{error, Error} ->
Fun(Error)
end.

View file

@ -0,0 +1,429 @@
-module(gleam@set).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/gleam/set.gleam").
-export([new/0, size/1, is_empty/1, contains/2, delete/2, to_list/1, fold/3, filter/2, drop/2, take/2, intersection/2, difference/2, is_subset/2, is_disjoint/2, each/2, insert/2, from_list/1, map/2, union/2, symmetric_difference/2]).
-export_type([set/1]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
-opaque set(CVL) :: {set, gleam@dict:dict(CVL, list(nil))}.
-file("src/gleam/set.gleam", 32).
?DOC(" Creates a new empty set.\n").
-spec new() -> set(any()).
new() ->
{set, maps:new()}.
-file("src/gleam/set.gleam", 50).
?DOC(
" Gets the number of members in a set.\n"
"\n"
" This function runs in constant time.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" new()\n"
" |> insert(1)\n"
" |> insert(2)\n"
" |> size\n"
" // -> 2\n"
" ```\n"
).
-spec size(set(any())) -> integer().
size(Set) ->
maps:size(erlang:element(2, Set)).
-file("src/gleam/set.gleam", 68).
?DOC(
" Determines whether or not the set is empty.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" new() |> is_empty\n"
" // -> True\n"
" ```\n"
"\n"
" ```gleam\n"
" new() |> insert(1) |> is_empty\n"
" // -> False\n"
" ```\n"
).
-spec is_empty(set(any())) -> boolean().
is_empty(Set) ->
Set =:= new().
-file("src/gleam/set.gleam", 110).
?DOC(
" Checks whether a set contains a given member.\n"
"\n"
" This function runs in logarithmic time.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" new()\n"
" |> insert(2)\n"
" |> contains(2)\n"
" // -> True\n"
" ```\n"
"\n"
" ```gleam\n"
" new()\n"
" |> insert(2)\n"
" |> contains(1)\n"
" // -> False\n"
" ```\n"
).
-spec contains(set(CVW), CVW) -> boolean().
contains(Set, Member) ->
_pipe = erlang:element(2, Set),
_pipe@1 = gleam_stdlib:map_get(_pipe, Member),
gleam@result:is_ok(_pipe@1).
-file("src/gleam/set.gleam", 131).
?DOC(
" Removes a member from a set. If the set does not contain the member then\n"
" the set is returned unchanged.\n"
"\n"
" This function runs in logarithmic time.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" new()\n"
" |> insert(2)\n"
" |> delete(2)\n"
" |> contains(1)\n"
" // -> False\n"
" ```\n"
).
-spec delete(set(CVY), CVY) -> set(CVY).
delete(Set, Member) ->
{set, gleam@dict:delete(erlang:element(2, Set), Member)}.
-file("src/gleam/set.gleam", 149).
?DOC(
" Converts the set into a list of the contained members.\n"
"\n"
" The list has no specific ordering, any unintentional ordering may change in\n"
" future versions of Gleam or Erlang.\n"
"\n"
" This function runs in linear time.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" new() |> insert(2) |> to_list\n"
" // -> [2]\n"
" ```\n"
).
-spec to_list(set(CWB)) -> list(CWB).
to_list(Set) ->
maps:keys(erlang:element(2, Set)).
-file("src/gleam/set.gleam", 190).
?DOC(
" Combines all entries into a single value by calling a given function on each\n"
" one.\n"
"\n"
" Sets are not ordered so the values are not returned in any specific order.\n"
" Do not write code that relies on the order entries are used by this\n"
" function as it may change in later versions of Gleam or Erlang.\n"
"\n"
" # Examples\n"
"\n"
" ```gleam\n"
" from_list([1, 3, 9])\n"
" |> fold(0, fn(accumulator, member) { accumulator + member })\n"
" // -> 13\n"
" ```\n"
).
-spec fold(set(CWH), CWJ, fun((CWJ, CWH) -> CWJ)) -> CWJ.
fold(Set, Initial, Reducer) ->
gleam@dict:fold(
erlang:element(2, Set),
Initial,
fun(A, K, _) -> Reducer(A, K) end
).
-file("src/gleam/set.gleam", 214).
?DOC(
" Creates a new set from an existing set, minus any members that a given\n"
" function returns `False` for.\n"
"\n"
" This function runs in loglinear time.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" import gleam/int\n"
"\n"
" from_list([1, 4, 6, 3, 675, 44, 67])\n"
" |> filter(keeping: int.is_even)\n"
" |> to_list\n"
" // -> [4, 6, 44]\n"
" ```\n"
).
-spec filter(set(CWK), fun((CWK) -> boolean())) -> set(CWK).
filter(Set, Predicate) ->
{set,
gleam@dict:filter(erlang:element(2, Set), fun(M, _) -> Predicate(M) end)}.
-file("src/gleam/set.gleam", 249).
?DOC(
" Creates a new set from a given set with all the same entries except any\n"
" entry found on the given list.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" from_list([1, 2, 3, 4])\n"
" |> drop([1, 3])\n"
" |> to_list\n"
" // -> [2, 4]\n"
" ```\n"
).
-spec drop(set(CWR), list(CWR)) -> set(CWR).
drop(Set, Disallowed) ->
gleam@list:fold(Disallowed, Set, fun delete/2).
-file("src/gleam/set.gleam", 267).
?DOC(
" Creates a new set from a given set, only including any members which are in\n"
" a given list.\n"
"\n"
" This function runs in loglinear time.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" from_list([1, 2, 3])\n"
" |> take([1, 3, 5])\n"
" |> to_list\n"
" // -> [1, 3]\n"
" ```\n"
).
-spec take(set(CWV), list(CWV)) -> set(CWV).
take(Set, Desired) ->
{set, gleam@dict:take(erlang:element(2, Set), Desired)}.
-file("src/gleam/set.gleam", 287).
-spec order(set(CXD), set(CXD)) -> {set(CXD), set(CXD)}.
order(First, Second) ->
case maps:size(erlang:element(2, First)) > maps:size(
erlang:element(2, Second)
) of
true ->
{First, Second};
false ->
{Second, First}
end.
-file("src/gleam/set.gleam", 305).
?DOC(
" Creates a new set that contains members that are present in both given sets.\n"
"\n"
" This function runs in loglinear time.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" intersection(from_list([1, 2]), from_list([2, 3])) |> to_list\n"
" // -> [2]\n"
" ```\n"
).
-spec intersection(set(CXI), set(CXI)) -> set(CXI).
intersection(First, Second) ->
{Larger, Smaller} = order(First, Second),
take(Larger, to_list(Smaller)).
-file("src/gleam/set.gleam", 323).
?DOC(
" Creates a new set that contains members that are present in the first set\n"
" but not the second.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" difference(from_list([1, 2]), from_list([2, 3, 4])) |> to_list\n"
" // -> [1]\n"
" ```\n"
).
-spec difference(set(CXM), set(CXM)) -> set(CXM).
difference(First, Second) ->
drop(First, to_list(Second)).
-file("src/gleam/set.gleam", 344).
?DOC(
" Determines if a set is fully contained by another.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" is_subset(from_list([1]), from_list([1, 2]))\n"
" // -> True\n"
" ```\n"
"\n"
" ```gleam\n"
" is_subset(from_list([1, 2, 3]), from_list([3, 4, 5]))\n"
" // -> False\n"
" ```\n"
).
-spec is_subset(set(CXQ), set(CXQ)) -> boolean().
is_subset(First, Second) ->
intersection(First, Second) =:= First.
-file("src/gleam/set.gleam", 362).
?DOC(
" Determines if two sets contain no common members\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" is_disjoint(from_list([1, 2, 3]), from_list([4, 5, 6]))\n"
" // -> True\n"
" ```\n"
"\n"
" ```gleam\n"
" is_disjoint(from_list([1, 2, 3]), from_list([3, 4, 5]))\n"
" // -> False\n"
" ```\n"
).
-spec is_disjoint(set(CXT), set(CXT)) -> boolean().
is_disjoint(First, Second) ->
intersection(First, Second) =:= new().
-file("src/gleam/set.gleam", 402).
?DOC(
" Calls a function for each member in a set, discarding the return\n"
" value.\n"
"\n"
" Useful for producing a side effect for every item of a set.\n"
"\n"
" ```gleam\n"
" let set = from_list([\"apple\", \"banana\", \"cherry\"])\n"
"\n"
" each(set, io.println)\n"
" // -> Nil\n"
" // apple\n"
" // banana\n"
" // cherry\n"
" ```\n"
"\n"
" The order of elements in the iteration is an implementation detail that\n"
" should not be relied upon.\n"
).
-spec each(set(CYA), fun((CYA) -> any())) -> nil.
each(Set, Fun) ->
fold(
Set,
nil,
fun(Nil, Member) ->
Fun(Member),
Nil
end
).
-file("src/gleam/set.gleam", 86).
?DOC(
" Inserts an member into the set.\n"
"\n"
" This function runs in logarithmic time.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" new()\n"
" |> insert(1)\n"
" |> insert(2)\n"
" |> size\n"
" // -> 2\n"
" ```\n"
).
-spec insert(set(CVT), CVT) -> set(CVT).
insert(Set, Member) ->
{set, gleam@dict:insert(erlang:element(2, Set), Member, [])}.
-file("src/gleam/set.gleam", 167).
?DOC(
" Creates a new set of the members in a given list.\n"
"\n"
" This function runs in loglinear time.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" import gleam/int\n"
" import gleam/list\n"
"\n"
" [1, 1, 2, 4, 3, 2] |> from_list |> to_list |> list.sort(by: int.compare)\n"
" // -> [1, 2, 3, 4]\n"
" ```\n"
).
-spec from_list(list(CWE)) -> set(CWE).
from_list(Members) ->
Dict = gleam@list:fold(
Members,
maps:new(),
fun(M, K) -> gleam@dict:insert(M, K, []) end
),
{set, Dict}.
-file("src/gleam/set.gleam", 232).
?DOC(
" Creates a new set from a given set with the result of applying the given\n"
" function to each member.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" from_list([1, 2, 3, 4])\n"
" |> map(with: fn(x) { x * 2 })\n"
" |> to_list\n"
" // -> [2, 4, 6, 8]\n"
" ```\n"
).
-spec map(set(CWN), fun((CWN) -> CWP)) -> set(CWP).
map(Set, Fun) ->
fold(Set, new(), fun(Acc, Member) -> insert(Acc, Fun(Member)) end).
-file("src/gleam/set.gleam", 282).
?DOC(
" Creates a new set that contains all members of both given sets.\n"
"\n"
" This function runs in loglinear time.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" union(from_list([1, 2]), from_list([2, 3])) |> to_list\n"
" // -> [1, 2, 3]\n"
" ```\n"
).
-spec union(set(CWZ), set(CWZ)) -> set(CWZ).
union(First, Second) ->
{Larger, Smaller} = order(First, Second),
fold(Smaller, Larger, fun insert/2).
-file("src/gleam/set.gleam", 374).
?DOC(
" Creates a new set that contains members that are present in either set, but\n"
" not both.\n"
"\n"
" ```gleam\n"
" symmetric_difference(from_list([1, 2, 3]), from_list([3, 4])) |> to_list\n"
" // -> [1, 2, 4]\n"
" ```\n"
).
-spec symmetric_difference(set(CXW), set(CXW)) -> set(CXW).
symmetric_difference(First, Second) ->
difference(union(First, Second), intersection(First, Second)).

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,207 @@
-module(gleam@string_tree).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/gleam/string_tree.gleam").
-export([append_tree/2, prepend_tree/2, from_strings/1, new/0, 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_tree/0, direction/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
-type string_tree() :: any().
-type direction() :: all.
-file("src/gleam/string_tree.gleam", 61).
?DOC(
" Appends some `StringTree` onto the end of another.\n"
"\n"
" Runs in constant time.\n"
).
-spec append_tree(string_tree(), string_tree()) -> string_tree().
append_tree(Tree, Suffix) ->
gleam_stdlib:iodata_append(Tree, Suffix).
-file("src/gleam/string_tree.gleam", 48).
?DOC(
" Prepends some `StringTree` onto the start of another.\n"
"\n"
" Runs in constant time.\n"
).
-spec prepend_tree(string_tree(), string_tree()) -> string_tree().
prepend_tree(Tree, Prefix) ->
gleam_stdlib:iodata_append(Prefix, Tree).
-file("src/gleam/string_tree.gleam", 69).
?DOC(
" Converts a list of strings into a `StringTree`.\n"
"\n"
" Runs in constant time.\n"
).
-spec from_strings(list(binary())) -> string_tree().
from_strings(Strings) ->
gleam_stdlib:identity(Strings).
-file("src/gleam/string_tree.gleam", 24).
?DOC(
" Create an empty `StringTree`. Useful as the start of a pipe chaining many\n"
" trees together.\n"
).
-spec new() -> string_tree().
new() ->
gleam_stdlib:identity([]).
-file("src/gleam/string_tree.gleam", 77).
?DOC(
" Joins a list of trees into a single tree.\n"
"\n"
" Runs in constant time.\n"
).
-spec concat(list(string_tree())) -> string_tree().
concat(Trees) ->
gleam_stdlib:identity(Trees).
-file("src/gleam/string_tree.gleam", 85).
?DOC(
" Converts a string into a `StringTree`.\n"
"\n"
" Runs in constant time.\n"
).
-spec from_string(binary()) -> string_tree().
from_string(String) ->
gleam_stdlib:identity(String).
-file("src/gleam/string_tree.gleam", 32).
?DOC(
" Prepends a `String` onto the start of some `StringTree`.\n"
"\n"
" Runs in constant time.\n"
).
-spec prepend(string_tree(), binary()) -> string_tree().
prepend(Tree, Prefix) ->
gleam_stdlib:iodata_append(gleam_stdlib:identity(Prefix), Tree).
-file("src/gleam/string_tree.gleam", 40).
?DOC(
" Appends a `String` onto the end of some `StringTree`.\n"
"\n"
" Runs in constant time.\n"
).
-spec append(string_tree(), binary()) -> string_tree().
append(Tree, Second) ->
gleam_stdlib:iodata_append(Tree, gleam_stdlib:identity(Second)).
-file("src/gleam/string_tree.gleam", 94).
?DOC(
" Turns a `StringTree` into a `String`\n"
"\n"
" This function is implemented natively by the virtual machine and is highly\n"
" optimised.\n"
).
-spec to_string(string_tree()) -> binary().
to_string(Tree) ->
unicode:characters_to_binary(Tree).
-file("src/gleam/string_tree.gleam", 100).
?DOC(" Returns the size of the `StringTree` in bytes.\n").
-spec byte_size(string_tree()) -> integer().
byte_size(Tree) ->
erlang:iolist_size(Tree).
-file("src/gleam/string_tree.gleam", 104).
?DOC(" Joins the given trees into a new tree separated with the given string.\n").
-spec join(list(string_tree()), binary()) -> string_tree().
join(Trees, Sep) ->
_pipe = Trees,
_pipe@1 = gleam@list:intersperse(_pipe, gleam_stdlib:identity(Sep)),
gleam_stdlib:identity(_pipe@1).
-file("src/gleam/string_tree.gleam", 115).
?DOC(
" Converts a `StringTree` to a new one where the contents have been\n"
" lowercased.\n"
).
-spec lowercase(string_tree()) -> string_tree().
lowercase(Tree) ->
string:lowercase(Tree).
-file("src/gleam/string_tree.gleam", 122).
?DOC(
" Converts a `StringTree` to a new one where the contents have been\n"
" uppercased.\n"
).
-spec uppercase(string_tree()) -> string_tree().
uppercase(Tree) ->
string:uppercase(Tree).
-file("src/gleam/string_tree.gleam", 127).
?DOC(" Converts a `StringTree` to a new one with the contents reversed.\n").
-spec reverse(string_tree()) -> string_tree().
reverse(Tree) ->
string:reverse(Tree).
-file("src/gleam/string_tree.gleam", 145).
?DOC(" Splits a `StringTree` on a given pattern into a list of trees.\n").
-spec split(string_tree(), binary()) -> list(string_tree()).
split(Tree, Pattern) ->
string:split(Tree, Pattern, all).
-file("src/gleam/string_tree.gleam", 156).
?DOC(" Replaces all instances of a pattern with a given string substitute.\n").
-spec replace(string_tree(), binary(), binary()) -> string_tree().
replace(Tree, Pattern, Substitute) ->
gleam_stdlib:string_replace(Tree, Pattern, Substitute).
-file("src/gleam/string_tree.gleam", 182).
?DOC(
" Compares two string trees to determine if they have the same textual\n"
" content.\n"
"\n"
" Comparing two string trees using the `==` operator may return `False` even\n"
" if they have the same content as they may have been build in different ways,\n"
" so using this function is often preferred.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" from_strings([\"a\", \"b\"]) == from_string(\"ab\")\n"
" // -> False\n"
" ```\n"
"\n"
" ```gleam\n"
" is_equal(from_strings([\"a\", \"b\"]), from_string(\"ab\"))\n"
" // -> True\n"
" ```\n"
).
-spec is_equal(string_tree(), string_tree()) -> boolean().
is_equal(A, B) ->
string:equal(A, B).
-file("src/gleam/string_tree.gleam", 206).
?DOC(
" Inspects a `StringTree` to determine if it is equivalent to an empty string.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" from_string(\"ok\") |> is_empty\n"
" // -> False\n"
" ```\n"
"\n"
" ```gleam\n"
" from_string(\"\") |> is_empty\n"
" // -> True\n"
" ```\n"
"\n"
" ```gleam\n"
" from_strings([]) |> is_empty\n"
" // -> True\n"
" ```\n"
).
-spec is_empty(string_tree()) -> boolean().
is_empty(Tree) ->
string:is_empty(Tree).

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,31 @@
{application, gleam_stdlib, [
{vsn, "0.65.0"},
{applications, []},
{description, "A standard library for the Gleam programming language"},
{modules, [gleam@bit_array,
gleam@bool,
gleam@bytes_tree,
gleam@dict,
gleam@dynamic,
gleam@dynamic@decode,
gleam@float,
gleam@function,
gleam@int,
gleam@io,
gleam@list,
gleam@option,
gleam@order,
gleam@pair,
gleam@result,
gleam@set,
gleam@string,
gleam@string_tree,
gleam@uri,
gleam_stdlib,
gleam_stdlib@@main,
gleam_stdlib_test_ffi,
gleeunit_ffi,
gleeunit_gleam_panic_ffi,
gleeunit_progress]},
{registered, []}
]}.

View file

@ -0,0 +1,534 @@
-module(gleam_stdlib).
-export([
map_get/2, iodata_append/2, identity/1, parse_int/1, parse_float/1,
less_than/2, string_pop_grapheme/1, string_pop_codeunit/1,
string_starts_with/2, wrap_list/1, string_ends_with/2, string_pad/4,
uri_parse/1, bit_array_slice/3, percent_encode/1, percent_decode/1,
base64_decode/1, parse_query/1, bit_array_concat/1,
base64_encode/2, 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_encode/1, base16_decode/1, string_replace/3, slice/3,
bit_array_to_int_and_size/1, bit_array_pad_to_bytes/1, index/2, list/5,
dict/1, int/1, float/1, bit_array/1, is_null/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)).
-define(is_ascii_character(X),
(erlang:is_integer(X) andalso X >= 32 andalso X =< 126)).
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.
classify_dynamic(nil) -> <<"Nil">>;
classify_dynamic(null) -> <<"Nil">>;
classify_dynamic(undefined) -> <<"Nil">>;
classify_dynamic(X) when is_boolean(X) -> <<"Bool">>;
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_map(X) -> <<"Dict">>;
classify_dynamic(X) when is_tuple(X) -> <<"Array">>;
classify_dynamic(X) when is_reference(X) -> <<"Reference">>;
classify_dynamic(X) when is_pid(X) -> <<"Pid">>;
classify_dynamic(X) when is_port(X) -> <<"Port">>;
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(_) -> <<"Unknown">>.
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)}.
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 ] when is_binary(Rest) ->
{ok, {unicode:characters_to_binary([Next]), Rest}};
[ Next | Rest ] ->
{ok, {unicode:characters_to_binary([Next]), unicode:characters_to_binary(Rest)}};
_ -> {error, nil}
end.
string_pop_codeunit(<<Cp/integer, Rest/binary>>) -> {Cp, Rest};
string_pop_codeunit(Binary) -> {0, Binary}.
bit_array_pad_to_bytes(Bin) ->
case erlang:bit_size(Bin) rem 8 of
0 -> Bin;
TrailingBits ->
PaddingBits = 8 - TrailingBits,
<<Bin/bits, 0:PaddingBits>>
end.
bit_array_concat(BitArrays) ->
list_to_bitstring(BitArrays).
-if(?OTP_RELEASE >= 26).
base64_encode(Bin, Padding) ->
PaddedBin = bit_array_pad_to_bytes(Bin),
base64:encode(PaddedBin, #{padding => Padding}).
-else.
base64_encode(_Bin, _Padding) ->
erlang:error(<<"Erlang OTP/26 or higher is required to use base64:encode">>).
-endif.
bit_array_slice(Bin, Pos, Len) ->
try {ok, binary:part(Bin, Pos, Len)}
catch error:badarg -> {error, nil}
end.
base64_decode(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 ->
Port =
try maps:get(port, Uri) of
undefined -> none;
Value -> {some, Value}
catch _:_ -> none
end,
{ok, {uri,
maps_get_optional(Uri, scheme),
maps_get_optional(Uri, userinfo),
maps_get_optional(Uri, host),
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) ->
erlang:element(2, inspect_atom(Atom));
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, true) of
{charlist, _} -> ["charlist.from_string(\"", list_to_binary(List), "\")"];
{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),
InspectedArgs = lists:map(fun inspect/1, ArgsList),
case inspect_atom(Atom) of
{gleam_atom, GleamAtom} ->
Args = lists:join(<<", ">>, InspectedArgs),
[GleamAtom, "(", Args, ")"];
{erlang_atom, ErlangAtom} ->
Args = lists:join(<<", ">>, [ErlangAtom | InspectedArgs]),
["#(", Args, ")"]
end;
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_atom(Atom) ->
Binary = erlang:atom_to_binary(Atom),
case inspect_maybe_gleam_atom(Binary, none, <<>>) of
{ok, Inspected} -> {gleam_atom, Inspected};
{error, _} -> {erlang_atom, ["atom.create(\"", Binary, "\")"]}
end.
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], true) when ?is_ascii_character(First) ->
{charlist, nil};
inspect_list([First], _) ->
{proper, [inspect(First)]};
inspect_list([First | Rest], ValidCharlist) when is_list(Rest) ->
StillValidCharlist = ValidCharlist andalso ?is_ascii_character(First),
{Kind, Inspected} = inspect_list(Rest, StillValidCharlist),
{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)).
bit_array_to_int_and_size(A) ->
Size = bit_size(A),
<<A1:Size>> = A,
{A1, Size}.
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>>;
$\f -> <<$\\, $f>>;
X when X > 126, X < 160 -> convert_to_u(X);
X when X < 32 -> convert_to_u(X);
Other -> <<Other/utf8>>
end,
inspect_maybe_utf8_string(Rest, <<Acc/binary, Escaped/binary>>);
_ -> {error, not_a_utf8_string}
end.
convert_to_u(Code) ->
list_to_binary(io_lib:format("\\u{~4.16.0B}", [Code])).
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_encode(Bin) ->
PaddedBin = bit_array_pad_to_bytes(Bin),
binary:encode_hex(PaddedBin).
base16_decode(String) ->
try
{ok, binary:decode_hex(String)}
catch
_:_ -> {error, nil}
end.
string_replace(String, Pattern, Replacement) ->
string:replace(String, Pattern, Replacement, all).
slice(String, Index, Length) ->
case string:slice(String, Index, Length) of
X when is_binary(X) -> X;
X when is_list(X) -> unicode:characters_to_binary(X)
end.
index([X | _], 0) ->
{ok, {some, X}};
index([_, X | _], 1) ->
{ok, {some, X}};
index([_, _, X | _], 2) ->
{ok, {some, X}};
index([_, _, _, X | _], 3) ->
{ok, {some, X}};
index([_, _, _, _, X | _], 4) ->
{ok, {some, X}};
index([_, _, _, _, _, X | _], 5) ->
{ok, {some, X}};
index([_, _, _, _, _, _, X | _], 6) ->
{ok, {some, X}};
index([_, _, _, _, _, _, _, X | _], 7) ->
{ok, {some, X}};
index(Tuple, Index) when is_tuple(Tuple) andalso is_integer(Index) ->
{ok, try
{some, element(Index + 1, Tuple)}
catch _:_ ->
none
end};
index(Map, Key) when is_map(Map) ->
{ok, try
{some, maps:get(Key, Map)}
catch _:_ ->
none
end};
index(_, Index) when is_integer(Index) ->
{error, <<"Indexable">>};
index(_, _) ->
{error, <<"Dict">>}.
list(T, A, B, C, D) when is_tuple(T) ->
list(tuple_to_list(T), A, B, C, D);
list([], _, _, _, Acc) ->
{lists:reverse(Acc), []};
list([X | Xs], Decode, PushPath, Index, Acc) ->
{Out, Errors} = Decode(X),
case Errors of
[] -> list(Xs, Decode, PushPath, Index + 1, [Out | Acc]);
_ -> PushPath({[], Errors}, integer_to_binary(Index))
end;
list(Unexpected, _, _, _, []) ->
Found = gleam@dynamic:classify(Unexpected),
Error = {decode_error, <<"List"/utf8>>, Found, []},
{[], [Error]};
list(_, _, _, _, Acc) ->
{lists:reverse(Acc), []}.
dict(#{} = Data) -> {ok, Data};
dict(_) -> {error, nil}.
int(I) when is_integer(I) -> {ok, I};
int(_) -> {error, 0}.
float(F) when is_float(F) -> {ok, F};
float(_) -> {error, 0.0}.
bit_array(B) when is_bitstring(B) -> {ok, B};
bit_array(_) -> {error, <<>>}.
is_null(X) ->
X =:= undefined orelse X =:= null orelse X =:= nil.

File diff suppressed because it is too large Load diff