Initial commit
This commit is contained in:
commit
a6272848f9
379 changed files with 74829 additions and 0 deletions
0
build/packages/gleam.lock
Normal file
0
build/packages/gleam.lock
Normal file
190
build/packages/gleam_community_colour/LICENCE
Normal file
190
build/packages/gleam_community_colour/LICENCE
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
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 2023 Gleam Community Contributors
|
||||
|
||||
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.
|
||||
36
build/packages/gleam_community_colour/README.md
Normal file
36
build/packages/gleam_community_colour/README.md
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
# gleam-community/colour
|
||||
|
||||
A package for a standard Colour type, conversions, and other utilities.
|
||||
|
||||
[](https://hex.pm/packages/gleam_community_colour)
|
||||
[](https://hexdocs.pm/gleam_community_colour/)
|
||||
|
||||
✨ This project is written in pure Gleam so you can use it anywhere Gleam runs: Erlang, Elixir, Node, Deno, and the browser!
|
||||
|
||||
---
|
||||
|
||||
## Quickstart
|
||||
|
||||
```gleam
|
||||
import gleam_community/colour
|
||||
import gleam_community/colour/accessibility
|
||||
|
||||
pub fn main() {
|
||||
let foreground = colour.from_hsl(h: 0.858, s: 1.0, l: 0.843)
|
||||
|
||||
let background_options = [colour.light_grey, colour.dark_grey]
|
||||
|
||||
let background = accessibility.maximum_contrast(foreground, background_options)
|
||||
}
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
`gleam_community` packages are published to [hex.pm](https://hex.pm/packages/gleam_community_colour)
|
||||
with the prefix `gleam_community_`. You can add them to your Gleam projects directly:
|
||||
|
||||
```sh
|
||||
gleam add gleam_community_colour
|
||||
```
|
||||
|
||||
The docs can be found over at [hexdocs.pm](https://hexdocs.pm/gleam_community_colour).
|
||||
13
build/packages/gleam_community_colour/gleam.toml
Normal file
13
build/packages/gleam_community_colour/gleam.toml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
name = "gleam_community_colour"
|
||||
version = "2.0.2"
|
||||
licences = ["Apache-2.0"]
|
||||
description = "Colour types, conversions, and other utilities"
|
||||
repository = { type = "github", user = "gleam-community", repo = "colour" }
|
||||
gleam = ">= 1.4.0"
|
||||
|
||||
[dependencies]
|
||||
gleam_stdlib = ">= 0.50.0 and < 2.0.0"
|
||||
gleam_json = ">= 2.2.0 and < 4.0.0"
|
||||
|
||||
[dev-dependencies]
|
||||
gleeunit = ">= 1.3.0 and < 2.0.0"
|
||||
|
|
@ -0,0 +1 @@
|
|||
-record(hsla, {h :: float(), s :: float(), l :: float(), a :: float()}).
|
||||
|
|
@ -0,0 +1 @@
|
|||
-record(rgba, {r :: float(), g :: float(), b :: float(), a :: float()}).
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,173 @@
|
|||
////
|
||||
//// - **Accessibility**
|
||||
//// - [`luminance`](#luminance)
|
||||
//// - [`contrast_ratio`](#contrast_ratio)
|
||||
//// - [`maximum_contrast`](#maximum_contrast)
|
||||
////
|
||||
//// ---
|
||||
////
|
||||
//// This package was heavily inspired by the `elm-color-extra` module.
|
||||
//// The original source code can be found
|
||||
//// <a href="https://github.com/noahzgordon/elm-color-extra">here</a>.
|
||||
////
|
||||
//// <details>
|
||||
//// <summary>The license of that package is produced below:</summary>
|
||||
////
|
||||
////
|
||||
//// > MIT License
|
||||
////
|
||||
//// > Copyright (c) 2016 Andreas Köberle
|
||||
////
|
||||
//// > Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
//// of this software and associated documentation files (the "Software"), to deal
|
||||
//// in the Software without restriction, including without limitation the rights
|
||||
//// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
//// copies of the Software, and to permit persons to whom the Software is
|
||||
//// furnished to do so, subject to the following conditions:
|
||||
////
|
||||
//// > The above copyright notice and this permission notice shall be included in all
|
||||
//// copies or substantial portions of the Software.
|
||||
////
|
||||
//// > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
//// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
//// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
//// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
//// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
//// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
//// SOFTWARE.
|
||||
////
|
||||
//// </details>
|
||||
////
|
||||
|
||||
// Just in case we decide in the future to no longer include the above reference
|
||||
// and license, this package was initially a port of the `elm-color-extra` module:
|
||||
//
|
||||
// https://github.com/noahzgordon/elm-color-extra
|
||||
//
|
||||
|
||||
// IMPORTS --------------------------------------------------------------------
|
||||
|
||||
import gleam/float
|
||||
import gleam/list
|
||||
import gleam_community/colour.{type Colour}
|
||||
|
||||
// UTILITIES ------------------------------------------------------------------
|
||||
|
||||
fn intensity(colour_value: Float) -> Float {
|
||||
// Calculation taken from https://www.w3.org/TR/WCAG20/#relativeluminancedef
|
||||
case True {
|
||||
_ if colour_value <=. 0.03928 -> colour_value /. 12.92
|
||||
_ -> {
|
||||
// Is this guaranteed to be `OK`?
|
||||
let assert Ok(i) = float.power({ colour_value +. 0.055 } /. 1.055, 2.4)
|
||||
i
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ACCESSIBILITY --------------------------------------------------------------
|
||||
|
||||
/// Returns the relative brightness of the given `Colour` as a `Float` between
|
||||
/// 0.0, and 1.0 with 0.0 being the darkest possible colour and 1.0 being the lightest.
|
||||
///
|
||||
/// <details>
|
||||
/// <summary>Example:</summary>
|
||||
///
|
||||
/// ```gleam
|
||||
/// fn example() {
|
||||
/// luminance(colour.white) // 1.0
|
||||
/// }
|
||||
/// ```
|
||||
/// </details>
|
||||
///
|
||||
/// <div style="position: relative;">
|
||||
/// <a style="position: absolute; left: 0;" href="https://github.com/gleam-community/colour/issues">
|
||||
/// <small>Spot a typo? Open an issue!</small>
|
||||
/// </a>
|
||||
/// <a style="position: absolute; right: 0;" href="#">
|
||||
/// <small>Back to top ↑</small>
|
||||
/// </a>
|
||||
/// </div>
|
||||
///
|
||||
pub fn luminance(colour: Colour) -> Float {
|
||||
// Calculation taken from https://www.w3.org/TR/WCAG20/#relativeluminancedef
|
||||
let #(r, g, b, _) = colour.to_rgba(colour)
|
||||
|
||||
let r_intensity = intensity(r)
|
||||
let g_intensity = intensity(g)
|
||||
let b_intensity = intensity(b)
|
||||
|
||||
0.2126 *. r_intensity +. 0.7152 *. g_intensity +. 0.0722 *. b_intensity
|
||||
}
|
||||
|
||||
/// Returns the contrast between two `Colour` values as a `Float` between 1.0,
|
||||
/// and 21.0 with 1.0 being no contrast and, 21.0 being the highest possible contrast.
|
||||
///
|
||||
/// <details>
|
||||
/// <summary>Example:</summary>
|
||||
///
|
||||
/// ```gleam
|
||||
/// fn example() {
|
||||
/// contrast_ratio(between: colour.white, and: colour.black) // 21.0
|
||||
/// }
|
||||
/// ```
|
||||
/// </details>
|
||||
///
|
||||
/// <div style="position: relative;">
|
||||
/// <a style="position: absolute; left: 0;" href="https://github.com/gleam-community/colour/issues">
|
||||
/// <small>Spot a typo? Open an issue!</small>
|
||||
/// </a>
|
||||
/// <a style="position: absolute; right: 0;" href="#">
|
||||
/// <small>Back to top ↑</small>
|
||||
/// </a>
|
||||
/// </div>
|
||||
///
|
||||
pub fn contrast_ratio(between colour_a: Colour, and colour_b: Colour) -> Float {
|
||||
// Calculation taken from https://www.w3.org/TR/WCAG20/#contrast-ratiodef
|
||||
let luminance_a = luminance(colour_a) +. 0.05
|
||||
let luminance_b = luminance(colour_b) +. 0.05
|
||||
|
||||
case luminance_a >. luminance_b {
|
||||
True -> luminance_a /. luminance_b
|
||||
False -> luminance_b /. luminance_a
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the `Colour` with the highest contrast between the base `Colour`,
|
||||
/// and and the other provided `Colour` values.
|
||||
///
|
||||
/// <details>
|
||||
/// <summary>Example:</summary>
|
||||
///
|
||||
/// ```gleam
|
||||
/// fn example() {
|
||||
/// maximum_contrast(
|
||||
/// colour.yellow,
|
||||
/// [colour.white, colour.dark_blue, colour.green],
|
||||
/// )
|
||||
/// }
|
||||
/// ```
|
||||
/// </details>
|
||||
///
|
||||
/// <div style="position: relative;">
|
||||
/// <a style="position: absolute; left: 0;" href="https://github.com/gleam-community/colour/issues">
|
||||
/// <small>Spot a typo? Open an issue!</small>
|
||||
/// </a>
|
||||
/// <a style="position: absolute; right: 0;" href="#">
|
||||
/// <small>Back to top ↑</small>
|
||||
/// </a>
|
||||
/// </div>
|
||||
///
|
||||
pub fn maximum_contrast(
|
||||
base: Colour,
|
||||
colours: List(Colour),
|
||||
) -> Result(Colour, Nil) {
|
||||
colours
|
||||
|> list.sort(fn(colour_a, colour_b) {
|
||||
let contrast_a = contrast_ratio(base, colour_a)
|
||||
let contrast_b = contrast_ratio(base, colour_b)
|
||||
|
||||
float.compare(contrast_b, contrast_a)
|
||||
})
|
||||
|> list.first()
|
||||
}
|
||||
1199
build/packages/gleam_community_colour/src/gleam_community@colour.erl
Normal file
1199
build/packages/gleam_community_colour/src/gleam_community@colour.erl
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,203 @@
|
|||
-module(gleam_community@colour@accessibility).
|
||||
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).
|
||||
-define(FILEPATH, "src/gleam_community/colour/accessibility.gleam").
|
||||
-export([luminance/1, contrast_ratio/2, maximum_contrast/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(
|
||||
" \n"
|
||||
" - **Accessibility**\n"
|
||||
" - [`luminance`](#luminance)\n"
|
||||
" - [`contrast_ratio`](#contrast_ratio)\n"
|
||||
" - [`maximum_contrast`](#maximum_contrast)\n"
|
||||
"\n"
|
||||
" ---\n"
|
||||
"\n"
|
||||
" This package was heavily inspired by the `elm-color-extra` module.\n"
|
||||
" The original source code can be found\n"
|
||||
" <a href=\"https://github.com/noahzgordon/elm-color-extra\">here</a>.\n"
|
||||
"\n"
|
||||
" <details>\n"
|
||||
" <summary>The license of that package is produced below:</summary>\n"
|
||||
" \n"
|
||||
" \n"
|
||||
" > MIT License\n"
|
||||
"\n"
|
||||
" > Copyright (c) 2016 Andreas Köberle\n"
|
||||
"\n"
|
||||
" > Permission is hereby granted, free of charge, to any person obtaining a copy\n"
|
||||
" of this software and associated documentation files (the \"Software\"), to deal\n"
|
||||
" in the Software without restriction, including without limitation the rights\n"
|
||||
" to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n"
|
||||
" copies of the Software, and to permit persons to whom the Software is\n"
|
||||
" furnished to do so, subject to the following conditions:\n"
|
||||
"\n"
|
||||
" > The above copyright notice and this permission notice shall be included in all\n"
|
||||
" copies or substantial portions of the Software.\n"
|
||||
"\n"
|
||||
" > THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n"
|
||||
" IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n"
|
||||
" FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n"
|
||||
" AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n"
|
||||
" LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n"
|
||||
" OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n"
|
||||
" SOFTWARE.\n"
|
||||
"\n"
|
||||
" </details>\n"
|
||||
"\n"
|
||||
).
|
||||
|
||||
-file("src/gleam_community/colour/accessibility.gleam", 56).
|
||||
-spec intensity(float()) -> float().
|
||||
intensity(Colour_value) ->
|
||||
case true of
|
||||
_ when Colour_value =< 0.03928 ->
|
||||
Colour_value / 12.92;
|
||||
|
||||
_ ->
|
||||
I@1 = case gleam@float:power((Colour_value + 0.055) / 1.055, 2.4) of
|
||||
{ok, I} -> I;
|
||||
_assert_fail ->
|
||||
erlang:error(#{gleam_error => let_assert,
|
||||
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
|
||||
file => <<?FILEPATH/utf8>>,
|
||||
module => <<"gleam_community/colour/accessibility"/utf8>>,
|
||||
function => <<"intensity"/utf8>>,
|
||||
line => 62,
|
||||
value => _assert_fail,
|
||||
start => 2399,
|
||||
'end' => 2470,
|
||||
pattern_start => 2410,
|
||||
pattern_end => 2415})
|
||||
end,
|
||||
I@1
|
||||
end.
|
||||
|
||||
-file("src/gleam_community/colour/accessibility.gleam", 92).
|
||||
?DOC(
|
||||
" Returns the relative brightness of the given `Colour` as a `Float` between\n"
|
||||
" 0.0, and 1.0 with 0.0 being the darkest possible colour and 1.0 being the lightest.\n"
|
||||
"\n"
|
||||
" <details>\n"
|
||||
" <summary>Example:</summary>\n"
|
||||
"\n"
|
||||
" ```gleam\n"
|
||||
" fn example() {\n"
|
||||
" luminance(colour.white) // 1.0\n"
|
||||
" }\n"
|
||||
" ```\n"
|
||||
" </details>\n"
|
||||
"\n"
|
||||
" <div style=\"position: relative;\">\n"
|
||||
" <a style=\"position: absolute; left: 0;\" href=\"https://github.com/gleam-community/colour/issues\">\n"
|
||||
" <small>Spot a typo? Open an issue!</small>\n"
|
||||
" </a>\n"
|
||||
" <a style=\"position: absolute; right: 0;\" href=\"#\">\n"
|
||||
" <small>Back to top ↑</small>\n"
|
||||
" </a>\n"
|
||||
" </div>\n"
|
||||
).
|
||||
-spec luminance(gleam_community@colour:colour()) -> float().
|
||||
luminance(Colour) ->
|
||||
{R, G, B, _} = gleam_community@colour:to_rgba(Colour),
|
||||
R_intensity = intensity(R),
|
||||
G_intensity = intensity(G),
|
||||
B_intensity = intensity(B),
|
||||
((0.2126 * R_intensity) + (0.7152 * G_intensity)) + (0.0722 * B_intensity).
|
||||
|
||||
-file("src/gleam_community/colour/accessibility.gleam", 125).
|
||||
?DOC(
|
||||
" Returns the contrast between two `Colour` values as a `Float` between 1.0,\n"
|
||||
" and 21.0 with 1.0 being no contrast and, 21.0 being the highest possible contrast.\n"
|
||||
"\n"
|
||||
" <details>\n"
|
||||
" <summary>Example:</summary>\n"
|
||||
"\n"
|
||||
" ```gleam\n"
|
||||
" fn example() {\n"
|
||||
" contrast_ratio(between: colour.white, and: colour.black) // 21.0\n"
|
||||
" }\n"
|
||||
" ```\n"
|
||||
" </details>\n"
|
||||
"\n"
|
||||
" <div style=\"position: relative;\">\n"
|
||||
" <a style=\"position: absolute; left: 0;\" href=\"https://github.com/gleam-community/colour/issues\">\n"
|
||||
" <small>Spot a typo? Open an issue!</small>\n"
|
||||
" </a>\n"
|
||||
" <a style=\"position: absolute; right: 0;\" href=\"#\">\n"
|
||||
" <small>Back to top ↑</small>\n"
|
||||
" </a>\n"
|
||||
" </div>\n"
|
||||
).
|
||||
-spec contrast_ratio(
|
||||
gleam_community@colour:colour(),
|
||||
gleam_community@colour:colour()
|
||||
) -> float().
|
||||
contrast_ratio(Colour_a, Colour_b) ->
|
||||
Luminance_a = luminance(Colour_a) + 0.05,
|
||||
Luminance_b = luminance(Colour_b) + 0.05,
|
||||
case Luminance_a > Luminance_b of
|
||||
true ->
|
||||
case Luminance_b of
|
||||
+0.0 -> +0.0;
|
||||
-0.0 -> -0.0;
|
||||
Gleam@denominator -> Luminance_a / Gleam@denominator
|
||||
end;
|
||||
|
||||
false ->
|
||||
case Luminance_a of
|
||||
+0.0 -> +0.0;
|
||||
-0.0 -> -0.0;
|
||||
Gleam@denominator@1 -> Luminance_b / Gleam@denominator@1
|
||||
end
|
||||
end.
|
||||
|
||||
-file("src/gleam_community/colour/accessibility.gleam", 161).
|
||||
?DOC(
|
||||
" Returns the `Colour` with the highest contrast between the base `Colour`,\n"
|
||||
" and and the other provided `Colour` values.\n"
|
||||
"\n"
|
||||
" <details>\n"
|
||||
" <summary>Example:</summary>\n"
|
||||
"\n"
|
||||
" ```gleam\n"
|
||||
" fn example() {\n"
|
||||
" maximum_contrast(\n"
|
||||
" colour.yellow,\n"
|
||||
" [colour.white, colour.dark_blue, colour.green],\n"
|
||||
" )\n"
|
||||
" }\n"
|
||||
" ```\n"
|
||||
" </details>\n"
|
||||
"\n"
|
||||
" <div style=\"position: relative;\">\n"
|
||||
" <a style=\"position: absolute; left: 0;\" href=\"https://github.com/gleam-community/colour/issues\">\n"
|
||||
" <small>Spot a typo? Open an issue!</small>\n"
|
||||
" </a>\n"
|
||||
" <a style=\"position: absolute; right: 0;\" href=\"#\">\n"
|
||||
" <small>Back to top ↑</small>\n"
|
||||
" </a>\n"
|
||||
" </div>\n"
|
||||
).
|
||||
-spec maximum_contrast(
|
||||
gleam_community@colour:colour(),
|
||||
list(gleam_community@colour:colour())
|
||||
) -> {ok, gleam_community@colour:colour()} | {error, nil}.
|
||||
maximum_contrast(Base, Colours) ->
|
||||
_pipe = Colours,
|
||||
_pipe@1 = gleam@list:sort(
|
||||
_pipe,
|
||||
fun(Colour_a, Colour_b) ->
|
||||
Contrast_a = contrast_ratio(Base, Colour_a),
|
||||
Contrast_b = contrast_ratio(Base, Colour_b),
|
||||
gleam@float:compare(Contrast_b, Contrast_a)
|
||||
end
|
||||
),
|
||||
gleam@list:first(_pipe@1).
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
{application, gleam_community_colour, [
|
||||
{vsn, "2.0.2"},
|
||||
{applications, [gleam_json,
|
||||
gleam_stdlib]},
|
||||
{description, "Colour types, conversions, and other utilities"},
|
||||
{modules, [gleam_community@colour,
|
||||
gleam_community@colour@accessibility,
|
||||
gleam_community_colour@@main]},
|
||||
{registered, []}
|
||||
]}.
|
||||
191
build/packages/gleam_json/LICENCE
Normal file
191
build/packages/gleam_json/LICENCE
Normal 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 2021 - present, 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.
|
||||
|
||||
47
build/packages/gleam_json/README.md
Normal file
47
build/packages/gleam_json/README.md
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
# json 🐑
|
||||
|
||||
Work with JSON in Gleam!
|
||||
|
||||
## Installation
|
||||
|
||||
```shell
|
||||
gleam add gleam_json@3
|
||||
```
|
||||
|
||||
## Encoding
|
||||
|
||||
```gleam
|
||||
import myapp.{type Cat}
|
||||
import gleam/json
|
||||
|
||||
pub fn cat_to_json(cat: Cat) -> String {
|
||||
json.object([
|
||||
#("name", json.string(cat.name)),
|
||||
#("lives", json.int(cat.lives)),
|
||||
#("flaws", json.null()),
|
||||
#("nicknames", json.array(cat.nicknames, of: json.string)),
|
||||
])
|
||||
|> json.to_string
|
||||
}
|
||||
```
|
||||
|
||||
## Parsing
|
||||
|
||||
JSON is parsed into a `Dynamic` value which can be decoded using the
|
||||
`gleam/dynamic/decode` module from the Gleam standard library.
|
||||
|
||||
```gleam
|
||||
import myapp.{Cat}
|
||||
import gleam/json
|
||||
import gleam/dynamic/decode
|
||||
|
||||
pub fn cat_from_json(json_string: String) -> Result(Cat, json.DecodeError) {
|
||||
let cat_decoder = {
|
||||
use name <- decode.field("name", decode.string)
|
||||
use lives <- decode.field("lives", decode.int)
|
||||
use nicknames <- decode.field("nicknames", decode.list(decode.string))
|
||||
decode.success(Cat(name:, lives:, nicknames:))
|
||||
}
|
||||
json.parse(from: json_string, using: cat_decoder)
|
||||
}
|
||||
```
|
||||
18
build/packages/gleam_json/gleam.toml
Normal file
18
build/packages/gleam_json/gleam.toml
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
name = "gleam_json"
|
||||
version = "3.1.0"
|
||||
gleam = ">= 1.13.0"
|
||||
|
||||
licences = ["Apache-2.0"]
|
||||
description = "Work with JSON in Gleam"
|
||||
|
||||
repository = { type = "github", user = "gleam-lang", repo = "json" }
|
||||
links = [
|
||||
{ title = "Website", href = "https://gleam.run" },
|
||||
{ title = "Sponsor", href = "https://github.com/sponsors/lpil" },
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
gleam_stdlib = ">= 0.51.0 and < 2.0.0"
|
||||
|
||||
[dev-dependencies]
|
||||
gleeunit = ">= 1.2.0 and < 2.0.0"
|
||||
316
build/packages/gleam_json/src/gleam/json.gleam
Normal file
316
build/packages/gleam_json/src/gleam/json.gleam
Normal file
|
|
@ -0,0 +1,316 @@
|
|||
import gleam/bit_array
|
||||
import gleam/dict.{type Dict}
|
||||
import gleam/dynamic.{type Dynamic}
|
||||
import gleam/dynamic/decode
|
||||
import gleam/list
|
||||
import gleam/option.{type Option, None, Some}
|
||||
import gleam/result
|
||||
import gleam/string_tree.{type StringTree}
|
||||
|
||||
pub type Json
|
||||
|
||||
pub type DecodeError {
|
||||
UnexpectedEndOfInput
|
||||
UnexpectedByte(String)
|
||||
UnexpectedSequence(String)
|
||||
UnableToDecode(List(decode.DecodeError))
|
||||
}
|
||||
|
||||
/// Decode a JSON string into dynamically typed data which can be decoded into
|
||||
/// typed data with the `gleam/dynamic` module.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```gleam
|
||||
/// > parse("[1,2,3]", decode.list(of: decode.int))
|
||||
/// Ok([1, 2, 3])
|
||||
/// ```
|
||||
///
|
||||
/// ```gleam
|
||||
/// > parse("[", decode.list(of: decode.int))
|
||||
/// Error(UnexpectedEndOfInput)
|
||||
/// ```
|
||||
///
|
||||
/// ```gleam
|
||||
/// > parse("1", decode.string)
|
||||
/// Error(UnableToDecode([decode.DecodeError("String", "Int", [])]))
|
||||
/// ```
|
||||
///
|
||||
pub fn parse(
|
||||
from json: String,
|
||||
using decoder: decode.Decoder(t),
|
||||
) -> Result(t, DecodeError) {
|
||||
do_parse(from: json, using: decoder)
|
||||
}
|
||||
|
||||
@target(erlang)
|
||||
fn do_parse(
|
||||
from json: String,
|
||||
using decoder: decode.Decoder(t),
|
||||
) -> Result(t, DecodeError) {
|
||||
let bits = bit_array.from_string(json)
|
||||
parse_bits(bits, decoder)
|
||||
}
|
||||
|
||||
@target(javascript)
|
||||
fn do_parse(
|
||||
from json: String,
|
||||
using decoder: decode.Decoder(t),
|
||||
) -> Result(t, DecodeError) {
|
||||
use dynamic_value <- result.try(decode_string(json))
|
||||
decode.run(dynamic_value, decoder)
|
||||
|> result.map_error(UnableToDecode)
|
||||
}
|
||||
|
||||
@external(javascript, "../gleam_json_ffi.mjs", "decode")
|
||||
fn decode_string(a: String) -> Result(Dynamic, DecodeError)
|
||||
|
||||
/// Decode a JSON bit string into dynamically typed data which can be decoded
|
||||
/// into typed data with the `gleam/dynamic` module.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```gleam
|
||||
/// > parse_bits(<<"[1,2,3]">>, decode.list(of: decode.int))
|
||||
/// Ok([1, 2, 3])
|
||||
/// ```
|
||||
///
|
||||
/// ```gleam
|
||||
/// > parse_bits(<<"[">>, decode.list(of: decode.int))
|
||||
/// Error(UnexpectedEndOfInput)
|
||||
/// ```
|
||||
///
|
||||
/// ```gleam
|
||||
/// > parse_bits(<<"1">>, decode.string)
|
||||
/// Error(UnableToDecode([decode.DecodeError("String", "Int", [])])),
|
||||
/// ```
|
||||
///
|
||||
pub fn parse_bits(
|
||||
from json: BitArray,
|
||||
using decoder: decode.Decoder(t),
|
||||
) -> Result(t, DecodeError) {
|
||||
use dynamic_value <- result.try(decode_to_dynamic(json))
|
||||
decode.run(dynamic_value, decoder)
|
||||
|> result.map_error(UnableToDecode)
|
||||
}
|
||||
|
||||
@external(erlang, "gleam_json_ffi", "decode")
|
||||
fn decode_to_dynamic(json: BitArray) -> Result(Dynamic, DecodeError) {
|
||||
case bit_array.to_string(json) {
|
||||
Ok(string) -> decode_string(string)
|
||||
Error(Nil) -> Error(UnexpectedByte(""))
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a JSON value into a string.
|
||||
///
|
||||
/// Where possible prefer the `to_string_tree` function as it is faster than
|
||||
/// this function, and BEAM VM IO is optimised for sending `StringTree` data.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```gleam
|
||||
/// > to_string(array([1, 2, 3], of: int))
|
||||
/// "[1,2,3]"
|
||||
/// ```
|
||||
///
|
||||
pub fn to_string(json: Json) -> String {
|
||||
do_to_string(json)
|
||||
}
|
||||
|
||||
@external(erlang, "gleam_json_ffi", "json_to_string")
|
||||
@external(javascript, "../gleam_json_ffi.mjs", "json_to_string")
|
||||
fn do_to_string(a: Json) -> String
|
||||
|
||||
/// Convert a JSON value into a string tree.
|
||||
///
|
||||
/// Where possible prefer this function to the `to_string` function as it is
|
||||
/// slower than this function, and BEAM VM IO is optimised for sending
|
||||
/// `StringTree` data.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```gleam
|
||||
/// > to_string_tree(array([1, 2, 3], of: int))
|
||||
/// string_tree.from_string("[1,2,3]")
|
||||
/// ```
|
||||
///
|
||||
@external(erlang, "gleam_json_ffi", "json_to_iodata")
|
||||
@external(javascript, "../gleam_json_ffi.mjs", "json_to_string")
|
||||
pub fn to_string_tree(json: Json) -> StringTree
|
||||
|
||||
/// Encode a string into JSON, using normal JSON escaping.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```gleam
|
||||
/// > to_string(string("Hello!"))
|
||||
/// "\"Hello!\""
|
||||
/// ```
|
||||
///
|
||||
pub fn string(input: String) -> Json {
|
||||
do_string(input)
|
||||
}
|
||||
|
||||
@external(erlang, "gleam_json_ffi", "string")
|
||||
@external(javascript, "../gleam_json_ffi.mjs", "identity")
|
||||
fn do_string(a: String) -> Json
|
||||
|
||||
/// Encode a bool into JSON.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```gleam
|
||||
/// > to_string(bool(False))
|
||||
/// "false"
|
||||
/// ```
|
||||
///
|
||||
pub fn bool(input: Bool) -> Json {
|
||||
do_bool(input)
|
||||
}
|
||||
|
||||
@external(erlang, "gleam_json_ffi", "bool")
|
||||
@external(javascript, "../gleam_json_ffi.mjs", "identity")
|
||||
fn do_bool(a: Bool) -> Json
|
||||
|
||||
/// Encode an int into JSON.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```gleam
|
||||
/// > to_string(int(50))
|
||||
/// "50"
|
||||
/// ```
|
||||
///
|
||||
pub fn int(input: Int) -> Json {
|
||||
do_int(input)
|
||||
}
|
||||
|
||||
@external(erlang, "gleam_json_ffi", "int")
|
||||
@external(javascript, "../gleam_json_ffi.mjs", "identity")
|
||||
fn do_int(a: Int) -> Json
|
||||
|
||||
/// Encode a float into JSON.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```gleam
|
||||
/// > to_string(float(4.7))
|
||||
/// "4.7"
|
||||
/// ```
|
||||
///
|
||||
pub fn float(input: Float) -> Json {
|
||||
do_float(input)
|
||||
}
|
||||
|
||||
@external(erlang, "gleam_json_ffi", "float")
|
||||
@external(javascript, "../gleam_json_ffi.mjs", "identity")
|
||||
fn do_float(input input: Float) -> Json
|
||||
|
||||
/// The JSON value null.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```gleam
|
||||
/// > to_string(null())
|
||||
/// "null"
|
||||
/// ```
|
||||
///
|
||||
pub fn null() -> Json {
|
||||
do_null()
|
||||
}
|
||||
|
||||
@external(erlang, "gleam_json_ffi", "null")
|
||||
@external(javascript, "../gleam_json_ffi.mjs", "do_null")
|
||||
fn do_null() -> Json
|
||||
|
||||
/// Encode an optional value into JSON, using null if it is the `None` variant.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```gleam
|
||||
/// > to_string(nullable(Some(50), of: int))
|
||||
/// "50"
|
||||
/// ```
|
||||
///
|
||||
/// ```gleam
|
||||
/// > to_string(nullable(None, of: int))
|
||||
/// "null"
|
||||
/// ```
|
||||
///
|
||||
pub fn nullable(from input: Option(a), of inner_type: fn(a) -> Json) -> Json {
|
||||
case input {
|
||||
Some(value) -> inner_type(value)
|
||||
None -> null()
|
||||
}
|
||||
}
|
||||
|
||||
/// Encode a list of key-value pairs into a JSON object.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```gleam
|
||||
/// > to_string(object([
|
||||
/// #("game", string("Pac-Man")),
|
||||
/// #("score", int(3333360)),
|
||||
/// ]))
|
||||
/// "{\"game\":\"Pac-Mac\",\"score\":3333360}"
|
||||
/// ```
|
||||
///
|
||||
pub fn object(entries: List(#(String, Json))) -> Json {
|
||||
do_object(entries)
|
||||
}
|
||||
|
||||
@external(erlang, "gleam_json_ffi", "object")
|
||||
@external(javascript, "../gleam_json_ffi.mjs", "object")
|
||||
fn do_object(entries entries: List(#(String, Json))) -> Json
|
||||
|
||||
/// Encode a list into a JSON array.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```gleam
|
||||
/// > to_string(array([1, 2, 3], of: int))
|
||||
/// "[1, 2, 3]"
|
||||
/// ```
|
||||
///
|
||||
pub fn array(from entries: List(a), of inner_type: fn(a) -> Json) -> Json {
|
||||
entries
|
||||
|> list.map(inner_type)
|
||||
|> preprocessed_array
|
||||
}
|
||||
|
||||
/// Encode a list of JSON values into a JSON array.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```gleam
|
||||
/// > to_string(preprocessed_array([int(1), float(2.0), string("3")]))
|
||||
/// "[1, 2.0, \"3\"]"
|
||||
/// ```
|
||||
///
|
||||
pub fn preprocessed_array(from: List(Json)) -> Json {
|
||||
do_preprocessed_array(from)
|
||||
}
|
||||
|
||||
@external(erlang, "gleam_json_ffi", "array")
|
||||
@external(javascript, "../gleam_json_ffi.mjs", "array")
|
||||
fn do_preprocessed_array(from from: List(Json)) -> Json
|
||||
|
||||
/// Encode a Dict into a JSON object using the supplied functions to encode
|
||||
/// the keys and the values respectively.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```gleam
|
||||
/// > to_string(dict(dict.from_list([ #(3, 3.0), #(4, 4.0)]), int.to_string, float)
|
||||
/// "{\"3\": 3.0, \"4\": 4.0}"
|
||||
/// ```
|
||||
///
|
||||
pub fn dict(
|
||||
dict: Dict(k, v),
|
||||
keys: fn(k) -> String,
|
||||
values: fn(v) -> Json,
|
||||
) -> Json {
|
||||
object(dict.fold(dict, [], fn(acc, k, v) { [#(keys(k), values(v)), ..acc] }))
|
||||
}
|
||||
304
build/packages/gleam_json/src/gleam@json.erl
Normal file
304
build/packages/gleam_json/src/gleam@json.erl
Normal file
|
|
@ -0,0 +1,304 @@
|
|||
-module(gleam@json).
|
||||
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
|
||||
-define(FILEPATH, "src/gleam/json.gleam").
|
||||
-export([parse_bits/2, parse/2, to_string/1, to_string_tree/1, string/1, bool/1, int/1, float/1, null/0, nullable/2, object/1, preprocessed_array/1, array/2, dict/3]).
|
||||
-export_type([json/0, decode_error/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 json() :: any().
|
||||
|
||||
-type decode_error() :: unexpected_end_of_input |
|
||||
{unexpected_byte, binary()} |
|
||||
{unexpected_sequence, binary()} |
|
||||
{unable_to_decode, list(gleam@dynamic@decode:decode_error())}.
|
||||
|
||||
-file("src/gleam/json.gleam", 88).
|
||||
?DOC(
|
||||
" Decode a JSON bit string into dynamically typed data which can be decoded\n"
|
||||
" into typed data with the `gleam/dynamic` module.\n"
|
||||
"\n"
|
||||
" ## Examples\n"
|
||||
"\n"
|
||||
" ```gleam\n"
|
||||
" > parse_bits(<<\"[1,2,3]\">>, decode.list(of: decode.int))\n"
|
||||
" Ok([1, 2, 3])\n"
|
||||
" ```\n"
|
||||
"\n"
|
||||
" ```gleam\n"
|
||||
" > parse_bits(<<\"[\">>, decode.list(of: decode.int))\n"
|
||||
" Error(UnexpectedEndOfInput)\n"
|
||||
" ```\n"
|
||||
"\n"
|
||||
" ```gleam\n"
|
||||
" > parse_bits(<<\"1\">>, decode.string)\n"
|
||||
" Error(UnableToDecode([decode.DecodeError(\"String\", \"Int\", [])])),\n"
|
||||
" ```\n"
|
||||
).
|
||||
-spec parse_bits(bitstring(), gleam@dynamic@decode:decoder(DNO)) -> {ok, DNO} |
|
||||
{error, decode_error()}.
|
||||
parse_bits(Json, Decoder) ->
|
||||
gleam@result:'try'(
|
||||
gleam_json_ffi:decode(Json),
|
||||
fun(Dynamic_value) ->
|
||||
_pipe = gleam@dynamic@decode:run(Dynamic_value, Decoder),
|
||||
gleam@result:map_error(
|
||||
_pipe,
|
||||
fun(Field@0) -> {unable_to_decode, Field@0} end
|
||||
)
|
||||
end
|
||||
).
|
||||
|
||||
-file("src/gleam/json.gleam", 47).
|
||||
-spec do_parse(binary(), gleam@dynamic@decode:decoder(DNI)) -> {ok, DNI} |
|
||||
{error, decode_error()}.
|
||||
do_parse(Json, Decoder) ->
|
||||
Bits = gleam_stdlib:identity(Json),
|
||||
parse_bits(Bits, Decoder).
|
||||
|
||||
-file("src/gleam/json.gleam", 39).
|
||||
?DOC(
|
||||
" Decode a JSON string into dynamically typed data which can be decoded into\n"
|
||||
" typed data with the `gleam/dynamic` module.\n"
|
||||
"\n"
|
||||
" ## Examples\n"
|
||||
"\n"
|
||||
" ```gleam\n"
|
||||
" > parse(\"[1,2,3]\", decode.list(of: decode.int))\n"
|
||||
" Ok([1, 2, 3])\n"
|
||||
" ```\n"
|
||||
"\n"
|
||||
" ```gleam\n"
|
||||
" > parse(\"[\", decode.list(of: decode.int))\n"
|
||||
" Error(UnexpectedEndOfInput)\n"
|
||||
" ```\n"
|
||||
"\n"
|
||||
" ```gleam\n"
|
||||
" > parse(\"1\", decode.string)\n"
|
||||
" Error(UnableToDecode([decode.DecodeError(\"String\", \"Int\", [])]))\n"
|
||||
" ```\n"
|
||||
).
|
||||
-spec parse(binary(), gleam@dynamic@decode:decoder(DNE)) -> {ok, DNE} |
|
||||
{error, decode_error()}.
|
||||
parse(Json, Decoder) ->
|
||||
do_parse(Json, Decoder).
|
||||
|
||||
-file("src/gleam/json.gleam", 117).
|
||||
?DOC(
|
||||
" Convert a JSON value into a string.\n"
|
||||
"\n"
|
||||
" Where possible prefer the `to_string_tree` function as it is faster than\n"
|
||||
" this function, and BEAM VM IO is optimised for sending `StringTree` data.\n"
|
||||
"\n"
|
||||
" ## Examples\n"
|
||||
"\n"
|
||||
" ```gleam\n"
|
||||
" > to_string(array([1, 2, 3], of: int))\n"
|
||||
" \"[1,2,3]\"\n"
|
||||
" ```\n"
|
||||
).
|
||||
-spec to_string(json()) -> binary().
|
||||
to_string(Json) ->
|
||||
gleam_json_ffi:json_to_string(Json).
|
||||
|
||||
-file("src/gleam/json.gleam", 140).
|
||||
?DOC(
|
||||
" Convert a JSON value into a string tree.\n"
|
||||
"\n"
|
||||
" Where possible prefer this function to the `to_string` function as it is\n"
|
||||
" slower than this function, and BEAM VM IO is optimised for sending\n"
|
||||
" `StringTree` data.\n"
|
||||
"\n"
|
||||
" ## Examples\n"
|
||||
"\n"
|
||||
" ```gleam\n"
|
||||
" > to_string_tree(array([1, 2, 3], of: int))\n"
|
||||
" string_tree.from_string(\"[1,2,3]\")\n"
|
||||
" ```\n"
|
||||
).
|
||||
-spec to_string_tree(json()) -> gleam@string_tree:string_tree().
|
||||
to_string_tree(Json) ->
|
||||
gleam_json_ffi:json_to_iodata(Json).
|
||||
|
||||
-file("src/gleam/json.gleam", 151).
|
||||
?DOC(
|
||||
" Encode a string into JSON, using normal JSON escaping.\n"
|
||||
"\n"
|
||||
" ## Examples\n"
|
||||
"\n"
|
||||
" ```gleam\n"
|
||||
" > to_string(string(\"Hello!\"))\n"
|
||||
" \"\\\"Hello!\\\"\"\n"
|
||||
" ```\n"
|
||||
).
|
||||
-spec string(binary()) -> json().
|
||||
string(Input) ->
|
||||
gleam_json_ffi:string(Input).
|
||||
|
||||
-file("src/gleam/json.gleam", 168).
|
||||
?DOC(
|
||||
" Encode a bool into JSON.\n"
|
||||
"\n"
|
||||
" ## Examples\n"
|
||||
"\n"
|
||||
" ```gleam\n"
|
||||
" > to_string(bool(False))\n"
|
||||
" \"false\"\n"
|
||||
" ```\n"
|
||||
).
|
||||
-spec bool(boolean()) -> json().
|
||||
bool(Input) ->
|
||||
gleam_json_ffi:bool(Input).
|
||||
|
||||
-file("src/gleam/json.gleam", 185).
|
||||
?DOC(
|
||||
" Encode an int into JSON.\n"
|
||||
"\n"
|
||||
" ## Examples\n"
|
||||
"\n"
|
||||
" ```gleam\n"
|
||||
" > to_string(int(50))\n"
|
||||
" \"50\"\n"
|
||||
" ```\n"
|
||||
).
|
||||
-spec int(integer()) -> json().
|
||||
int(Input) ->
|
||||
gleam_json_ffi:int(Input).
|
||||
|
||||
-file("src/gleam/json.gleam", 202).
|
||||
?DOC(
|
||||
" Encode a float into JSON.\n"
|
||||
"\n"
|
||||
" ## Examples\n"
|
||||
"\n"
|
||||
" ```gleam\n"
|
||||
" > to_string(float(4.7))\n"
|
||||
" \"4.7\"\n"
|
||||
" ```\n"
|
||||
).
|
||||
-spec float(float()) -> json().
|
||||
float(Input) ->
|
||||
gleam_json_ffi:float(Input).
|
||||
|
||||
-file("src/gleam/json.gleam", 219).
|
||||
?DOC(
|
||||
" The JSON value null.\n"
|
||||
"\n"
|
||||
" ## Examples\n"
|
||||
"\n"
|
||||
" ```gleam\n"
|
||||
" > to_string(null())\n"
|
||||
" \"null\"\n"
|
||||
" ```\n"
|
||||
).
|
||||
-spec null() -> json().
|
||||
null() ->
|
||||
gleam_json_ffi:null().
|
||||
|
||||
-file("src/gleam/json.gleam", 241).
|
||||
?DOC(
|
||||
" Encode an optional value into JSON, using null if it is the `None` variant.\n"
|
||||
"\n"
|
||||
" ## Examples\n"
|
||||
"\n"
|
||||
" ```gleam\n"
|
||||
" > to_string(nullable(Some(50), of: int))\n"
|
||||
" \"50\"\n"
|
||||
" ```\n"
|
||||
"\n"
|
||||
" ```gleam\n"
|
||||
" > to_string(nullable(None, of: int))\n"
|
||||
" \"null\"\n"
|
||||
" ```\n"
|
||||
).
|
||||
-spec nullable(gleam@option:option(DNU), fun((DNU) -> json())) -> json().
|
||||
nullable(Input, Inner_type) ->
|
||||
case Input of
|
||||
{some, Value} ->
|
||||
Inner_type(Value);
|
||||
|
||||
none ->
|
||||
null()
|
||||
end.
|
||||
|
||||
-file("src/gleam/json.gleam", 260).
|
||||
?DOC(
|
||||
" Encode a list of key-value pairs into a JSON object.\n"
|
||||
"\n"
|
||||
" ## Examples\n"
|
||||
"\n"
|
||||
" ```gleam\n"
|
||||
" > to_string(object([\n"
|
||||
" #(\"game\", string(\"Pac-Man\")),\n"
|
||||
" #(\"score\", int(3333360)),\n"
|
||||
" ]))\n"
|
||||
" \"{\\\"game\\\":\\\"Pac-Mac\\\",\\\"score\\\":3333360}\"\n"
|
||||
" ```\n"
|
||||
).
|
||||
-spec object(list({binary(), json()})) -> json().
|
||||
object(Entries) ->
|
||||
gleam_json_ffi:object(Entries).
|
||||
|
||||
-file("src/gleam/json.gleam", 292).
|
||||
?DOC(
|
||||
" Encode a list of JSON values into a JSON array.\n"
|
||||
"\n"
|
||||
" ## Examples\n"
|
||||
"\n"
|
||||
" ```gleam\n"
|
||||
" > to_string(preprocessed_array([int(1), float(2.0), string(\"3\")]))\n"
|
||||
" \"[1, 2.0, \\\"3\\\"]\"\n"
|
||||
" ```\n"
|
||||
).
|
||||
-spec preprocessed_array(list(json())) -> json().
|
||||
preprocessed_array(From) ->
|
||||
gleam_json_ffi:array(From).
|
||||
|
||||
-file("src/gleam/json.gleam", 277).
|
||||
?DOC(
|
||||
" Encode a list into a JSON array.\n"
|
||||
"\n"
|
||||
" ## Examples\n"
|
||||
"\n"
|
||||
" ```gleam\n"
|
||||
" > to_string(array([1, 2, 3], of: int))\n"
|
||||
" \"[1, 2, 3]\"\n"
|
||||
" ```\n"
|
||||
).
|
||||
-spec array(list(DNY), fun((DNY) -> json())) -> json().
|
||||
array(Entries, Inner_type) ->
|
||||
_pipe = Entries,
|
||||
_pipe@1 = gleam@list:map(_pipe, Inner_type),
|
||||
preprocessed_array(_pipe@1).
|
||||
|
||||
-file("src/gleam/json.gleam", 310).
|
||||
?DOC(
|
||||
" Encode a Dict into a JSON object using the supplied functions to encode\n"
|
||||
" the keys and the values respectively.\n"
|
||||
"\n"
|
||||
" ## Examples\n"
|
||||
"\n"
|
||||
" ```gleam\n"
|
||||
" > to_string(dict(dict.from_list([ #(3, 3.0), #(4, 4.0)]), int.to_string, float)\n"
|
||||
" \"{\\\"3\\\": 3.0, \\\"4\\\": 4.0}\"\n"
|
||||
" ```\n"
|
||||
).
|
||||
-spec dict(
|
||||
gleam@dict:dict(DOC, DOD),
|
||||
fun((DOC) -> binary()),
|
||||
fun((DOD) -> json())
|
||||
) -> json().
|
||||
dict(Dict, Keys, Values) ->
|
||||
object(
|
||||
gleam@dict:fold(
|
||||
Dict,
|
||||
[],
|
||||
fun(Acc, K, V) -> [{Keys(K), Values(V)} | Acc] end
|
||||
)
|
||||
).
|
||||
9
build/packages/gleam_json/src/gleam_json.app.src
Normal file
9
build/packages/gleam_json/src/gleam_json.app.src
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
{application, gleam_json, [
|
||||
{vsn, "3.1.0"},
|
||||
{applications, [gleam_stdlib]},
|
||||
{description, "Work with JSON in Gleam"},
|
||||
{modules, [gleam@json,
|
||||
gleam_json@@main,
|
||||
gleam_json_ffi]},
|
||||
{registered, []}
|
||||
]}.
|
||||
66
build/packages/gleam_json/src/gleam_json_ffi.erl
Normal file
66
build/packages/gleam_json/src/gleam_json_ffi.erl
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
-module(gleam_json_ffi).
|
||||
|
||||
-export([
|
||||
decode/1, json_to_iodata/1, json_to_string/1, int/1, float/1, string/1,
|
||||
bool/1, null/0, array/1, object/1
|
||||
]).
|
||||
|
||||
-if(?OTP_RELEASE < 27).
|
||||
-define(bad_version,
|
||||
error({erlang_otp_27_required, << "Insufficient Erlang/OTP version.
|
||||
|
||||
`gleam_json` uses the Erlang `json` module introduced in Erlang/OTP 27.
|
||||
You are using Erlang/OTP "/utf8, (integer_to_binary(?OTP_RELEASE))/binary, "
|
||||
Please upgrade your Erlang install or downgrade to `gleam_json` v1.0.1.
|
||||
"/utf8>>})).
|
||||
|
||||
decode(_) -> ?bad_version.
|
||||
json_to_iodata(_) -> ?bad_version.
|
||||
json_to_string(_) -> ?bad_version.
|
||||
int(_) -> ?bad_version.
|
||||
float(_) -> ?bad_version.
|
||||
string(_) -> ?bad_version.
|
||||
bool(_) -> ?bad_version.
|
||||
array(_) -> ?bad_version.
|
||||
object(_) -> ?bad_version.
|
||||
null() -> ?bad_version.
|
||||
-else.
|
||||
|
||||
decode(Json) ->
|
||||
try
|
||||
{ok, json:decode(Json)}
|
||||
catch
|
||||
error:unexpected_end -> {error, unexpected_end_of_input};
|
||||
error:{invalid_byte, Byte} -> {error, {unexpected_byte, hex(Byte)}};
|
||||
error:{unexpected_sequence, Byte} -> {error, {unexpected_sequence, Byte}}
|
||||
end.
|
||||
|
||||
hex(I) ->
|
||||
H = list_to_binary(integer_to_list(I, 16)),
|
||||
<<"0x"/utf8, H/binary>>.
|
||||
|
||||
json_to_iodata(Json) ->
|
||||
Json.
|
||||
|
||||
json_to_string(Json) when is_binary(Json) ->
|
||||
Json;
|
||||
json_to_string(Json) when is_list(Json) ->
|
||||
list_to_binary(Json).
|
||||
|
||||
null() -> <<"null">>.
|
||||
bool(true) -> <<"true">>;
|
||||
bool(false) -> <<"false">>.
|
||||
int(X) -> json:encode_integer(X).
|
||||
float(X) -> json:encode_float(X).
|
||||
string(X) -> json:encode_binary(X).
|
||||
|
||||
array([]) -> <<"[]">>;
|
||||
array([First | Rest]) -> [$[, First | array_loop(Rest)].
|
||||
array_loop([]) -> "]";
|
||||
array_loop([Elem | Rest]) -> [$,, Elem | array_loop(Rest)].
|
||||
|
||||
object(List) -> encode_object([[$,, string(Key), $: | Value] || {Key, Value} <- List]).
|
||||
encode_object([]) -> <<"{}">>;
|
||||
encode_object([[_Comma | Entry] | Rest]) -> ["{", Entry, Rest, "}"].
|
||||
|
||||
-endif.
|
||||
201
build/packages/gleam_json/src/gleam_json_ffi.mjs
Normal file
201
build/packages/gleam_json/src/gleam_json_ffi.mjs
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
import {
|
||||
Result$Ok,
|
||||
Result$Error,
|
||||
List$isNonEmpty,
|
||||
List$NonEmpty$first,
|
||||
List$NonEmpty$rest,
|
||||
} from "./gleam.mjs";
|
||||
import {
|
||||
DecodeError$UnexpectedByte,
|
||||
DecodeError$UnexpectedEndOfInput,
|
||||
} from "./gleam/json.mjs";
|
||||
|
||||
export function json_to_string(json) {
|
||||
return JSON.stringify(json);
|
||||
}
|
||||
|
||||
export function object(entries) {
|
||||
return Object.fromEntries(entries);
|
||||
}
|
||||
|
||||
export function identity(x) {
|
||||
return x;
|
||||
}
|
||||
|
||||
export function array(list) {
|
||||
const array = [];
|
||||
while (List$isNonEmpty(list)) {
|
||||
array.push(List$NonEmpty$first(list));
|
||||
list = List$NonEmpty$rest(list);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
export function do_null() {
|
||||
return null;
|
||||
}
|
||||
|
||||
export function decode(string) {
|
||||
try {
|
||||
const result = JSON.parse(string);
|
||||
return Result$Ok(result);
|
||||
} catch (err) {
|
||||
return Result$Error(getJsonDecodeError(err, string));
|
||||
}
|
||||
}
|
||||
|
||||
export function getJsonDecodeError(stdErr, json) {
|
||||
if (isUnexpectedEndOfInput(stdErr)) return DecodeError$UnexpectedEndOfInput();
|
||||
return toUnexpectedByteError(stdErr, json);
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches unexpected end of input messages in:
|
||||
* - Chromium (edge, chrome, node)
|
||||
* - Spidermonkey (firefox)
|
||||
* - JavascriptCore (safari)
|
||||
*
|
||||
* Note that Spidermonkey and JavascriptCore will both incorrectly report some
|
||||
* UnexpectedByte errors as UnexpectedEndOfInput errors. For example:
|
||||
*
|
||||
* @example
|
||||
* // in JavascriptCore
|
||||
* JSON.parse('{"a"]: "b"})
|
||||
* // => JSON Parse error: Expected ':' before value
|
||||
*
|
||||
* JSON.parse('{"a"')
|
||||
* // => JSON Parse error: Expected ':' before value
|
||||
*
|
||||
* // in Chromium (correct)
|
||||
* JSON.parse('{"a"]: "b"})
|
||||
* // => Unexpected token ] in JSON at position 4
|
||||
*
|
||||
* JSON.parse('{"a"')
|
||||
* // => Unexpected end of JSON input
|
||||
*/
|
||||
function isUnexpectedEndOfInput(err) {
|
||||
const unexpectedEndOfInputRegex =
|
||||
/((unexpected (end|eof))|(end of data)|(unterminated string)|(json( parse error|\.parse)\: expected '(\:|\}|\])'))/i;
|
||||
return unexpectedEndOfInputRegex.test(err.message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a SyntaxError to an UnexpectedByte error based on the JS runtime.
|
||||
*
|
||||
* For Chromium, the unexpected byte and position are reported by the runtime.
|
||||
*
|
||||
* For JavascriptCore, only the unexpected byte is reported by the runtime, so
|
||||
* there is no way to know which position that character is in unless we then
|
||||
* parse the string again ourselves. So instead, the position is reported as 0.
|
||||
*
|
||||
* For Spidermonkey, the position is reported by the runtime as a line and column number
|
||||
* and the unexpected byte is found using those coordinates.
|
||||
*/
|
||||
function toUnexpectedByteError(err, json) {
|
||||
let converters = [
|
||||
v8UnexpectedByteError,
|
||||
oldV8UnexpectedByteError,
|
||||
jsCoreUnexpectedByteError,
|
||||
spidermonkeyUnexpectedByteError,
|
||||
];
|
||||
|
||||
for (let converter of converters) {
|
||||
let result = converter(err, json);
|
||||
if (result) return result;
|
||||
}
|
||||
|
||||
return DecodeError$UnexpectedByte("");
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches unexpected byte messages in:
|
||||
* - V8 (edge, chrome, node)
|
||||
*
|
||||
* Matches the character but not the position as this is no longer reported by
|
||||
* V8. Boo!
|
||||
*/
|
||||
function v8UnexpectedByteError(err) {
|
||||
const regex = /unexpected token '(.)', ".+" is not valid JSON/i;
|
||||
const match = regex.exec(err.message);
|
||||
if (!match) return null;
|
||||
const byte = toHex(match[1]);
|
||||
return DecodeError$UnexpectedByte(byte);
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches unexpected byte messages in:
|
||||
* - V8 (edge, chrome, node)
|
||||
*
|
||||
* No longer works in current versions of V8.
|
||||
*
|
||||
* Matches the character and its position.
|
||||
*/
|
||||
function oldV8UnexpectedByteError(err) {
|
||||
const regex = /unexpected token (.) in JSON at position (\d+)/i;
|
||||
const match = regex.exec(err.message);
|
||||
if (!match) return null;
|
||||
const byte = toHex(match[1]);
|
||||
return DecodeError$UnexpectedByte(byte);
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches unexpected byte messages in:
|
||||
* - Spidermonkey (firefox)
|
||||
*
|
||||
* Matches the position in a 2d grid only and not the character.
|
||||
*/
|
||||
function spidermonkeyUnexpectedByteError(err, json) {
|
||||
const regex =
|
||||
/(unexpected character|expected .*) at line (\d+) column (\d+)/i;
|
||||
const match = regex.exec(err.message);
|
||||
if (!match) return null;
|
||||
const line = Number(match[2]);
|
||||
const column = Number(match[3]);
|
||||
const position = getPositionFromMultiline(line, column, json);
|
||||
const byte = toHex(json[position]);
|
||||
return DecodeError$UnexpectedByte(byte);
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches unexpected byte messages in:
|
||||
* - JavascriptCore (safari)
|
||||
*
|
||||
* JavascriptCore only reports what the character is and not its position.
|
||||
*/
|
||||
function jsCoreUnexpectedByteError(err) {
|
||||
const regex = /unexpected (identifier|token) "(.)"/i;
|
||||
const match = regex.exec(err.message);
|
||||
if (!match) return null;
|
||||
const byte = toHex(match[2]);
|
||||
return DecodeError$UnexpectedByte(byte);
|
||||
}
|
||||
|
||||
function toHex(char) {
|
||||
return "0x" + char.charCodeAt(0).toString(16).toUpperCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the position of a character in a flattened (i.e. single line) string
|
||||
* from a line and column number. Note that the position is 0-indexed and
|
||||
* the line and column numbers are 1-indexed.
|
||||
*
|
||||
* @param {number} line
|
||||
* @param {number} column
|
||||
* @param {string} string
|
||||
*/
|
||||
function getPositionFromMultiline(line, column, string) {
|
||||
if (line === 1) return column - 1;
|
||||
|
||||
let currentLn = 1;
|
||||
let position = 0;
|
||||
string.split("").find((char, idx) => {
|
||||
if (char === "\n") currentLn += 1;
|
||||
if (currentLn === line) {
|
||||
position = idx + column;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
return position;
|
||||
}
|
||||
191
build/packages/gleam_stdlib/LICENCE
Normal file
191
build/packages/gleam_stdlib/LICENCE
Normal 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.
|
||||
|
||||
34
build/packages/gleam_stdlib/README.md
Normal file
34
build/packages/gleam_stdlib/README.md
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
# stdlib
|
||||
|
||||
[](https://hex.pm/packages/gleam_stdlib)
|
||||
[](https://hexdocs.pm/gleam_stdlib/)
|
||||
[](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.
|
||||
14
build/packages/gleam_stdlib/gleam.toml
Normal file
14
build/packages/gleam_stdlib/gleam.toml
Normal 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 = ["./"]
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
-record(decode_error, {
|
||||
expected :: binary(),
|
||||
found :: binary(),
|
||||
path :: list(binary())
|
||||
}).
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
-record(decoder, {
|
||||
function :: fun((gleam@dynamic:dynamic_()) -> {any(),
|
||||
list(gleam@dynamic@decode:decode_error())})
|
||||
}).
|
||||
1
build/packages/gleam_stdlib/include/gleam@set_Set.hrl
Normal file
1
build/packages/gleam_stdlib/include/gleam@set_Set.hrl
Normal file
|
|
@ -0,0 +1 @@
|
|||
-record(set, {dict :: gleam@dict:dict(any(), list(nil))}).
|
||||
9
build/packages/gleam_stdlib/include/gleam@uri_Uri.hrl
Normal file
9
build/packages/gleam_stdlib/include/gleam@uri_Uri.hrl
Normal 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())
|
||||
}).
|
||||
993
build/packages/gleam_stdlib/src/dict.mjs
Normal file
993
build/packages/gleam_stdlib/src/dict.mjs
Normal 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();
|
||||
280
build/packages/gleam_stdlib/src/gleam/bit_array.gleam
Normal file
280
build/packages/gleam_stdlib/src/gleam/bit_array.gleam
Normal 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
|
||||
}
|
||||
}
|
||||
316
build/packages/gleam_stdlib/src/gleam/bool.gleam
Normal file
316
build/packages/gleam_stdlib/src/gleam/bool.gleam
Normal 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()
|
||||
}
|
||||
}
|
||||
190
build/packages/gleam_stdlib/src/gleam/bytes_tree.gleam
Normal file
190
build/packages/gleam_stdlib/src/gleam/bytes_tree.gleam
Normal 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 })
|
||||
}
|
||||
548
build/packages/gleam_stdlib/src/gleam/dict.gleam
Normal file
548
build/packages/gleam_stdlib/src/gleam/dict.gleam
Normal 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)
|
||||
}
|
||||
}
|
||||
100
build/packages/gleam_stdlib/src/gleam/dynamic.gleam
Normal file
100
build/packages/gleam_stdlib/src/gleam/dynamic.gleam
Normal 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
|
||||
1061
build/packages/gleam_stdlib/src/gleam/dynamic/decode.gleam
Normal file
1061
build/packages/gleam_stdlib/src/gleam/dynamic/decode.gleam
Normal file
File diff suppressed because it is too large
Load diff
661
build/packages/gleam_stdlib/src/gleam/float.gleam
Normal file
661
build/packages/gleam_stdlib/src/gleam/float.gleam
Normal 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
|
||||
15
build/packages/gleam_stdlib/src/gleam/function.gleam
Normal file
15
build/packages/gleam_stdlib/src/gleam/function.gleam
Normal 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
|
||||
}
|
||||
828
build/packages/gleam_stdlib/src/gleam/int.gleam
Normal file
828
build/packages/gleam_stdlib/src/gleam/int.gleam
Normal 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
|
||||
59
build/packages/gleam_stdlib/src/gleam/io.gleam
Normal file
59
build/packages/gleam_stdlib/src/gleam/io.gleam
Normal 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
|
||||
2426
build/packages/gleam_stdlib/src/gleam/list.gleam
Normal file
2426
build/packages/gleam_stdlib/src/gleam/list.gleam
Normal file
File diff suppressed because it is too large
Load diff
358
build/packages/gleam_stdlib/src/gleam/option.gleam
Normal file
358
build/packages/gleam_stdlib/src/gleam/option.gleam
Normal 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])
|
||||
}
|
||||
}
|
||||
156
build/packages/gleam_stdlib/src/gleam/order.gleam
Normal file
156
build/packages/gleam_stdlib/src/gleam/order.gleam
Normal 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()
|
||||
}
|
||||
}
|
||||
85
build/packages/gleam_stdlib/src/gleam/pair.gleam
Normal file
85
build/packages/gleam_stdlib/src/gleam/pair.gleam
Normal 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)
|
||||
}
|
||||
453
build/packages/gleam_stdlib/src/gleam/result.gleam
Normal file
453
build/packages/gleam_stdlib/src/gleam/result.gleam
Normal 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)
|
||||
}
|
||||
}
|
||||
407
build/packages/gleam_stdlib/src/gleam/set.gleam
Normal file
407
build/packages/gleam_stdlib/src/gleam/set.gleam
Normal 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
|
||||
})
|
||||
}
|
||||
900
build/packages/gleam_stdlib/src/gleam/string.gleam
Normal file
900
build/packages/gleam_stdlib/src/gleam/string.gleam
Normal 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
|
||||
208
build/packages/gleam_stdlib/src/gleam/string_tree.gleam
Normal file
208
build/packages/gleam_stdlib/src/gleam/string_tree.gleam
Normal 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
|
||||
}
|
||||
770
build/packages/gleam_stdlib/src/gleam/uri.gleam
Normal file
770
build/packages/gleam_stdlib/src/gleam/uri.gleam
Normal 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], "/")
|
||||
}
|
||||
347
build/packages/gleam_stdlib/src/gleam@bit_array.erl
Normal file
347
build/packages/gleam_stdlib/src/gleam@bit_array.erl
Normal 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.
|
||||
352
build/packages/gleam_stdlib/src/gleam@bool.erl
Normal file
352
build/packages/gleam_stdlib/src/gleam@bool.erl
Normal 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.
|
||||
211
build/packages/gleam_stdlib/src/gleam@bytes_tree.erl
Normal file
211
build/packages/gleam_stdlib/src/gleam@bytes_tree.erl
Normal 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).
|
||||
561
build/packages/gleam_stdlib/src/gleam@dict.erl
Normal file
561
build/packages/gleam_stdlib/src/gleam@dict.erl
Normal 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
|
||||
).
|
||||
106
build/packages/gleam_stdlib/src/gleam@dynamic.erl
Normal file
106
build/packages/gleam_stdlib/src/gleam@dynamic.erl
Normal 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).
|
||||
1088
build/packages/gleam_stdlib/src/gleam@dynamic@decode.erl
Normal file
1088
build/packages/gleam_stdlib/src/gleam@dynamic@decode.erl
Normal file
File diff suppressed because it is too large
Load diff
744
build/packages/gleam_stdlib/src/gleam@float.erl
Normal file
744
build/packages/gleam_stdlib/src/gleam@float.erl
Normal 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).
|
||||
30
build/packages/gleam_stdlib/src/gleam@function.erl
Normal file
30
build/packages/gleam_stdlib/src/gleam@function.erl
Normal 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.
|
||||
986
build/packages/gleam_stdlib/src/gleam@int.erl
Normal file
986
build/packages/gleam_stdlib/src/gleam@int.erl
Normal 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).
|
||||
80
build/packages/gleam_stdlib/src/gleam@io.erl
Normal file
80
build/packages/gleam_stdlib/src/gleam@io.erl
Normal 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).
|
||||
2873
build/packages/gleam_stdlib/src/gleam@list.erl
Normal file
2873
build/packages/gleam_stdlib/src/gleam@list.erl
Normal file
File diff suppressed because it is too large
Load diff
413
build/packages/gleam_stdlib/src/gleam@option.erl
Normal file
413
build/packages/gleam_stdlib/src/gleam@option.erl
Normal 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, []).
|
||||
200
build/packages/gleam_stdlib/src/gleam@order.erl
Normal file
200
build/packages/gleam_stdlib/src/gleam@order.erl
Normal 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.
|
||||
110
build/packages/gleam_stdlib/src/gleam@pair.erl
Normal file
110
build/packages/gleam_stdlib/src/gleam@pair.erl
Normal 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}.
|
||||
550
build/packages/gleam_stdlib/src/gleam@result.erl
Normal file
550
build/packages/gleam_stdlib/src/gleam@result.erl
Normal 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.
|
||||
429
build/packages/gleam_stdlib/src/gleam@set.erl
Normal file
429
build/packages/gleam_stdlib/src/gleam@set.erl
Normal 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)).
|
||||
1012
build/packages/gleam_stdlib/src/gleam@string.erl
Normal file
1012
build/packages/gleam_stdlib/src/gleam@string.erl
Normal file
File diff suppressed because it is too large
Load diff
207
build/packages/gleam_stdlib/src/gleam@string_tree.erl
Normal file
207
build/packages/gleam_stdlib/src/gleam@string_tree.erl
Normal 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).
|
||||
1044
build/packages/gleam_stdlib/src/gleam@uri.erl
Normal file
1044
build/packages/gleam_stdlib/src/gleam@uri.erl
Normal file
File diff suppressed because it is too large
Load diff
31
build/packages/gleam_stdlib/src/gleam_stdlib.app.src
Normal file
31
build/packages/gleam_stdlib/src/gleam_stdlib.app.src
Normal 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, []}
|
||||
]}.
|
||||
534
build/packages/gleam_stdlib/src/gleam_stdlib.erl
Normal file
534
build/packages/gleam_stdlib/src/gleam_stdlib.erl
Normal 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.
|
||||
1048
build/packages/gleam_stdlib/src/gleam_stdlib.mjs
Normal file
1048
build/packages/gleam_stdlib/src/gleam_stdlib.mjs
Normal file
File diff suppressed because it is too large
Load diff
101
build/packages/gleam_time/README.md
Normal file
101
build/packages/gleam_time/README.md
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
# Time 🕰️
|
||||
|
||||
Work with time in Gleam!
|
||||
|
||||
[](https://hex.pm/packages/gleam_time)
|
||||
[](https://hexdocs.pm/gleam_time/)
|
||||
|
||||
```sh
|
||||
gleam add gleam_time
|
||||
```
|
||||
|
||||
This package is the foundation of all code that works with time in Gleam. If
|
||||
your program uses time then you should be using the types in this package, and
|
||||
you might choose to add other packages to provide additional functionality.
|
||||
|
||||
## How not to have time related bugs
|
||||
|
||||
Time is famously difficult to work with! It's a very complex area, and there's
|
||||
many approaches that seem reasonable or obvious, but then commonly result in
|
||||
bugs. This package is carefully designed to help you avoid these problems, so
|
||||
it is wise to read this documentation before continuing.
|
||||
|
||||
It is important to understand there are two main ways that time is represented:
|
||||
|
||||
- **Calendar time**: This is how humans commonly think and communicate about
|
||||
time. For example, "10pm on the 5th of January". This is easy for a human to
|
||||
read, but is it typically ambiguous and hard to work with! 10pm in
|
||||
Killorglin, Ireland is not the same point in time as 10pm in Harare,
|
||||
Zimbabwe. The exact meaning of calendar time depends on daylight savings
|
||||
time, leap years, leap seconds, and continuously changing national and
|
||||
political declarations. To make calendar time unambiguous you will need to
|
||||
know what time zone it is for, and to have an up-to-date time zone database.
|
||||
|
||||
- **Epoch time**: Epoch time is defined as an exact amount of since some fixed
|
||||
point in time. It is always unambiguous as is not impacted by geopolitics,
|
||||
time zones, etc. It is efficient for computers to work with, and it is less
|
||||
likely to result in buggy code.
|
||||
|
||||
In this package epoch time is provided by the `gleam/time/timestamp` module,
|
||||
and calendar time is provided by the `gleam/time/calendar` module.
|
||||
|
||||
Time zone information has to be loaded from elsewhere, but which approch is
|
||||
best will depend on your application. User interfaces may want to read current
|
||||
time zone information from the user's web browser or operating system. Server
|
||||
side applications may want to embed or downloads a full copy of the time zone
|
||||
database and then ask clients which time zone they want to use.
|
||||
|
||||
For an entertaining overview of some of the problems of calendar time view this
|
||||
video: ["The Problem with Time & Timezones" by Computerphile](https://www.youtube.com/watch?v=-5wpm-gesOY).
|
||||
|
||||
### Which time representation should you use?
|
||||
|
||||
> **tldr**: Use `gleam/time/timestamp`.
|
||||
|
||||
The longer, more detailed answer:
|
||||
|
||||
- Default to `gleam/time/timestamp`, which is epoch time. It is
|
||||
unambiguous, efficient, and significantly less likely to result in logic
|
||||
bugs.
|
||||
|
||||
- When writing time to a database or other data storage use epoch time,
|
||||
using whatever epoch format it supports. For example, PostgreSQL
|
||||
`timestamp` and `timestampz` are both epoch time, and `timestamp` is
|
||||
preferred as it is more straightforward to use as your application is
|
||||
also using epoch time.
|
||||
|
||||
- When communicating with other computer systems continue to use epoch
|
||||
time. For example, when sending times to another program you could
|
||||
encode time as UNIX timestamps (seconds since 00:00:00 UTC on 1 January
|
||||
1970).
|
||||
|
||||
- When communicating with humans use epoch time internally, and convert
|
||||
to-and-from calendar time at the last moment, when iteracting with the
|
||||
human user. It may also help the users to also show the time as a fuzzy
|
||||
duration from the present time, such as "about 4 days ago".
|
||||
|
||||
- When representing "fuzzy" human time concepts that don't exact periods
|
||||
in time, such as "one month" (varies depending on which month, which
|
||||
year, and in which time zone) and "Christmas Day" (varies depending on
|
||||
which year and time zone) then use calendar time.
|
||||
|
||||
Any time you do use calendar time you should be extra careful! It is very
|
||||
easy to make mistake with. Avoid it where possible.
|
||||
|
||||
## Special thanks
|
||||
|
||||
This package was created with great help from several kind contributors. In
|
||||
alphabetical order:
|
||||
|
||||
- [Hayleigh Thompson](https://github.com/hayleigh-dot-dev)
|
||||
- [John Strunk](https://github.com/jrstrunk)
|
||||
- [Ryan Moore](https://github.com/mooreryan)
|
||||
- [Shayan Javani](https://github.com/massivefermion)
|
||||
|
||||
These non-Gleam projects where highly influential on the design of this
|
||||
package:
|
||||
|
||||
- Elm's `elm/time` package.
|
||||
- Go's `time` module.
|
||||
- Rust's `std::time` module.
|
||||
- Elixir's standard library time modules and `timex` package.
|
||||
19
build/packages/gleam_time/gleam.toml
Normal file
19
build/packages/gleam_time/gleam.toml
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
name = "gleam_time"
|
||||
version = "1.6.0"
|
||||
description = "Work with time in Gleam!"
|
||||
gleam = ">= 1.11.0"
|
||||
licences = ["Apache-2.0"]
|
||||
repository = { type = "github", user = "gleam-lang", repo = "time" }
|
||||
links = [
|
||||
{ title = "Sponsor", href = "https://github.com/sponsors/lpil" }
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
gleam_stdlib = ">= 0.44.0 and < 2.0.0"
|
||||
|
||||
[dev-dependencies]
|
||||
gleeunit = ">= 1.0.0 and < 2.0.0"
|
||||
qcheck = ">= 1.0.0 and < 2.0.0"
|
||||
simplifile = ">= 2.2.0 and < 3.0.0"
|
||||
gleam_regexp = ">= 1.0.0 and < 2.0.0"
|
||||
prng = ">= 4.0.1 and < 5.0.0"
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
-record(date, {
|
||||
year :: integer(),
|
||||
month :: gleam@time@calendar:month(),
|
||||
day :: integer()
|
||||
}).
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
-record(time_of_day, {
|
||||
hours :: integer(),
|
||||
minutes :: integer(),
|
||||
seconds :: integer(),
|
||||
nanoseconds :: integer()
|
||||
}).
|
||||
|
|
@ -0,0 +1 @@
|
|||
-record(duration, {seconds :: integer(), nanoseconds :: integer()}).
|
||||
|
|
@ -0,0 +1 @@
|
|||
-record(timestamp, {seconds :: integer(), nanoseconds :: integer()}).
|
||||
346
build/packages/gleam_time/src/gleam/time/calendar.gleam
Normal file
346
build/packages/gleam_time/src/gleam/time/calendar.gleam
Normal file
|
|
@ -0,0 +1,346 @@
|
|||
//// This module is for working with the Gregorian calendar, established by
|
||||
//// Pope Gregory XIII in 1582!
|
||||
////
|
||||
//// ## When should you use this module?
|
||||
////
|
||||
//// > **tldr:** You probably want to use [`gleam/time/timestamp`](./timestamp.html)
|
||||
//// > instead!
|
||||
////
|
||||
//// Calendar time is difficult to work with programmatically, it is the source
|
||||
//// of most time-related bugs in software. Compared to _epoch time_, which the
|
||||
//// `gleam/time/timestamp` module uses, there are many disadvantages to
|
||||
//// calendar time:
|
||||
////
|
||||
//// - They are ambiguous if you don't know what time-zone is being used.
|
||||
////
|
||||
//// - A time-zone database is required to understand calendar time even when
|
||||
//// you have the time zone. These are large and your program has to
|
||||
//// continously be updated as new versions of the database are published.
|
||||
////
|
||||
//// - The type permits invalid states. e.g. `days` could be set to the number
|
||||
//// 32, but this should not be possible!
|
||||
////
|
||||
//// - There is not a single unique canonical value for each point in time,
|
||||
//// thanks to time zones. Two different `Date` + `TimeOfDay` value pairs
|
||||
//// could represent the same point in time. This means that you can't check
|
||||
//// for time equality with `==` when using calendar types.
|
||||
////
|
||||
//// - They are computationally complex, using a more memory to represent and
|
||||
//// requiring a lot more CPU time to manipulate.
|
||||
////
|
||||
//// There are also advantages to calendar time:
|
||||
////
|
||||
//// - Calendar time is how human's talk about time, so if you want to show a
|
||||
//// time or take a time from a human user then calendar time will make it
|
||||
//// easier for them.
|
||||
////
|
||||
//// - They can represent more abstract time periods such as "New Year's Day".
|
||||
//// This may seem like an exact window of time at first, but really the
|
||||
//// definition of "New Year's Day" is more fuzzy than that. When it starts
|
||||
//// and ends will depend where in the world you are, so if you want to refer
|
||||
//// to a day as a global concept instead of a fixed window of time for that
|
||||
//// day in a specific location, then calendar time can represent that.
|
||||
////
|
||||
//// So when should you use calendar time? These are our recommendations:
|
||||
////
|
||||
//// - Default to `gleam/time/timestamp`, which is epoch time. It is
|
||||
//// unambiguous, efficient, and significantly less likely to result in logic
|
||||
//// bugs.
|
||||
////
|
||||
//// - When writing time to a database or other data storage use epoch time,
|
||||
//// using whatever epoch format it supports. For example, PostgreSQL
|
||||
//// `timestamp` and `timestampz` are both epoch time, and `timestamp` is
|
||||
//// preferred as it is more straightforward to use as your application is
|
||||
//// also using epoch time.
|
||||
////
|
||||
//// - When communicating with other computer systems continue to use epoch
|
||||
//// time. For example, when sending times to another program you could
|
||||
//// encode time as UNIX timestamps (seconds since 00:00:00 UTC on 1 January
|
||||
//// 1970).
|
||||
////
|
||||
//// - When communicating with humans use epoch time internally, and convert
|
||||
//// to-and-from calendar time at the last moment, when iteracting with the
|
||||
//// human user. It may also help the users to also show the time as a fuzzy
|
||||
//// duration from the present time, such as "about 4 days ago".
|
||||
////
|
||||
//// - When representing "fuzzy" human time concepts that don't exact periods
|
||||
//// in time, such as "one month" (varies depending on which month, which
|
||||
//// year, and in which time zone) and "Christmas Day" (varies depending on
|
||||
//// which year and time zone) then use calendar time.
|
||||
////
|
||||
//// Any time you do use calendar time you should be extra careful! It is very
|
||||
//// easy to make mistake with. Avoid it where possible.
|
||||
////
|
||||
//// ## Time zone offsets
|
||||
////
|
||||
//// This package includes the `utc_offset` value and the `local_offset`
|
||||
//// function, which are the offset for the UTC time zone and get the time
|
||||
//// offset the computer running the program is configured to respectively.
|
||||
////
|
||||
//// If you need to use other offsets in your program then you will need to get
|
||||
//// them from somewhere else, such as from a package which loads the
|
||||
//// [IANA Time Zone Database](https://www.iana.org/time-zones), or from the
|
||||
//// website visitor's web browser, which your frontend can send for you.
|
||||
////
|
||||
//// ## Use in APIs
|
||||
////
|
||||
//// If you are making an API such as a HTTP JSON API you are encouraged to use
|
||||
//// Unix timestamps instead of calendar times.
|
||||
|
||||
import gleam/int
|
||||
import gleam/order.{type Order}
|
||||
import gleam/time/duration
|
||||
|
||||
/// The Gregorian calendar date. Ambiguous without a time zone.
|
||||
///
|
||||
/// Prefer to represent your time using the `Timestamp` type, and convert it
|
||||
/// only to calendar types when you need to display them. See the documentation
|
||||
/// for this module for more information.
|
||||
///
|
||||
pub type Date {
|
||||
Date(year: Int, month: Month, day: Int)
|
||||
}
|
||||
|
||||
/// The time of day. Ambiguous without a date and time zone.
|
||||
///
|
||||
pub type TimeOfDay {
|
||||
TimeOfDay(hours: Int, minutes: Int, seconds: Int, nanoseconds: Int)
|
||||
}
|
||||
|
||||
/// The 12 months of the year.
|
||||
pub type Month {
|
||||
January
|
||||
February
|
||||
March
|
||||
April
|
||||
May
|
||||
June
|
||||
July
|
||||
August
|
||||
September
|
||||
October
|
||||
November
|
||||
December
|
||||
}
|
||||
|
||||
/// The offset for the [Coordinated Universal Time (UTC)](https://en.wikipedia.org/wiki/Coordinated_Universal_Time)
|
||||
/// time zone.
|
||||
///
|
||||
/// The utc zone has no time adjustments, it is always zero. It never observes
|
||||
/// daylight-saving time and it never shifts around based on political
|
||||
/// restructuring.
|
||||
///
|
||||
pub const utc_offset = duration.empty
|
||||
|
||||
/// Get the offset for the computer's currently configured time zone.
|
||||
///
|
||||
/// Note this may not be the time zone that is correct to use for your user.
|
||||
/// For example, if you are making a web application that runs on a server you
|
||||
/// want _their_ computer's time zone, not yours.
|
||||
///
|
||||
/// This is the _current local_ offset, not the current local time zone. This
|
||||
/// means that while it will result in the expected outcome for the current
|
||||
/// time, it may result in unexpected output if used with other timestamps. For
|
||||
/// example: a timestamp that would locally be during daylight savings time if
|
||||
/// is it not currently daylight savings time when this function is called.
|
||||
///
|
||||
pub fn local_offset() -> duration.Duration {
|
||||
duration.seconds(local_time_offset_seconds())
|
||||
}
|
||||
|
||||
@external(erlang, "gleam_time_ffi", "local_time_offset_seconds")
|
||||
@external(javascript, "../../gleam_time_ffi.mjs", "local_time_offset_seconds")
|
||||
fn local_time_offset_seconds() -> Int
|
||||
|
||||
/// Returns the English name for a month.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```gleam
|
||||
/// month_to_string(April)
|
||||
/// // -> "April"
|
||||
/// ```
|
||||
pub fn month_to_string(month: Month) -> String {
|
||||
case month {
|
||||
January -> "January"
|
||||
February -> "February"
|
||||
March -> "March"
|
||||
April -> "April"
|
||||
May -> "May"
|
||||
June -> "June"
|
||||
July -> "July"
|
||||
August -> "August"
|
||||
September -> "September"
|
||||
October -> "October"
|
||||
November -> "November"
|
||||
December -> "December"
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the number for the month, where January is 1 and December is 12.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```gleam
|
||||
/// month_to_int(January)
|
||||
/// // -> 1
|
||||
/// ```
|
||||
pub fn month_to_int(month: Month) -> Int {
|
||||
case month {
|
||||
January -> 1
|
||||
February -> 2
|
||||
March -> 3
|
||||
April -> 4
|
||||
May -> 5
|
||||
June -> 6
|
||||
July -> 7
|
||||
August -> 8
|
||||
September -> 9
|
||||
October -> 10
|
||||
November -> 11
|
||||
December -> 12
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the month for a given number, where January is 1 and December is 12.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```gleam
|
||||
/// month_from_int(1)
|
||||
/// // -> Ok(January)
|
||||
/// ```
|
||||
pub fn month_from_int(month: Int) -> Result(Month, Nil) {
|
||||
case month {
|
||||
1 -> Ok(January)
|
||||
2 -> Ok(February)
|
||||
3 -> Ok(March)
|
||||
4 -> Ok(April)
|
||||
5 -> Ok(May)
|
||||
6 -> Ok(June)
|
||||
7 -> Ok(July)
|
||||
8 -> Ok(August)
|
||||
9 -> Ok(September)
|
||||
10 -> Ok(October)
|
||||
11 -> Ok(November)
|
||||
12 -> Ok(December)
|
||||
_ -> Error(Nil)
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if a given date is valid.
|
||||
///
|
||||
/// This function properly accounts for leap years when validating February days.
|
||||
/// A leap year occurs every 4 years, except for years divisible by 100,
|
||||
/// unless they are also divisible by 400.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```gleam
|
||||
/// is_valid_date(Date(2023, April, 15))
|
||||
/// // -> True
|
||||
/// ```
|
||||
///
|
||||
/// ```gleam
|
||||
/// is_valid_date(Date(2023, April, 31))
|
||||
/// // -> False
|
||||
/// ```
|
||||
///
|
||||
/// ```gleam
|
||||
/// is_valid_date(Date(2024, February, 29))
|
||||
/// // -> True (2024 is a leap year)
|
||||
/// ```
|
||||
///
|
||||
pub fn is_valid_date(date: Date) -> Bool {
|
||||
let Date(year:, month:, day:) = date
|
||||
case day < 1 {
|
||||
True -> False
|
||||
False ->
|
||||
case month {
|
||||
January | March | May | July | August | October | December -> day <= 31
|
||||
April | June | September | November -> day <= 30
|
||||
February -> {
|
||||
let max_february_days = case is_leap_year(year) {
|
||||
True -> 29
|
||||
False -> 28
|
||||
}
|
||||
day <= max_february_days
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Determines if a given year is a leap year.
|
||||
///
|
||||
/// A leap year occurs every 4 years, except for years divisible by 100,
|
||||
/// unless they are also divisible by 400.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```gleam
|
||||
/// is_leap_year(2024)
|
||||
/// // -> True
|
||||
/// ```
|
||||
///
|
||||
/// ```gleam
|
||||
/// is_leap_year(2023)
|
||||
/// // -> False
|
||||
/// ```
|
||||
///
|
||||
pub fn is_leap_year(year: Int) -> Bool {
|
||||
case year % 400 == 0 {
|
||||
True -> True
|
||||
False ->
|
||||
case year % 100 == 0 {
|
||||
True -> False
|
||||
False -> year % 4 == 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if a time of day is valid.
|
||||
///
|
||||
/// Validates that hours are 0-23, minutes are 0-59, seconds are 0-59,
|
||||
/// and nanoseconds are 0-999,999,999.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```gleam
|
||||
/// is_valid_time_of_day(TimeOfDay(12, 30, 45, 123456789))
|
||||
/// // -> True
|
||||
/// ```
|
||||
///
|
||||
pub fn is_valid_time_of_day(time: TimeOfDay) -> Bool {
|
||||
let TimeOfDay(hours:, minutes:, seconds:, nanoseconds:) = time
|
||||
|
||||
hours >= 0
|
||||
&& hours <= 23
|
||||
&& minutes >= 0
|
||||
&& minutes <= 59
|
||||
&& seconds >= 0
|
||||
&& seconds <= 59
|
||||
&& nanoseconds >= 0
|
||||
&& nanoseconds <= 999_999_999
|
||||
}
|
||||
|
||||
/// Naively compares two dates without any time zone information, returning an
|
||||
/// order.
|
||||
///
|
||||
/// ## Correctness
|
||||
///
|
||||
/// This function compares dates without any time zone information, only using
|
||||
/// the rules for the gregorian calendar. This is typically sufficient, but be
|
||||
/// aware that in reality some time zones will change their calendar date
|
||||
/// occasionally. This can result in days being skipped, out of order, or
|
||||
/// happening multiple times.
|
||||
///
|
||||
/// If you need real-world correct time ordering then use the
|
||||
/// `gleam/time/timestamp` module instead.
|
||||
///
|
||||
pub fn naive_date_compare(one: Date, other: Date) -> Order {
|
||||
int.compare(one.year, other.year)
|
||||
|> order.lazy_break_tie(fn() {
|
||||
int.compare(month_to_int(one.month), month_to_int(other.month))
|
||||
})
|
||||
|> order.lazy_break_tie(fn() { int.compare(one.day, other.day) })
|
||||
}
|
||||
297
build/packages/gleam_time/src/gleam/time/duration.gleam
Normal file
297
build/packages/gleam_time/src/gleam/time/duration.gleam
Normal file
|
|
@ -0,0 +1,297 @@
|
|||
import gleam/bool
|
||||
import gleam/int
|
||||
import gleam/order
|
||||
import gleam/string
|
||||
|
||||
/// An amount of time, with up to nanosecond precision.
|
||||
///
|
||||
/// This type does not represent calendar periods such as "1 month" or "2
|
||||
/// days". Those periods will be different lengths of time depending on which
|
||||
/// month or day they apply to. For example, January is longer than February.
|
||||
/// A different type should be used for calendar periods.
|
||||
///
|
||||
pub opaque type Duration {
|
||||
// When compiling to JavaScript ints have limited precision and size. This
|
||||
// means that if we were to store the the timestamp in a single int the
|
||||
// duration would not be able to represent very large or small durations.
|
||||
// Durations are instead represented as a number of seconds and a number of
|
||||
// nanoseconds.
|
||||
//
|
||||
// If you have manually adjusted the seconds and nanoseconds values the
|
||||
// `normalise` function can be used to ensure the time is represented the
|
||||
// intended way, with `nanoseconds` being positive and less than 1 second.
|
||||
//
|
||||
// The duration is the sum of the seconds and the nanoseconds.
|
||||
Duration(seconds: Int, nanoseconds: Int)
|
||||
}
|
||||
|
||||
/// A division of time.
|
||||
///
|
||||
/// Note that not all months and years are the same length, so a reasonable
|
||||
/// average length is used by this module.
|
||||
///
|
||||
pub type Unit {
|
||||
Nanosecond
|
||||
/// 1000 nanoseconds.
|
||||
Microsecond
|
||||
/// 1000 microseconds.
|
||||
Millisecond
|
||||
/// 1000 milliseconds.
|
||||
Second
|
||||
/// 60 seconds.
|
||||
Minute
|
||||
/// 60 minutes.
|
||||
Hour
|
||||
/// 24 hours.
|
||||
Day
|
||||
/// 7 days.
|
||||
Week
|
||||
/// About 30.4375 days. Real calendar months vary in length.
|
||||
Month
|
||||
/// About 365.25 days. Real calendar years vary in length.
|
||||
Year
|
||||
}
|
||||
|
||||
/// Convert a duration to a number of the largest number of a unit, serving as
|
||||
/// a rough description of the duration that a human can understand.
|
||||
///
|
||||
/// The size used for each unit are described in the documentation for the
|
||||
/// `Unit` type.
|
||||
///
|
||||
/// ```gleam
|
||||
/// seconds(125)
|
||||
/// |> approximate
|
||||
/// // -> #(2, Minute)
|
||||
/// ```
|
||||
///
|
||||
/// This function rounds _towards zero_. This means that if a duration is just
|
||||
/// short of 2 days then it will approximate to 1 day.
|
||||
///
|
||||
/// ```gleam
|
||||
/// hours(47)
|
||||
/// |> approximate
|
||||
/// // -> #(1, Day)
|
||||
/// ```
|
||||
///
|
||||
pub fn approximate(duration: Duration) -> #(Int, Unit) {
|
||||
let Duration(seconds: s, nanoseconds: ns) = duration
|
||||
let minute = 60
|
||||
let hour = minute * 60
|
||||
let day = hour * 24
|
||||
let week = day * 7
|
||||
let year = day * 365 + hour * 6
|
||||
let month = year / 12
|
||||
let microsecond = 1000
|
||||
let millisecond = microsecond * 1000
|
||||
case Nil {
|
||||
_ if s < 0 -> {
|
||||
let #(amount, unit) = Duration(-s, -ns) |> normalise |> approximate
|
||||
#(-amount, unit)
|
||||
}
|
||||
_ if s >= year -> #(s / year, Year)
|
||||
_ if s >= month -> #(s / month, Month)
|
||||
_ if s >= week -> #(s / week, Week)
|
||||
_ if s >= day -> #(s / day, Day)
|
||||
_ if s >= hour -> #(s / hour, Hour)
|
||||
_ if s >= minute -> #(s / minute, Minute)
|
||||
_ if s > 0 -> #(s, Second)
|
||||
_ if ns >= millisecond -> #(ns / millisecond, Millisecond)
|
||||
_ if ns >= microsecond -> #(ns / microsecond, Microsecond)
|
||||
_ -> #(ns, Nanosecond)
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensure the duration is represented with `nanoseconds` being positive and
|
||||
/// less than 1 second.
|
||||
///
|
||||
/// This function does not change the amount of time that the duratoin refers
|
||||
/// to, it only adjusts the values used to represent the time.
|
||||
///
|
||||
fn normalise(duration: Duration) -> Duration {
|
||||
let multiplier = 1_000_000_000
|
||||
let nanoseconds = duration.nanoseconds % multiplier
|
||||
let overflow = duration.nanoseconds - nanoseconds
|
||||
let seconds = duration.seconds + overflow / multiplier
|
||||
case nanoseconds >= 0 {
|
||||
True -> Duration(seconds, nanoseconds)
|
||||
False -> Duration(seconds - 1, multiplier + nanoseconds)
|
||||
}
|
||||
}
|
||||
|
||||
/// Compare one duration to another, indicating whether the first spans a
|
||||
/// larger amount of time (and so is greater) or smaller amount of time (and so
|
||||
/// is lesser) than the second.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```gleam
|
||||
/// compare(seconds(1), seconds(2))
|
||||
/// // -> order.Lt
|
||||
/// ```
|
||||
///
|
||||
/// Whether a duration is negative or positive doesn't matter for comparing
|
||||
/// them, only the amount of time spanned matters.
|
||||
///
|
||||
/// ```gleam
|
||||
/// compare(seconds(-2), seconds(1))
|
||||
/// // -> order.Gt
|
||||
/// ```
|
||||
///
|
||||
pub fn compare(left: Duration, right: Duration) -> order.Order {
|
||||
let parts = fn(x: Duration) {
|
||||
case x.seconds >= 0 {
|
||||
True -> #(x.seconds, x.nanoseconds)
|
||||
False -> #(x.seconds * -1 - 1, 1_000_000_000 - x.nanoseconds)
|
||||
}
|
||||
}
|
||||
let #(ls, lns) = parts(left)
|
||||
let #(rs, rns) = parts(right)
|
||||
int.compare(ls, rs)
|
||||
|> order.break_tie(int.compare(lns, rns))
|
||||
}
|
||||
|
||||
/// Calculate the difference between two durations.
|
||||
///
|
||||
/// This is effectively substracting the first duration from the second.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```gleam
|
||||
/// difference(seconds(1), seconds(5))
|
||||
/// // -> seconds(4)
|
||||
/// ```
|
||||
///
|
||||
pub fn difference(left: Duration, right: Duration) -> Duration {
|
||||
Duration(right.seconds - left.seconds, right.nanoseconds - left.nanoseconds)
|
||||
|> normalise
|
||||
}
|
||||
|
||||
/// Add two durations together.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```gleam
|
||||
/// add(seconds(1), seconds(5))
|
||||
/// // -> seconds(6)
|
||||
/// ```
|
||||
///
|
||||
pub fn add(left: Duration, right: Duration) -> Duration {
|
||||
Duration(left.seconds + right.seconds, left.nanoseconds + right.nanoseconds)
|
||||
|> normalise
|
||||
}
|
||||
|
||||
/// Convert the duration to an [ISO8601][1] formatted duration string.
|
||||
///
|
||||
/// The ISO8601 duration format is ambiguous without context due to months and
|
||||
/// years having different lengths, and because of leap seconds. This function
|
||||
/// encodes the duration as days, hours, and seconds without any leap seconds.
|
||||
/// Be sure to take this into account when using the duration strings.
|
||||
///
|
||||
/// [1]: https://en.wikipedia.org/wiki/ISO_8601#Durations
|
||||
///
|
||||
pub fn to_iso8601_string(duration: Duration) -> String {
|
||||
use <- bool.guard(duration == empty, "PT0S")
|
||||
let split = fn(total, limit) {
|
||||
let amount = total % limit
|
||||
let remainder = { total - amount } / limit
|
||||
#(amount, remainder)
|
||||
}
|
||||
let #(seconds, rest) = split(duration.seconds, 60)
|
||||
let #(minutes, rest) = split(rest, 60)
|
||||
let #(hours, rest) = split(rest, 24)
|
||||
let days = rest
|
||||
let add = fn(out, value, unit) {
|
||||
case value {
|
||||
0 -> out
|
||||
_ -> out <> int.to_string(value) <> unit
|
||||
}
|
||||
}
|
||||
let output =
|
||||
"P"
|
||||
|> add(days, "D")
|
||||
|> string.append("T")
|
||||
|> add(hours, "H")
|
||||
|> add(minutes, "M")
|
||||
case seconds, duration.nanoseconds {
|
||||
0, 0 -> output
|
||||
_, 0 -> output <> int.to_string(seconds) <> "S"
|
||||
_, _ -> {
|
||||
let f = nanosecond_digits(duration.nanoseconds, 0, "")
|
||||
output <> int.to_string(seconds) <> "." <> f <> "S"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn nanosecond_digits(n: Int, position: Int, acc: String) -> String {
|
||||
case position {
|
||||
9 -> acc
|
||||
_ if acc == "" && n % 10 == 0 -> {
|
||||
nanosecond_digits(n / 10, position + 1, acc)
|
||||
}
|
||||
_ -> {
|
||||
let acc = int.to_string(n % 10) <> acc
|
||||
nanosecond_digits(n / 10, position + 1, acc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a duration of a number of seconds.
|
||||
pub fn seconds(amount: Int) -> Duration {
|
||||
Duration(amount, 0)
|
||||
}
|
||||
|
||||
/// Create a duration of a number of minutes.
|
||||
pub fn minutes(amount: Int) -> Duration {
|
||||
seconds(amount * 60)
|
||||
}
|
||||
|
||||
/// Create a duration of a number of hours.
|
||||
pub fn hours(amount: Int) -> Duration {
|
||||
seconds(amount * 60 * 60)
|
||||
}
|
||||
|
||||
/// Create a duration of a number of milliseconds.
|
||||
pub fn milliseconds(amount: Int) -> Duration {
|
||||
let remainder = amount % 1000
|
||||
let overflow = amount - remainder
|
||||
let nanoseconds = remainder * 1_000_000
|
||||
let seconds = overflow / 1000
|
||||
Duration(seconds, nanoseconds)
|
||||
|> normalise
|
||||
}
|
||||
|
||||
/// Create a duration of a number of nanoseconds.
|
||||
///
|
||||
/// # JavaScript int limitations
|
||||
///
|
||||
/// Remember that JavaScript can only perfectly represent ints between positive
|
||||
/// and negative 9,007,199,254,740,991! If you use a single call to this
|
||||
/// function to create durations larger than that number of nanoseconds then
|
||||
/// you will likely not get exactly the value you expect. Use `seconds` and
|
||||
/// `milliseconds` as much as possible for large durations.
|
||||
///
|
||||
pub fn nanoseconds(amount: Int) -> Duration {
|
||||
Duration(0, amount)
|
||||
|> normalise
|
||||
}
|
||||
|
||||
/// Convert the duration to a number of seconds.
|
||||
///
|
||||
/// There may be some small loss of precision due to `Duration` being
|
||||
/// nanosecond accurate and `Float` not being able to represent this.
|
||||
///
|
||||
pub fn to_seconds(duration: Duration) -> Float {
|
||||
let seconds = int.to_float(duration.seconds)
|
||||
let nanoseconds = int.to_float(duration.nanoseconds)
|
||||
seconds +. { nanoseconds /. 1_000_000_000.0 }
|
||||
}
|
||||
|
||||
/// Convert the duration to a number of seconds and nanoseconds. There is no
|
||||
/// loss of precision with this conversion on any target.
|
||||
///
|
||||
pub fn to_seconds_and_nanoseconds(duration: Duration) -> #(Int, Int) {
|
||||
#(duration.seconds, duration.nanoseconds)
|
||||
}
|
||||
|
||||
@internal
|
||||
pub const empty = Duration(0, 0)
|
||||
899
build/packages/gleam_time/src/gleam/time/timestamp.gleam
Normal file
899
build/packages/gleam_time/src/gleam/time/timestamp.gleam
Normal file
|
|
@ -0,0 +1,899 @@
|
|||
//// Welcome to the timestamp module! This module and its `Timestamp` type are
|
||||
//// what you will be using most commonly when working with time in Gleam.
|
||||
////
|
||||
//// A timestamp represents a moment in time, represented as an amount of time
|
||||
//// since the calendar time 00:00:00 UTC on 1 January 1970, also known as the
|
||||
//// _Unix epoch_.
|
||||
////
|
||||
//// # Wall clock time and monotonicity
|
||||
////
|
||||
//// Time is very complicated, especially on computers! While they generally do
|
||||
//// a good job of keeping track of what the time is, computers can get
|
||||
//// out-of-sync and start to report a time that is too late or too early. Most
|
||||
//// computers use "network time protocol" to tell each other what they think
|
||||
//// the time is, and computers that realise they are running too fast or too
|
||||
//// slow will adjust their clock to correct it. When this happens it can seem
|
||||
//// to your program that the current time has changed, and it may have even
|
||||
//// jumped backwards in time!
|
||||
////
|
||||
//// This measure of time is called _wall clock time_, and it is what people
|
||||
//// commonly think of when they think of time. It is important to be aware that
|
||||
//// it can go backwards, and your program must not rely on it only ever going
|
||||
//// forwards at a steady rate. For example, for tracking what order events happen
|
||||
//// in.
|
||||
////
|
||||
//// This module uses wall clock time. If your program needs time values to always
|
||||
//// increase you will need a _monotonic_ time instead. It's uncommon that you
|
||||
//// would need monotonic time, one example might be if you're making a
|
||||
//// benchmarking framework.
|
||||
////
|
||||
//// The exact way that time works will depend on what runtime you use. The
|
||||
//// Erlang documentation on time has a lot of detail about time generally as well
|
||||
//// as how it works on the BEAM, it is worth reading.
|
||||
//// <https://www.erlang.org/doc/apps/erts/time_correction>.
|
||||
////
|
||||
//// # Converting to local calendar time
|
||||
////
|
||||
//// Timestamps don't take into account time zones, so a moment in time will
|
||||
//// have the same timestamp value regardless of where you are in the world. To
|
||||
//// convert them to local time you will need to know the offset for the time
|
||||
//// zone you wish to use, likely from a time zone database. See the
|
||||
//// `gleam/time/calendar` module for more information.
|
||||
////
|
||||
|
||||
import gleam/bit_array
|
||||
import gleam/float
|
||||
import gleam/int
|
||||
import gleam/list
|
||||
import gleam/order
|
||||
import gleam/result
|
||||
import gleam/string
|
||||
import gleam/time/calendar
|
||||
import gleam/time/duration.{type Duration}
|
||||
|
||||
const seconds_per_day: Int = 86_400
|
||||
|
||||
const seconds_per_hour: Int = 3600
|
||||
|
||||
const seconds_per_minute: Int = 60
|
||||
|
||||
const nanoseconds_per_second: Int = 1_000_000_000
|
||||
|
||||
/// The `:` character as a byte
|
||||
const byte_colon: Int = 0x3A
|
||||
|
||||
/// The `-` character as a byte
|
||||
const byte_minus: Int = 0x2D
|
||||
|
||||
/// The `0` character as a byte
|
||||
const byte_zero: Int = 0x30
|
||||
|
||||
/// The `9` character as a byte
|
||||
const byte_nine: Int = 0x39
|
||||
|
||||
/// The `t` character as a byte
|
||||
const byte_t_lowercase: Int = 0x74
|
||||
|
||||
/// The `T` character as a byte
|
||||
const byte_t_uppercase: Int = 0x54
|
||||
|
||||
/// The `T` character as a byte
|
||||
const byte_space: Int = 0x20
|
||||
|
||||
/// The Julian seconds of the UNIX epoch (Julian day is 2_440_588)
|
||||
const julian_seconds_unix_epoch: Int = 210_866_803_200
|
||||
|
||||
/// The main time type, which you should favour over other types such as
|
||||
/// calendar time types. It is efficient, unambiguous, and it is not possible
|
||||
/// to construct an invalid timestamp.
|
||||
///
|
||||
/// The most common situation in which you may need a different time data
|
||||
/// structure is when you need to display time to human for them to read. When
|
||||
/// you need to do this convert the timestamp to calendar time when presenting
|
||||
/// it, but internally always keep the time as a timestamp.
|
||||
///
|
||||
pub opaque type Timestamp {
|
||||
// When compiling to JavaScript ints have limited precision and size. This
|
||||
// means that if we were to store the the timestamp in a single int the
|
||||
// timestamp would not be able to represent times far in the future or in the
|
||||
// past, or distinguish between two times that are close together. Timestamps
|
||||
// are instead represented as a number of seconds and a number of nanoseconds.
|
||||
//
|
||||
// If you have manually adjusted the seconds and nanoseconds values the
|
||||
// `normalise` function can be used to ensure the time is represented the
|
||||
// intended way, with `nanoseconds` being positive and less than 1 second.
|
||||
//
|
||||
// The timestamp is the sum of the seconds and the nanoseconds.
|
||||
Timestamp(seconds: Int, nanoseconds: Int)
|
||||
}
|
||||
|
||||
/// The epoch of Unix time, which is 00:00:00 UTC on 1 January 1970.
|
||||
pub const unix_epoch = Timestamp(0, 0)
|
||||
|
||||
/// Ensure the time is represented with `nanoseconds` being positive and less
|
||||
/// than 1 second.
|
||||
///
|
||||
/// This function does not change the time that the timestamp refers to, it
|
||||
/// only adjusts the values used to represent the time.
|
||||
///
|
||||
fn normalise(timestamp: Timestamp) -> Timestamp {
|
||||
let multiplier = 1_000_000_000
|
||||
let nanoseconds = timestamp.nanoseconds % multiplier
|
||||
let overflow = timestamp.nanoseconds - nanoseconds
|
||||
let seconds = timestamp.seconds + overflow / multiplier
|
||||
case nanoseconds >= 0 {
|
||||
True -> Timestamp(seconds, nanoseconds)
|
||||
False -> Timestamp(seconds - 1, multiplier + nanoseconds)
|
||||
}
|
||||
}
|
||||
|
||||
/// Compare one timestamp to another, indicating whether the first is further
|
||||
/// into the future (greater) or further into the past (lesser) than the
|
||||
/// second.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```gleam
|
||||
/// compare(from_unix_seconds(1), from_unix_seconds(2))
|
||||
/// // -> order.Lt
|
||||
/// ```
|
||||
///
|
||||
pub fn compare(left: Timestamp, right: Timestamp) -> order.Order {
|
||||
order.break_tie(
|
||||
int.compare(left.seconds, right.seconds),
|
||||
int.compare(left.nanoseconds, right.nanoseconds),
|
||||
)
|
||||
}
|
||||
|
||||
/// Get the current system time.
|
||||
///
|
||||
/// Note this time is not unique or monotonic, it could change at any time or
|
||||
/// even go backwards! The exact behaviour will depend on the runtime used. See
|
||||
/// the module documentation for more information.
|
||||
///
|
||||
/// On Erlang this uses [`erlang:system_time/1`][1]. On JavaScript this uses
|
||||
/// [`Date.now`][2].
|
||||
///
|
||||
/// [1]: https://www.erlang.org/doc/apps/erts/erlang#system_time/1
|
||||
/// [2]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now
|
||||
///
|
||||
pub fn system_time() -> Timestamp {
|
||||
let #(seconds, nanoseconds) = get_system_time()
|
||||
normalise(Timestamp(seconds, nanoseconds))
|
||||
}
|
||||
|
||||
@external(erlang, "gleam_time_ffi", "system_time")
|
||||
@external(javascript, "../../gleam_time_ffi.mjs", "system_time")
|
||||
fn get_system_time() -> #(Int, Int)
|
||||
|
||||
/// Calculate the difference between two timestamps.
|
||||
///
|
||||
/// This is effectively substracting the first timestamp from the second.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```gleam
|
||||
/// difference(from_unix_seconds(1), from_unix_seconds(5))
|
||||
/// // -> duration.seconds(4)
|
||||
/// ```
|
||||
///
|
||||
pub fn difference(left: Timestamp, right: Timestamp) -> Duration {
|
||||
let seconds = duration.seconds(right.seconds - left.seconds)
|
||||
let nanoseconds = duration.nanoseconds(right.nanoseconds - left.nanoseconds)
|
||||
duration.add(seconds, nanoseconds)
|
||||
}
|
||||
|
||||
/// Add a duration to a timestamp.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```gleam
|
||||
/// add(from_unix_seconds(1000), duration.seconds(5))
|
||||
/// // -> from_unix_seconds(1005)
|
||||
/// ```
|
||||
///
|
||||
pub fn add(timestamp: Timestamp, duration: Duration) -> Timestamp {
|
||||
let #(seconds, nanoseconds) = duration.to_seconds_and_nanoseconds(duration)
|
||||
Timestamp(timestamp.seconds + seconds, timestamp.nanoseconds + nanoseconds)
|
||||
|> normalise
|
||||
}
|
||||
|
||||
/// Convert a timestamp to a RFC 3339 formatted time string, with an offset
|
||||
/// supplied as an additional argument.
|
||||
///
|
||||
/// The output of this function is also ISO 8601 compatible so long as the
|
||||
/// offset not negative. Offsets have at-most minute precision, so an offset
|
||||
/// with higher precision will be rounded to the nearest minute.
|
||||
///
|
||||
/// If you are making an API such as a HTTP JSON API you are encouraged to use
|
||||
/// Unix timestamps instead of this format or ISO 8601. Unix timestamps are a
|
||||
/// better choice as they don't contain offset information. Consider:
|
||||
///
|
||||
/// - UTC offsets are not time zones. This does not and cannot tell us the time
|
||||
/// zone in which the date was recorded. So what are we supposed to do with
|
||||
/// this information?
|
||||
/// - Users typically want dates formatted according to their local time zone.
|
||||
/// What if the provided UTC offset is different from the current user's time
|
||||
/// zone? What are we supposed to do with it then?
|
||||
/// - Despite it being useless (or worse, a source of bugs), the UTC offset
|
||||
/// creates a larger payload to transfer.
|
||||
///
|
||||
/// They also uses more memory than a unix timestamp. The way they are better
|
||||
/// than Unix timestamp is that it is easier for a human to read them, but
|
||||
/// this is a hinderance that tooling can remedy, and APIs are not primarily
|
||||
/// for humans.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```gleam
|
||||
/// timestamp.from_unix_seconds_and_nanoseconds(1000, 123_000_000)
|
||||
/// |> to_rfc3339(calendar.utc_offset)
|
||||
/// // -> "1970-01-01T00:16:40.123Z"
|
||||
/// ```
|
||||
///
|
||||
/// ```gleam
|
||||
/// timestamp.from_unix_seconds(1000)
|
||||
/// |> to_rfc3339(duration.seconds(3600))
|
||||
/// // -> "1970-01-01T01:16:40+01:00"
|
||||
/// ```
|
||||
///
|
||||
pub fn to_rfc3339(timestamp: Timestamp, offset: Duration) -> String {
|
||||
let offset = duration_to_minutes(offset)
|
||||
let #(years, months, days, hours, minutes, seconds) =
|
||||
to_calendar_from_offset(timestamp, offset)
|
||||
|
||||
let offset_minutes = modulo(offset, 60)
|
||||
let offset_hours = int.absolute_value(floored_div(offset, 60.0))
|
||||
|
||||
let n2 = pad_digit(_, to: 2)
|
||||
let n4 = pad_digit(_, to: 4)
|
||||
let out = ""
|
||||
let out = out <> n4(years) <> "-" <> n2(months) <> "-" <> n2(days)
|
||||
let out = out <> "T"
|
||||
let out = out <> n2(hours) <> ":" <> n2(minutes) <> ":" <> n2(seconds)
|
||||
let out = out <> show_second_fraction(timestamp.nanoseconds)
|
||||
case int.compare(offset, 0) {
|
||||
order.Eq -> out <> "Z"
|
||||
order.Gt -> out <> "+" <> n2(offset_hours) <> ":" <> n2(offset_minutes)
|
||||
order.Lt -> out <> "-" <> n2(offset_hours) <> ":" <> n2(offset_minutes)
|
||||
}
|
||||
}
|
||||
|
||||
fn pad_digit(digit: Int, to desired_length: Int) -> String {
|
||||
int.to_string(digit) |> string.pad_start(desired_length, "0")
|
||||
}
|
||||
|
||||
/// Convert a `Timestamp` to calendar time, suitable for presenting to a human
|
||||
/// to read.
|
||||
///
|
||||
/// If you want a machine to use the time value then you should not use this
|
||||
/// function and should instead keep it as a timestamp. See the documentation
|
||||
/// for the `gleam/time/calendar` module for more information.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```gleam
|
||||
/// timestamp.from_unix_seconds(0)
|
||||
/// |> timestamp.to_calendar(calendar.utc_offset)
|
||||
/// // -> #(Date(1970, January, 1), TimeOfDay(0, 0, 0, 0))
|
||||
/// ```
|
||||
///
|
||||
pub fn to_calendar(
|
||||
timestamp: Timestamp,
|
||||
offset: Duration,
|
||||
) -> #(calendar.Date, calendar.TimeOfDay) {
|
||||
let offset = duration_to_minutes(offset)
|
||||
let #(year, month, day, hours, minutes, seconds) =
|
||||
to_calendar_from_offset(timestamp, offset)
|
||||
let month = case month {
|
||||
1 -> calendar.January
|
||||
2 -> calendar.February
|
||||
3 -> calendar.March
|
||||
4 -> calendar.April
|
||||
5 -> calendar.May
|
||||
6 -> calendar.June
|
||||
7 -> calendar.July
|
||||
8 -> calendar.August
|
||||
9 -> calendar.September
|
||||
10 -> calendar.October
|
||||
11 -> calendar.November
|
||||
_ -> calendar.December
|
||||
}
|
||||
let nanoseconds = timestamp.nanoseconds
|
||||
let date = calendar.Date(year:, month:, day:)
|
||||
let time = calendar.TimeOfDay(hours:, minutes:, seconds:, nanoseconds:)
|
||||
#(date, time)
|
||||
}
|
||||
|
||||
fn duration_to_minutes(duration: duration.Duration) -> Int {
|
||||
float.round(duration.to_seconds(duration) /. 60.0)
|
||||
}
|
||||
|
||||
fn to_calendar_from_offset(
|
||||
timestamp: Timestamp,
|
||||
offset: Int,
|
||||
) -> #(Int, Int, Int, Int, Int, Int) {
|
||||
let total = timestamp.seconds + { offset * 60 }
|
||||
let seconds = modulo(total, 60)
|
||||
let total_minutes = floored_div(total, 60.0)
|
||||
let minutes = modulo(total, 60 * 60) / 60
|
||||
let hours = modulo(total, 24 * 60 * 60) / { 60 * 60 }
|
||||
let #(year, month, day) = to_civil(total_minutes)
|
||||
#(year, month, day, hours, minutes, seconds)
|
||||
}
|
||||
|
||||
/// Create a `Timestamp` from a human-readable calendar time.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```gleam
|
||||
/// timestamp.from_calendar(
|
||||
/// date: calendar.Date(2024, calendar.December, 25),
|
||||
/// time: calendar.TimeOfDay(12, 30, 50, 0),
|
||||
/// offset: calendar.utc_offset,
|
||||
/// )
|
||||
/// |> timestamp.to_rfc3339(calendar.utc_offset)
|
||||
/// // -> "2024-12-25T12:30:50Z"
|
||||
/// ```
|
||||
///
|
||||
pub fn from_calendar(
|
||||
date date: calendar.Date,
|
||||
time time: calendar.TimeOfDay,
|
||||
offset offset: Duration,
|
||||
) -> Timestamp {
|
||||
let month = case date.month {
|
||||
calendar.January -> 1
|
||||
calendar.February -> 2
|
||||
calendar.March -> 3
|
||||
calendar.April -> 4
|
||||
calendar.May -> 5
|
||||
calendar.June -> 6
|
||||
calendar.July -> 7
|
||||
calendar.August -> 8
|
||||
calendar.September -> 9
|
||||
calendar.October -> 10
|
||||
calendar.November -> 11
|
||||
calendar.December -> 12
|
||||
}
|
||||
from_date_time(
|
||||
year: date.year,
|
||||
month:,
|
||||
day: date.day,
|
||||
hours: time.hours,
|
||||
minutes: time.minutes,
|
||||
seconds: time.seconds,
|
||||
second_fraction_as_nanoseconds: time.nanoseconds,
|
||||
offset_seconds: float.round(duration.to_seconds(offset)),
|
||||
)
|
||||
}
|
||||
|
||||
fn modulo(n: Int, m: Int) -> Int {
|
||||
case int.modulo(n, m) {
|
||||
Ok(n) -> n
|
||||
Error(_) -> 0
|
||||
}
|
||||
}
|
||||
|
||||
fn floored_div(numerator: Int, denominator: Float) -> Int {
|
||||
let n = int.to_float(numerator) /. denominator
|
||||
float.round(float.floor(n))
|
||||
}
|
||||
|
||||
// Adapted from Elm's Time module
|
||||
fn to_civil(minutes: Int) -> #(Int, Int, Int) {
|
||||
let raw_day = floored_div(minutes, { 60.0 *. 24.0 }) + 719_468
|
||||
let era = case raw_day >= 0 {
|
||||
True -> raw_day / 146_097
|
||||
False -> { raw_day - 146_096 } / 146_097
|
||||
}
|
||||
let day_of_era = raw_day - era * 146_097
|
||||
let year_of_era =
|
||||
{
|
||||
day_of_era
|
||||
- { day_of_era / 1460 }
|
||||
+ { day_of_era / 36_524 }
|
||||
- { day_of_era / 146_096 }
|
||||
}
|
||||
/ 365
|
||||
let year = year_of_era + era * 400
|
||||
let day_of_year =
|
||||
day_of_era
|
||||
- { 365 * year_of_era + { year_of_era / 4 } - { year_of_era / 100 } }
|
||||
let mp = { 5 * day_of_year + 2 } / 153
|
||||
let month = case mp < 10 {
|
||||
True -> mp + 3
|
||||
False -> mp - 9
|
||||
}
|
||||
let day = day_of_year - { 153 * mp + 2 } / 5 + 1
|
||||
let year = case month <= 2 {
|
||||
True -> year + 1
|
||||
False -> year
|
||||
}
|
||||
#(year, month, day)
|
||||
}
|
||||
|
||||
/// Converts nanoseconds into a `String` representation of fractional seconds.
|
||||
///
|
||||
/// Assumes that `nanoseconds < 1_000_000_000`, which will be true for any
|
||||
/// normalised timestamp.
|
||||
///
|
||||
fn show_second_fraction(nanoseconds: Int) -> String {
|
||||
case int.compare(nanoseconds, 0) {
|
||||
// Zero fractional seconds are not shown.
|
||||
order.Lt | order.Eq -> ""
|
||||
order.Gt -> {
|
||||
let second_fraction_part = {
|
||||
nanoseconds
|
||||
|> get_zero_padded_digits
|
||||
|> remove_trailing_zeros
|
||||
|> list.map(int.to_string)
|
||||
|> string.join("")
|
||||
}
|
||||
|
||||
"." <> second_fraction_part
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Given a list of digits, return new list with any trailing zeros removed.
|
||||
///
|
||||
fn remove_trailing_zeros(digits: List(Int)) -> List(Int) {
|
||||
let reversed_digits = list.reverse(digits)
|
||||
|
||||
do_remove_trailing_zeros(reversed_digits)
|
||||
}
|
||||
|
||||
fn do_remove_trailing_zeros(reversed_digits) {
|
||||
case reversed_digits {
|
||||
[] -> []
|
||||
[digit, ..digits] if digit == 0 -> do_remove_trailing_zeros(digits)
|
||||
reversed_digits -> list.reverse(reversed_digits)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the list of digits of `number`. If the number of digits is less
|
||||
/// than 9, the result is zero-padded at the front.
|
||||
///
|
||||
fn get_zero_padded_digits(number: Int) -> List(Int) {
|
||||
do_get_zero_padded_digits(number, [], 0)
|
||||
}
|
||||
|
||||
fn do_get_zero_padded_digits(
|
||||
number: Int,
|
||||
digits: List(Int),
|
||||
count: Int,
|
||||
) -> List(Int) {
|
||||
case number {
|
||||
number if number <= 0 && count >= 9 -> digits
|
||||
number if number <= 0 ->
|
||||
// Zero-pad the digits at the front until we have at least 9 digits.
|
||||
do_get_zero_padded_digits(number, [0, ..digits], count + 1)
|
||||
number -> {
|
||||
let digit = number % 10
|
||||
let number = floored_div(number, 10.0)
|
||||
do_get_zero_padded_digits(number, [digit, ..digits], count + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses an [RFC 3339 formatted time string][spec] into a `Timestamp`.
|
||||
///
|
||||
/// [spec]: https://datatracker.ietf.org/doc/html/rfc3339#section-5.6
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```gleam
|
||||
/// let assert Ok(ts) = timestamp.parse_rfc3339("1970-01-01T00:00:01Z")
|
||||
/// timestamp.to_unix_seconds_and_nanoseconds(ts)
|
||||
/// // -> #(1, 0)
|
||||
/// ```
|
||||
///
|
||||
/// Parsing an invalid timestamp returns an error.
|
||||
///
|
||||
/// ```gleam
|
||||
/// let assert Error(Nil) = timestamp.parse_rfc3339("1995-10-31")
|
||||
/// ```
|
||||
///
|
||||
/// ## Time zones
|
||||
///
|
||||
/// It may at first seem that the RFC 3339 format includes timezone
|
||||
/// information, as it can specify an offset such as `Z` or `+3`, so why does
|
||||
/// this function not return calendar time with a time zone? There are multiple
|
||||
/// reasons:
|
||||
///
|
||||
/// - RFC 3339's timestamp format is based on calendar time, but it is
|
||||
/// unambigous, so it can be converted into epoch time when being parsed. It
|
||||
/// is always better to internally use epoch time to represent unambiguous
|
||||
/// points in time, so we perform that conversion as a convenience and to
|
||||
/// ensure that programmers with less time experience don't accidentally use
|
||||
/// a less suitable time representation.
|
||||
///
|
||||
/// - RFC 3339's contains _calendar time offset_ information, not time zone
|
||||
/// information. This is enough to convert it to an unambiguous timestamp,
|
||||
/// but it is not enough information to reliably work with calendar time.
|
||||
/// Without the time zone and the time zone database it's not possible to
|
||||
/// know what time period that offset is valid for, so it cannot be used
|
||||
/// without risk of bugs.
|
||||
///
|
||||
/// ## Behaviour details
|
||||
///
|
||||
/// - Follows the grammar specified in section 5.6 Internet Date/Time Format of
|
||||
/// RFC 3339 <https://datatracker.ietf.org/doc/html/rfc3339#section-5.6>.
|
||||
/// - The `T` and `Z` characters may alternatively be lower case `t` or `z`,
|
||||
/// respectively.
|
||||
/// - Full dates and full times must be separated by `T` or `t`. A space is also
|
||||
/// permitted.
|
||||
/// - Leap seconds rules are not considered. That is, any timestamp may
|
||||
/// specify digts `00` - `60` for the seconds.
|
||||
/// - Any part of a fractional second that cannot be represented in the
|
||||
/// nanosecond precision is tructated. That is, for the time string,
|
||||
/// `"1970-01-01T00:00:00.1234567899Z"`, the fractional second `.1234567899`
|
||||
/// will be represented as `123_456_789` in the `Timestamp`.
|
||||
///
|
||||
pub fn parse_rfc3339(input: String) -> Result(Timestamp, Nil) {
|
||||
let bytes = bit_array.from_string(input)
|
||||
|
||||
// Date
|
||||
use #(year, bytes) <- result.try(parse_year(from: bytes))
|
||||
use bytes <- result.try(accept_byte(from: bytes, value: byte_minus))
|
||||
use #(month, bytes) <- result.try(parse_month(from: bytes))
|
||||
use bytes <- result.try(accept_byte(from: bytes, value: byte_minus))
|
||||
use #(day, bytes) <- result.try(parse_day(from: bytes, year:, month:))
|
||||
|
||||
use bytes <- result.try(accept_date_time_separator(from: bytes))
|
||||
|
||||
// Time
|
||||
use #(hours, bytes) <- result.try(parse_hours(from: bytes))
|
||||
use bytes <- result.try(accept_byte(from: bytes, value: byte_colon))
|
||||
use #(minutes, bytes) <- result.try(parse_minutes(from: bytes))
|
||||
use bytes <- result.try(accept_byte(from: bytes, value: byte_colon))
|
||||
use #(seconds, bytes) <- result.try(parse_seconds(from: bytes))
|
||||
use #(second_fraction_as_nanoseconds, bytes) <- result.try(
|
||||
parse_second_fraction_as_nanoseconds(from: bytes),
|
||||
)
|
||||
|
||||
// Offset
|
||||
use #(offset_seconds, bytes) <- result.try(parse_offset(from: bytes))
|
||||
|
||||
// Done
|
||||
use Nil <- result.try(accept_empty(bytes))
|
||||
|
||||
Ok(from_date_time(
|
||||
year:,
|
||||
month:,
|
||||
day:,
|
||||
hours:,
|
||||
minutes:,
|
||||
seconds:,
|
||||
second_fraction_as_nanoseconds:,
|
||||
offset_seconds:,
|
||||
))
|
||||
}
|
||||
|
||||
fn parse_year(from bytes: BitArray) -> Result(#(Int, BitArray), Nil) {
|
||||
parse_digits(from: bytes, count: 4)
|
||||
}
|
||||
|
||||
fn parse_month(from bytes: BitArray) -> Result(#(Int, BitArray), Nil) {
|
||||
use #(month, bytes) <- result.try(parse_digits(from: bytes, count: 2))
|
||||
case 1 <= month && month <= 12 {
|
||||
True -> Ok(#(month, bytes))
|
||||
False -> Error(Nil)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_day(
|
||||
from bytes: BitArray,
|
||||
year year,
|
||||
month month,
|
||||
) -> Result(#(Int, BitArray), Nil) {
|
||||
use #(day, bytes) <- result.try(parse_digits(from: bytes, count: 2))
|
||||
|
||||
use max_day <- result.try(case month {
|
||||
1 | 3 | 5 | 7 | 8 | 10 | 12 -> Ok(31)
|
||||
4 | 6 | 9 | 11 -> Ok(30)
|
||||
2 -> {
|
||||
case is_leap_year(year) {
|
||||
True -> Ok(29)
|
||||
False -> Ok(28)
|
||||
}
|
||||
}
|
||||
_ -> Error(Nil)
|
||||
})
|
||||
|
||||
case 1 <= day && day <= max_day {
|
||||
True -> Ok(#(day, bytes))
|
||||
False -> Error(Nil)
|
||||
}
|
||||
}
|
||||
|
||||
// Implementation from RFC 3339 Appendix C
|
||||
fn is_leap_year(year: Int) -> Bool {
|
||||
year % 4 == 0 && { year % 100 != 0 || year % 400 == 0 }
|
||||
}
|
||||
|
||||
fn parse_hours(from bytes: BitArray) -> Result(#(Int, BitArray), Nil) {
|
||||
use #(hours, bytes) <- result.try(parse_digits(from: bytes, count: 2))
|
||||
case 0 <= hours && hours <= 23 {
|
||||
True -> Ok(#(hours, bytes))
|
||||
False -> Error(Nil)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_minutes(from bytes: BitArray) -> Result(#(Int, BitArray), Nil) {
|
||||
use #(minutes, bytes) <- result.try(parse_digits(from: bytes, count: 2))
|
||||
case 0 <= minutes && minutes <= 59 {
|
||||
True -> Ok(#(minutes, bytes))
|
||||
False -> Error(Nil)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_seconds(from bytes: BitArray) -> Result(#(Int, BitArray), Nil) {
|
||||
use #(seconds, bytes) <- result.try(parse_digits(from: bytes, count: 2))
|
||||
// Max of 60 for leap seconds. We don't bother to check if this leap second
|
||||
// actually occurred in the past or not.
|
||||
case 0 <= seconds && seconds <= 60 {
|
||||
True -> Ok(#(seconds, bytes))
|
||||
False -> Error(Nil)
|
||||
}
|
||||
}
|
||||
|
||||
// Truncates any part of the fraction that is beyond the nanosecond precision.
|
||||
fn parse_second_fraction_as_nanoseconds(from bytes: BitArray) {
|
||||
case bytes {
|
||||
<<".", byte, remaining_bytes:bytes>>
|
||||
if byte_zero <= byte && byte <= byte_nine
|
||||
-> {
|
||||
do_parse_second_fraction_as_nanoseconds(
|
||||
from: <<byte, remaining_bytes:bits>>,
|
||||
acc: 0,
|
||||
power: nanoseconds_per_second,
|
||||
)
|
||||
}
|
||||
// bytes starts with a ".", which should introduce a fraction, but it does
|
||||
// not, and so it is an ill-formed input.
|
||||
<<".", _:bytes>> -> Error(Nil)
|
||||
// bytes does not start with a "." so there is no fraction. Call this 0
|
||||
// nanoseconds.
|
||||
_ -> Ok(#(0, bytes))
|
||||
}
|
||||
}
|
||||
|
||||
fn do_parse_second_fraction_as_nanoseconds(
|
||||
from bytes: BitArray,
|
||||
acc acc: Int,
|
||||
power power: Int,
|
||||
) -> Result(#(Int, BitArray), a) {
|
||||
// Each digit place to the left in the fractional second is 10x fewer
|
||||
// nanoseconds.
|
||||
let power = power / 10
|
||||
|
||||
case bytes {
|
||||
<<byte, remaining_bytes:bytes>>
|
||||
if byte_zero <= byte && byte <= byte_nine && power < 1
|
||||
-> {
|
||||
// We already have the max precision for nanoseconds. Truncate any
|
||||
// remaining digits.
|
||||
do_parse_second_fraction_as_nanoseconds(
|
||||
from: remaining_bytes,
|
||||
acc:,
|
||||
power:,
|
||||
)
|
||||
}
|
||||
<<byte, remaining_bytes:bytes>> if byte_zero <= byte && byte <= byte_nine -> {
|
||||
// We have not yet reached the precision limit. Parse the next digit.
|
||||
let digit = byte - 0x30
|
||||
do_parse_second_fraction_as_nanoseconds(
|
||||
from: remaining_bytes,
|
||||
acc: acc + digit * power,
|
||||
power:,
|
||||
)
|
||||
}
|
||||
_ -> Ok(#(acc, bytes))
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_offset(from bytes: BitArray) -> Result(#(Int, BitArray), Nil) {
|
||||
case bytes {
|
||||
<<"Z", remaining_bytes:bytes>> | <<"z", remaining_bytes:bytes>> ->
|
||||
Ok(#(0, remaining_bytes))
|
||||
_ -> parse_numeric_offset(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_numeric_offset(from bytes: BitArray) -> Result(#(Int, BitArray), Nil) {
|
||||
use #(sign, bytes) <- result.try(parse_sign(from: bytes))
|
||||
use #(hours, bytes) <- result.try(parse_hours(from: bytes))
|
||||
use bytes <- result.try(accept_byte(from: bytes, value: byte_colon))
|
||||
use #(minutes, bytes) <- result.try(parse_minutes(from: bytes))
|
||||
|
||||
let offset_seconds = offset_to_seconds(sign, hours:, minutes:)
|
||||
|
||||
Ok(#(offset_seconds, bytes))
|
||||
}
|
||||
|
||||
fn parse_sign(from bytes) {
|
||||
case bytes {
|
||||
<<"+", remaining_bytes:bytes>> -> Ok(#("+", remaining_bytes))
|
||||
<<"-", remaining_bytes:bytes>> -> Ok(#("-", remaining_bytes))
|
||||
_ -> Error(Nil)
|
||||
}
|
||||
}
|
||||
|
||||
fn offset_to_seconds(sign, hours hours, minutes minutes) {
|
||||
let abs_seconds = hours * seconds_per_hour + minutes * seconds_per_minute
|
||||
|
||||
case sign {
|
||||
"-" -> -abs_seconds
|
||||
_ -> abs_seconds
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse and return the given number of digits from the given bytes.
|
||||
///
|
||||
fn parse_digits(
|
||||
from bytes: BitArray,
|
||||
count count: Int,
|
||||
) -> Result(#(Int, BitArray), Nil) {
|
||||
do_parse_digits(from: bytes, count:, acc: 0, k: 0)
|
||||
}
|
||||
|
||||
fn do_parse_digits(
|
||||
from bytes: BitArray,
|
||||
count count: Int,
|
||||
acc acc: Int,
|
||||
k k: Int,
|
||||
) -> Result(#(Int, BitArray), Nil) {
|
||||
case bytes {
|
||||
_ if k >= count -> Ok(#(acc, bytes))
|
||||
<<byte, remaining_bytes:bytes>> if byte_zero <= byte && byte <= byte_nine ->
|
||||
do_parse_digits(
|
||||
from: remaining_bytes,
|
||||
count:,
|
||||
acc: acc * 10 + { byte - 0x30 },
|
||||
k: k + 1,
|
||||
)
|
||||
_ -> Error(Nil)
|
||||
}
|
||||
}
|
||||
|
||||
/// Accept the given value from `bytes` and move past it if found.
|
||||
///
|
||||
fn accept_byte(from bytes: BitArray, value value: Int) -> Result(BitArray, Nil) {
|
||||
case bytes {
|
||||
<<byte, remaining_bytes:bytes>> if byte == value -> Ok(remaining_bytes)
|
||||
_ -> Error(Nil)
|
||||
}
|
||||
}
|
||||
|
||||
fn accept_date_time_separator(from bytes: BitArray) -> Result(BitArray, Nil) {
|
||||
case bytes {
|
||||
<<byte, remaining_bytes:bytes>>
|
||||
if byte == byte_t_uppercase
|
||||
|| byte == byte_t_lowercase
|
||||
|| byte == byte_space
|
||||
-> Ok(remaining_bytes)
|
||||
_ -> Error(Nil)
|
||||
}
|
||||
}
|
||||
|
||||
fn accept_empty(from bytes: BitArray) -> Result(Nil, Nil) {
|
||||
case bytes {
|
||||
<<>> -> Ok(Nil)
|
||||
_ -> Error(Nil)
|
||||
}
|
||||
}
|
||||
|
||||
/// Note: The caller of this function must ensure that all inputs are valid.
|
||||
///
|
||||
fn from_date_time(
|
||||
year year: Int,
|
||||
month month: Int,
|
||||
day day: Int,
|
||||
hours hours: Int,
|
||||
minutes minutes: Int,
|
||||
seconds seconds: Int,
|
||||
second_fraction_as_nanoseconds second_fraction_as_nanoseconds: Int,
|
||||
offset_seconds offset_seconds: Int,
|
||||
) -> Timestamp {
|
||||
let julian_seconds =
|
||||
julian_seconds_from_parts(year:, month:, day:, hours:, minutes:, seconds:)
|
||||
|
||||
let julian_seconds_since_epoch = julian_seconds - julian_seconds_unix_epoch
|
||||
|
||||
Timestamp(
|
||||
seconds: julian_seconds_since_epoch - offset_seconds,
|
||||
nanoseconds: second_fraction_as_nanoseconds,
|
||||
)
|
||||
|> normalise
|
||||
}
|
||||
|
||||
/// `julian_seconds_from_parts(year, month, day, hours, minutes, seconds)`
|
||||
/// returns the number of Julian
|
||||
/// seconds represented by the given arguments.
|
||||
///
|
||||
/// Note: It is the callers responsibility to ensure the inputs are valid.
|
||||
///
|
||||
/// See https://www.tondering.dk/claus/cal/julperiod.php#formula
|
||||
///
|
||||
fn julian_seconds_from_parts(
|
||||
year year: Int,
|
||||
month month: Int,
|
||||
day day: Int,
|
||||
hours hours: Int,
|
||||
minutes minutes: Int,
|
||||
seconds seconds: Int,
|
||||
) {
|
||||
let julian_day_seconds =
|
||||
julian_day_from_ymd(year:, month:, day:) * seconds_per_day
|
||||
|
||||
julian_day_seconds
|
||||
+ { hours * seconds_per_hour }
|
||||
+ { minutes * seconds_per_minute }
|
||||
+ seconds
|
||||
}
|
||||
|
||||
/// Note: It is the callers responsibility to ensure the inputs are valid.
|
||||
///
|
||||
/// See https://www.tondering.dk/claus/cal/julperiod.php#formula
|
||||
///
|
||||
fn julian_day_from_ymd(year year: Int, month month: Int, day day: Int) -> Int {
|
||||
let adjustment = { 14 - month } / 12
|
||||
let adjusted_year = year + 4800 - adjustment
|
||||
let adjusted_month = month + 12 * adjustment - 3
|
||||
|
||||
day
|
||||
+ { { 153 * adjusted_month } + 2 }
|
||||
/ 5
|
||||
+ 365
|
||||
* adjusted_year
|
||||
+ { adjusted_year / 4 }
|
||||
- { adjusted_year / 100 }
|
||||
+ { adjusted_year / 400 }
|
||||
- 32_045
|
||||
}
|
||||
|
||||
/// Create a timestamp from a number of seconds since 00:00:00 UTC on 1 January
|
||||
/// 1970.
|
||||
///
|
||||
pub fn from_unix_seconds(seconds: Int) -> Timestamp {
|
||||
Timestamp(seconds, 0)
|
||||
}
|
||||
|
||||
/// Create a timestamp from a number of seconds and nanoseconds since 00:00:00
|
||||
/// UTC on 1 January 1970.
|
||||
///
|
||||
/// # JavaScript int limitations
|
||||
///
|
||||
/// Remember that JavaScript can only perfectly represent ints between positive
|
||||
/// and negative 9,007,199,254,740,991! If you only use the nanosecond field
|
||||
/// then you will almost certainly not get the date value you want due to this
|
||||
/// loss of precision. Always use seconds primarily and then use nanoseconds
|
||||
/// for the final sub-second adjustment.
|
||||
///
|
||||
pub fn from_unix_seconds_and_nanoseconds(
|
||||
seconds seconds: Int,
|
||||
nanoseconds nanoseconds: Int,
|
||||
) -> Timestamp {
|
||||
Timestamp(seconds, nanoseconds)
|
||||
|> normalise
|
||||
}
|
||||
|
||||
/// Convert the timestamp to a number of seconds since 00:00:00 UTC on 1
|
||||
/// January 1970.
|
||||
///
|
||||
/// There may be some small loss of precision due to `Timestamp` being
|
||||
/// nanosecond accurate and `Float` not being able to represent this.
|
||||
///
|
||||
pub fn to_unix_seconds(timestamp: Timestamp) -> Float {
|
||||
let seconds = int.to_float(timestamp.seconds)
|
||||
let nanoseconds = int.to_float(timestamp.nanoseconds)
|
||||
seconds +. { nanoseconds /. 1_000_000_000.0 }
|
||||
}
|
||||
|
||||
/// Convert the timestamp to a number of seconds and nanoseconds since 00:00:00
|
||||
/// UTC on 1 January 1970. There is no loss of precision with this conversion
|
||||
/// on any target.
|
||||
pub fn to_unix_seconds_and_nanoseconds(timestamp: Timestamp) -> #(Int, Int) {
|
||||
#(timestamp.seconds, timestamp.nanoseconds)
|
||||
}
|
||||
468
build/packages/gleam_time/src/gleam@time@calendar.erl
Normal file
468
build/packages/gleam_time/src/gleam@time@calendar.erl
Normal file
|
|
@ -0,0 +1,468 @@
|
|||
-module(gleam@time@calendar).
|
||||
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
|
||||
-define(FILEPATH, "src/gleam/time/calendar.gleam").
|
||||
-export([local_offset/0, month_to_string/1, month_to_int/1, month_from_int/1, is_leap_year/1, is_valid_date/1, is_valid_time_of_day/1, naive_date_compare/2]).
|
||||
-export_type([date/0, time_of_day/0, month/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(
|
||||
" This module is for working with the Gregorian calendar, established by\n"
|
||||
" Pope Gregory XIII in 1582!\n"
|
||||
"\n"
|
||||
" ## When should you use this module?\n"
|
||||
"\n"
|
||||
" > **tldr:** You probably want to use [`gleam/time/timestamp`](./timestamp.html)\n"
|
||||
" > instead!\n"
|
||||
"\n"
|
||||
" Calendar time is difficult to work with programmatically, it is the source\n"
|
||||
" of most time-related bugs in software. Compared to _epoch time_, which the\n"
|
||||
" `gleam/time/timestamp` module uses, there are many disadvantages to\n"
|
||||
" calendar time:\n"
|
||||
"\n"
|
||||
" - They are ambiguous if you don't know what time-zone is being used.\n"
|
||||
"\n"
|
||||
" - A time-zone database is required to understand calendar time even when\n"
|
||||
" you have the time zone. These are large and your program has to\n"
|
||||
" continously be updated as new versions of the database are published.\n"
|
||||
"\n"
|
||||
" - The type permits invalid states. e.g. `days` could be set to the number\n"
|
||||
" 32, but this should not be possible!\n"
|
||||
"\n"
|
||||
" - There is not a single unique canonical value for each point in time,\n"
|
||||
" thanks to time zones. Two different `Date` + `TimeOfDay` value pairs\n"
|
||||
" could represent the same point in time. This means that you can't check\n"
|
||||
" for time equality with `==` when using calendar types.\n"
|
||||
"\n"
|
||||
" - They are computationally complex, using a more memory to represent and\n"
|
||||
" requiring a lot more CPU time to manipulate.\n"
|
||||
"\n"
|
||||
" There are also advantages to calendar time:\n"
|
||||
"\n"
|
||||
" - Calendar time is how human's talk about time, so if you want to show a\n"
|
||||
" time or take a time from a human user then calendar time will make it\n"
|
||||
" easier for them.\n"
|
||||
"\n"
|
||||
" - They can represent more abstract time periods such as \"New Year's Day\".\n"
|
||||
" This may seem like an exact window of time at first, but really the\n"
|
||||
" definition of \"New Year's Day\" is more fuzzy than that. When it starts\n"
|
||||
" and ends will depend where in the world you are, so if you want to refer\n"
|
||||
" to a day as a global concept instead of a fixed window of time for that\n"
|
||||
" day in a specific location, then calendar time can represent that.\n"
|
||||
"\n"
|
||||
" So when should you use calendar time? These are our recommendations:\n"
|
||||
"\n"
|
||||
" - Default to `gleam/time/timestamp`, which is epoch time. It is\n"
|
||||
" unambiguous, efficient, and significantly less likely to result in logic\n"
|
||||
" bugs.\n"
|
||||
"\n"
|
||||
" - When writing time to a database or other data storage use epoch time,\n"
|
||||
" using whatever epoch format it supports. For example, PostgreSQL\n"
|
||||
" `timestamp` and `timestampz` are both epoch time, and `timestamp` is\n"
|
||||
" preferred as it is more straightforward to use as your application is\n"
|
||||
" also using epoch time.\n"
|
||||
"\n"
|
||||
" - When communicating with other computer systems continue to use epoch\n"
|
||||
" time. For example, when sending times to another program you could\n"
|
||||
" encode time as UNIX timestamps (seconds since 00:00:00 UTC on 1 January\n"
|
||||
" 1970).\n"
|
||||
"\n"
|
||||
" - When communicating with humans use epoch time internally, and convert\n"
|
||||
" to-and-from calendar time at the last moment, when iteracting with the\n"
|
||||
" human user. It may also help the users to also show the time as a fuzzy\n"
|
||||
" duration from the present time, such as \"about 4 days ago\".\n"
|
||||
"\n"
|
||||
" - When representing \"fuzzy\" human time concepts that don't exact periods\n"
|
||||
" in time, such as \"one month\" (varies depending on which month, which\n"
|
||||
" year, and in which time zone) and \"Christmas Day\" (varies depending on\n"
|
||||
" which year and time zone) then use calendar time.\n"
|
||||
"\n"
|
||||
" Any time you do use calendar time you should be extra careful! It is very\n"
|
||||
" easy to make mistake with. Avoid it where possible.\n"
|
||||
"\n"
|
||||
" ## Time zone offsets\n"
|
||||
"\n"
|
||||
" This package includes the `utc_offset` value and the `local_offset`\n"
|
||||
" function, which are the offset for the UTC time zone and get the time\n"
|
||||
" offset the computer running the program is configured to respectively.\n"
|
||||
"\n"
|
||||
" If you need to use other offsets in your program then you will need to get\n"
|
||||
" them from somewhere else, such as from a package which loads the\n"
|
||||
" [IANA Time Zone Database](https://www.iana.org/time-zones), or from the\n"
|
||||
" website visitor's web browser, which your frontend can send for you.\n"
|
||||
"\n"
|
||||
" ## Use in APIs\n"
|
||||
"\n"
|
||||
" If you are making an API such as a HTTP JSON API you are encouraged to use\n"
|
||||
" Unix timestamps instead of calendar times.\n"
|
||||
).
|
||||
|
||||
-type date() :: {date, integer(), month(), integer()}.
|
||||
|
||||
-type time_of_day() :: {time_of_day, integer(), integer(), integer(), integer()}.
|
||||
|
||||
-type month() :: january |
|
||||
february |
|
||||
march |
|
||||
april |
|
||||
may |
|
||||
june |
|
||||
july |
|
||||
august |
|
||||
september |
|
||||
october |
|
||||
november |
|
||||
december.
|
||||
|
||||
-file("src/gleam/time/calendar.gleam", 147).
|
||||
?DOC(
|
||||
" Get the offset for the computer's currently configured time zone.\n"
|
||||
"\n"
|
||||
" Note this may not be the time zone that is correct to use for your user.\n"
|
||||
" For example, if you are making a web application that runs on a server you\n"
|
||||
" want _their_ computer's time zone, not yours.\n"
|
||||
"\n"
|
||||
" This is the _current local_ offset, not the current local time zone. This\n"
|
||||
" means that while it will result in the expected outcome for the current\n"
|
||||
" time, it may result in unexpected output if used with other timestamps. For\n"
|
||||
" example: a timestamp that would locally be during daylight savings time if\n"
|
||||
" is it not currently daylight savings time when this function is called.\n"
|
||||
).
|
||||
-spec local_offset() -> gleam@time@duration:duration().
|
||||
local_offset() ->
|
||||
gleam@time@duration:seconds(gleam_time_ffi:local_time_offset_seconds()).
|
||||
|
||||
-file("src/gleam/time/calendar.gleam", 163).
|
||||
?DOC(
|
||||
" Returns the English name for a month.\n"
|
||||
"\n"
|
||||
" # Examples\n"
|
||||
"\n"
|
||||
" ```gleam\n"
|
||||
" month_to_string(April)\n"
|
||||
" // -> \"April\"\n"
|
||||
" ```\n"
|
||||
).
|
||||
-spec month_to_string(month()) -> binary().
|
||||
month_to_string(Month) ->
|
||||
case Month of
|
||||
january ->
|
||||
<<"January"/utf8>>;
|
||||
|
||||
february ->
|
||||
<<"February"/utf8>>;
|
||||
|
||||
march ->
|
||||
<<"March"/utf8>>;
|
||||
|
||||
april ->
|
||||
<<"April"/utf8>>;
|
||||
|
||||
may ->
|
||||
<<"May"/utf8>>;
|
||||
|
||||
june ->
|
||||
<<"June"/utf8>>;
|
||||
|
||||
july ->
|
||||
<<"July"/utf8>>;
|
||||
|
||||
august ->
|
||||
<<"August"/utf8>>;
|
||||
|
||||
september ->
|
||||
<<"September"/utf8>>;
|
||||
|
||||
october ->
|
||||
<<"October"/utf8>>;
|
||||
|
||||
november ->
|
||||
<<"November"/utf8>>;
|
||||
|
||||
december ->
|
||||
<<"December"/utf8>>
|
||||
end.
|
||||
|
||||
-file("src/gleam/time/calendar.gleam", 188).
|
||||
?DOC(
|
||||
" Returns the number for the month, where January is 1 and December is 12.\n"
|
||||
"\n"
|
||||
" # Examples\n"
|
||||
"\n"
|
||||
" ```gleam\n"
|
||||
" month_to_int(January)\n"
|
||||
" // -> 1\n"
|
||||
" ```\n"
|
||||
).
|
||||
-spec month_to_int(month()) -> integer().
|
||||
month_to_int(Month) ->
|
||||
case Month of
|
||||
january ->
|
||||
1;
|
||||
|
||||
february ->
|
||||
2;
|
||||
|
||||
march ->
|
||||
3;
|
||||
|
||||
april ->
|
||||
4;
|
||||
|
||||
may ->
|
||||
5;
|
||||
|
||||
june ->
|
||||
6;
|
||||
|
||||
july ->
|
||||
7;
|
||||
|
||||
august ->
|
||||
8;
|
||||
|
||||
september ->
|
||||
9;
|
||||
|
||||
october ->
|
||||
10;
|
||||
|
||||
november ->
|
||||
11;
|
||||
|
||||
december ->
|
||||
12
|
||||
end.
|
||||
|
||||
-file("src/gleam/time/calendar.gleam", 213).
|
||||
?DOC(
|
||||
" Returns the month for a given number, where January is 1 and December is 12.\n"
|
||||
"\n"
|
||||
" # Examples\n"
|
||||
"\n"
|
||||
" ```gleam\n"
|
||||
" month_from_int(1)\n"
|
||||
" // -> Ok(January)\n"
|
||||
" ```\n"
|
||||
).
|
||||
-spec month_from_int(integer()) -> {ok, month()} | {error, nil}.
|
||||
month_from_int(Month) ->
|
||||
case Month of
|
||||
1 ->
|
||||
{ok, january};
|
||||
|
||||
2 ->
|
||||
{ok, february};
|
||||
|
||||
3 ->
|
||||
{ok, march};
|
||||
|
||||
4 ->
|
||||
{ok, april};
|
||||
|
||||
5 ->
|
||||
{ok, may};
|
||||
|
||||
6 ->
|
||||
{ok, june};
|
||||
|
||||
7 ->
|
||||
{ok, july};
|
||||
|
||||
8 ->
|
||||
{ok, august};
|
||||
|
||||
9 ->
|
||||
{ok, september};
|
||||
|
||||
10 ->
|
||||
{ok, october};
|
||||
|
||||
11 ->
|
||||
{ok, november};
|
||||
|
||||
12 ->
|
||||
{ok, december};
|
||||
|
||||
_ ->
|
||||
{error, nil}
|
||||
end.
|
||||
|
||||
-file("src/gleam/time/calendar.gleam", 290).
|
||||
?DOC(
|
||||
" Determines if a given year is a leap year.\n"
|
||||
"\n"
|
||||
" A leap year occurs every 4 years, except for years divisible by 100,\n"
|
||||
" unless they are also divisible by 400.\n"
|
||||
"\n"
|
||||
" # Examples\n"
|
||||
"\n"
|
||||
" ```gleam\n"
|
||||
" is_leap_year(2024)\n"
|
||||
" // -> True\n"
|
||||
" ```\n"
|
||||
"\n"
|
||||
" ```gleam\n"
|
||||
" is_leap_year(2023)\n"
|
||||
" // -> False\n"
|
||||
" ```\n"
|
||||
).
|
||||
-spec is_leap_year(integer()) -> boolean().
|
||||
is_leap_year(Year) ->
|
||||
case (Year rem 400) =:= 0 of
|
||||
true ->
|
||||
true;
|
||||
|
||||
false ->
|
||||
case (Year rem 100) =:= 0 of
|
||||
true ->
|
||||
false;
|
||||
|
||||
false ->
|
||||
(Year rem 4) =:= 0
|
||||
end
|
||||
end.
|
||||
|
||||
-file("src/gleam/time/calendar.gleam", 254).
|
||||
?DOC(
|
||||
" Checks if a given date is valid.\n"
|
||||
"\n"
|
||||
" This function properly accounts for leap years when validating February days.\n"
|
||||
" A leap year occurs every 4 years, except for years divisible by 100,\n"
|
||||
" unless they are also divisible by 400.\n"
|
||||
"\n"
|
||||
" # Examples\n"
|
||||
"\n"
|
||||
" ```gleam\n"
|
||||
" is_valid_date(Date(2023, April, 15))\n"
|
||||
" // -> True\n"
|
||||
" ```\n"
|
||||
"\n"
|
||||
" ```gleam\n"
|
||||
" is_valid_date(Date(2023, April, 31))\n"
|
||||
" // -> False\n"
|
||||
" ```\n"
|
||||
"\n"
|
||||
" ```gleam\n"
|
||||
" is_valid_date(Date(2024, February, 29))\n"
|
||||
" // -> True (2024 is a leap year)\n"
|
||||
" ```\n"
|
||||
).
|
||||
-spec is_valid_date(date()) -> boolean().
|
||||
is_valid_date(Date) ->
|
||||
{date, Year, Month, Day} = Date,
|
||||
case Day < 1 of
|
||||
true ->
|
||||
false;
|
||||
|
||||
false ->
|
||||
case Month of
|
||||
january ->
|
||||
Day =< 31;
|
||||
|
||||
march ->
|
||||
Day =< 31;
|
||||
|
||||
may ->
|
||||
Day =< 31;
|
||||
|
||||
july ->
|
||||
Day =< 31;
|
||||
|
||||
august ->
|
||||
Day =< 31;
|
||||
|
||||
october ->
|
||||
Day =< 31;
|
||||
|
||||
december ->
|
||||
Day =< 31;
|
||||
|
||||
april ->
|
||||
Day =< 30;
|
||||
|
||||
june ->
|
||||
Day =< 30;
|
||||
|
||||
september ->
|
||||
Day =< 30;
|
||||
|
||||
november ->
|
||||
Day =< 30;
|
||||
|
||||
february ->
|
||||
Max_february_days = case is_leap_year(Year) of
|
||||
true ->
|
||||
29;
|
||||
|
||||
false ->
|
||||
28
|
||||
end,
|
||||
Day =< Max_february_days
|
||||
end
|
||||
end.
|
||||
|
||||
-file("src/gleam/time/calendar.gleam", 313).
|
||||
?DOC(
|
||||
" Checks if a time of day is valid.\n"
|
||||
"\n"
|
||||
" Validates that hours are 0-23, minutes are 0-59, seconds are 0-59,\n"
|
||||
" and nanoseconds are 0-999,999,999.\n"
|
||||
"\n"
|
||||
" # Examples\n"
|
||||
"\n"
|
||||
" ```gleam\n"
|
||||
" is_valid_time_of_day(TimeOfDay(12, 30, 45, 123456789))\n"
|
||||
" // -> True\n"
|
||||
" ```\n"
|
||||
).
|
||||
-spec is_valid_time_of_day(time_of_day()) -> boolean().
|
||||
is_valid_time_of_day(Time) ->
|
||||
{time_of_day, Hours, Minutes, Seconds, Nanoseconds} = Time,
|
||||
(((((((Hours >= 0) andalso (Hours =< 23)) andalso (Minutes >= 0)) andalso (Minutes
|
||||
=< 59))
|
||||
andalso (Seconds >= 0))
|
||||
andalso (Seconds =< 59))
|
||||
andalso (Nanoseconds >= 0))
|
||||
andalso (Nanoseconds =< 999999999).
|
||||
|
||||
-file("src/gleam/time/calendar.gleam", 340).
|
||||
?DOC(
|
||||
" Naively compares two dates without any time zone information, returning an\n"
|
||||
" order.\n"
|
||||
"\n"
|
||||
" ## Correctness\n"
|
||||
"\n"
|
||||
" This function compares dates without any time zone information, only using\n"
|
||||
" the rules for the gregorian calendar. This is typically sufficient, but be\n"
|
||||
" aware that in reality some time zones will change their calendar date\n"
|
||||
" occasionally. This can result in days being skipped, out of order, or\n"
|
||||
" happening multiple times.\n"
|
||||
"\n"
|
||||
" If you need real-world correct time ordering then use the\n"
|
||||
" `gleam/time/timestamp` module instead.\n"
|
||||
).
|
||||
-spec naive_date_compare(date(), date()) -> gleam@order:order().
|
||||
naive_date_compare(One, Other) ->
|
||||
_pipe = gleam@int:compare(erlang:element(2, One), erlang:element(2, Other)),
|
||||
_pipe@1 = gleam@order:lazy_break_tie(
|
||||
_pipe,
|
||||
fun() ->
|
||||
gleam@int:compare(
|
||||
month_to_int(erlang:element(3, One)),
|
||||
month_to_int(erlang:element(3, Other))
|
||||
)
|
||||
end
|
||||
),
|
||||
gleam@order:lazy_break_tie(
|
||||
_pipe@1,
|
||||
fun() ->
|
||||
gleam@int:compare(erlang:element(4, One), erlang:element(4, Other))
|
||||
end
|
||||
).
|
||||
381
build/packages/gleam_time/src/gleam@time@duration.erl
Normal file
381
build/packages/gleam_time/src/gleam@time@duration.erl
Normal file
|
|
@ -0,0 +1,381 @@
|
|||
-module(gleam@time@duration).
|
||||
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
|
||||
-define(FILEPATH, "src/gleam/time/duration.gleam").
|
||||
-export([approximate/1, compare/2, difference/2, add/2, seconds/1, minutes/1, hours/1, milliseconds/1, nanoseconds/1, to_seconds/1, to_seconds_and_nanoseconds/1, to_iso8601_string/1]).
|
||||
-export_type([duration/0, unit/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.
|
||||
|
||||
-opaque duration() :: {duration, integer(), integer()}.
|
||||
|
||||
-type unit() :: nanosecond |
|
||||
microsecond |
|
||||
millisecond |
|
||||
second |
|
||||
minute |
|
||||
hour |
|
||||
day |
|
||||
week |
|
||||
month |
|
||||
year.
|
||||
|
||||
-file("src/gleam/time/duration.gleam", 110).
|
||||
?DOC(
|
||||
" Ensure the duration is represented with `nanoseconds` being positive and\n"
|
||||
" less than 1 second.\n"
|
||||
"\n"
|
||||
" This function does not change the amount of time that the duratoin refers\n"
|
||||
" to, it only adjusts the values used to represent the time.\n"
|
||||
).
|
||||
-spec normalise(duration()) -> duration().
|
||||
normalise(Duration) ->
|
||||
Multiplier = 1000000000,
|
||||
Nanoseconds = case Multiplier of
|
||||
0 -> 0;
|
||||
Gleam@denominator -> erlang:element(3, Duration) rem Gleam@denominator
|
||||
end,
|
||||
Overflow = erlang:element(3, Duration) - Nanoseconds,
|
||||
Seconds = erlang:element(2, Duration) + (case Multiplier of
|
||||
0 -> 0;
|
||||
Gleam@denominator@1 -> Overflow div Gleam@denominator@1
|
||||
end),
|
||||
case Nanoseconds >= 0 of
|
||||
true ->
|
||||
{duration, Seconds, Nanoseconds};
|
||||
|
||||
false ->
|
||||
{duration, Seconds - 1, Multiplier + Nanoseconds}
|
||||
end.
|
||||
|
||||
-file("src/gleam/time/duration.gleam", 76).
|
||||
?DOC(
|
||||
" Convert a duration to a number of the largest number of a unit, serving as\n"
|
||||
" a rough description of the duration that a human can understand.\n"
|
||||
"\n"
|
||||
" The size used for each unit are described in the documentation for the\n"
|
||||
" `Unit` type.\n"
|
||||
"\n"
|
||||
" ```gleam\n"
|
||||
" seconds(125)\n"
|
||||
" |> approximate\n"
|
||||
" // -> #(2, Minute)\n"
|
||||
" ```\n"
|
||||
"\n"
|
||||
" This function rounds _towards zero_. This means that if a duration is just\n"
|
||||
" short of 2 days then it will approximate to 1 day.\n"
|
||||
"\n"
|
||||
" ```gleam\n"
|
||||
" hours(47)\n"
|
||||
" |> approximate\n"
|
||||
" // -> #(1, Day)\n"
|
||||
" ```\n"
|
||||
).
|
||||
-spec approximate(duration()) -> {integer(), unit()}.
|
||||
approximate(Duration) ->
|
||||
{duration, S, Ns} = Duration,
|
||||
Minute = 60,
|
||||
Hour = Minute * 60,
|
||||
Day = Hour * 24,
|
||||
Week = Day * 7,
|
||||
Year = (Day * 365) + (Hour * 6),
|
||||
Month = Year div 12,
|
||||
Microsecond = 1000,
|
||||
Millisecond = Microsecond * 1000,
|
||||
case nil of
|
||||
_ when S < 0 ->
|
||||
{Amount, Unit} = begin
|
||||
_pipe = {duration, - S, - Ns},
|
||||
_pipe@1 = normalise(_pipe),
|
||||
approximate(_pipe@1)
|
||||
end,
|
||||
{- Amount, Unit};
|
||||
|
||||
_ when S >= Year ->
|
||||
{case Year of
|
||||
0 -> 0;
|
||||
Gleam@denominator -> S div Gleam@denominator
|
||||
end, year};
|
||||
|
||||
_ when S >= Month ->
|
||||
{case Month of
|
||||
0 -> 0;
|
||||
Gleam@denominator@1 -> S div Gleam@denominator@1
|
||||
end, month};
|
||||
|
||||
_ when S >= Week ->
|
||||
{case Week of
|
||||
0 -> 0;
|
||||
Gleam@denominator@2 -> S div Gleam@denominator@2
|
||||
end, week};
|
||||
|
||||
_ when S >= Day ->
|
||||
{case Day of
|
||||
0 -> 0;
|
||||
Gleam@denominator@3 -> S div Gleam@denominator@3
|
||||
end, day};
|
||||
|
||||
_ when S >= Hour ->
|
||||
{case Hour of
|
||||
0 -> 0;
|
||||
Gleam@denominator@4 -> S div Gleam@denominator@4
|
||||
end, hour};
|
||||
|
||||
_ when S >= Minute ->
|
||||
{case Minute of
|
||||
0 -> 0;
|
||||
Gleam@denominator@5 -> S div Gleam@denominator@5
|
||||
end, minute};
|
||||
|
||||
_ when S > 0 ->
|
||||
{S, second};
|
||||
|
||||
_ when Ns >= Millisecond ->
|
||||
{case Millisecond of
|
||||
0 -> 0;
|
||||
Gleam@denominator@6 -> Ns div Gleam@denominator@6
|
||||
end, millisecond};
|
||||
|
||||
_ when Ns >= Microsecond ->
|
||||
{case Microsecond of
|
||||
0 -> 0;
|
||||
Gleam@denominator@7 -> Ns div Gleam@denominator@7
|
||||
end, microsecond};
|
||||
|
||||
_ ->
|
||||
{Ns, nanosecond}
|
||||
end.
|
||||
|
||||
-file("src/gleam/time/duration.gleam", 140).
|
||||
?DOC(
|
||||
" Compare one duration to another, indicating whether the first spans a\n"
|
||||
" larger amount of time (and so is greater) or smaller amount of time (and so\n"
|
||||
" is lesser) than the second.\n"
|
||||
"\n"
|
||||
" # Examples\n"
|
||||
"\n"
|
||||
" ```gleam\n"
|
||||
" compare(seconds(1), seconds(2))\n"
|
||||
" // -> order.Lt\n"
|
||||
" ```\n"
|
||||
"\n"
|
||||
" Whether a duration is negative or positive doesn't matter for comparing\n"
|
||||
" them, only the amount of time spanned matters.\n"
|
||||
"\n"
|
||||
" ```gleam\n"
|
||||
" compare(seconds(-2), seconds(1))\n"
|
||||
" // -> order.Gt\n"
|
||||
" ```\n"
|
||||
).
|
||||
-spec compare(duration(), duration()) -> gleam@order:order().
|
||||
compare(Left, Right) ->
|
||||
Parts = fun(X) -> case erlang:element(2, X) >= 0 of
|
||||
true ->
|
||||
{erlang:element(2, X), erlang:element(3, X)};
|
||||
|
||||
false ->
|
||||
{(erlang:element(2, X) * -1) - 1,
|
||||
1000000000 - erlang:element(3, X)}
|
||||
end end,
|
||||
{Ls, Lns} = Parts(Left),
|
||||
{Rs, Rns} = Parts(Right),
|
||||
_pipe = gleam@int:compare(Ls, Rs),
|
||||
gleam@order:break_tie(_pipe, gleam@int:compare(Lns, Rns)).
|
||||
|
||||
-file("src/gleam/time/duration.gleam", 164).
|
||||
?DOC(
|
||||
" Calculate the difference between two durations.\n"
|
||||
"\n"
|
||||
" This is effectively substracting the first duration from the second.\n"
|
||||
"\n"
|
||||
" # Examples\n"
|
||||
"\n"
|
||||
" ```gleam\n"
|
||||
" difference(seconds(1), seconds(5))\n"
|
||||
" // -> seconds(4)\n"
|
||||
" ```\n"
|
||||
).
|
||||
-spec difference(duration(), duration()) -> duration().
|
||||
difference(Left, Right) ->
|
||||
_pipe = {duration,
|
||||
erlang:element(2, Right) - erlang:element(2, Left),
|
||||
erlang:element(3, Right) - erlang:element(3, Left)},
|
||||
normalise(_pipe).
|
||||
|
||||
-file("src/gleam/time/duration.gleam", 178).
|
||||
?DOC(
|
||||
" Add two durations together.\n"
|
||||
"\n"
|
||||
" # Examples\n"
|
||||
"\n"
|
||||
" ```gleam\n"
|
||||
" add(seconds(1), seconds(5))\n"
|
||||
" // -> seconds(6)\n"
|
||||
" ```\n"
|
||||
).
|
||||
-spec add(duration(), duration()) -> duration().
|
||||
add(Left, Right) ->
|
||||
_pipe = {duration,
|
||||
erlang:element(2, Left) + erlang:element(2, Right),
|
||||
erlang:element(3, Left) + erlang:element(3, Right)},
|
||||
normalise(_pipe).
|
||||
|
||||
-file("src/gleam/time/duration.gleam", 225).
|
||||
-spec nanosecond_digits(integer(), integer(), binary()) -> binary().
|
||||
nanosecond_digits(N, Position, Acc) ->
|
||||
case Position of
|
||||
9 ->
|
||||
Acc;
|
||||
|
||||
_ when (Acc =:= <<""/utf8>>) andalso ((N rem 10) =:= 0) ->
|
||||
nanosecond_digits(N div 10, Position + 1, Acc);
|
||||
|
||||
_ ->
|
||||
Acc@1 = <<(erlang:integer_to_binary(N rem 10))/binary, Acc/binary>>,
|
||||
nanosecond_digits(N div 10, Position + 1, Acc@1)
|
||||
end.
|
||||
|
||||
-file("src/gleam/time/duration.gleam", 239).
|
||||
?DOC(" Create a duration of a number of seconds.\n").
|
||||
-spec seconds(integer()) -> duration().
|
||||
seconds(Amount) ->
|
||||
{duration, Amount, 0}.
|
||||
|
||||
-file("src/gleam/time/duration.gleam", 244).
|
||||
?DOC(" Create a duration of a number of minutes.\n").
|
||||
-spec minutes(integer()) -> duration().
|
||||
minutes(Amount) ->
|
||||
seconds(Amount * 60).
|
||||
|
||||
-file("src/gleam/time/duration.gleam", 249).
|
||||
?DOC(" Create a duration of a number of hours.\n").
|
||||
-spec hours(integer()) -> duration().
|
||||
hours(Amount) ->
|
||||
seconds((Amount * 60) * 60).
|
||||
|
||||
-file("src/gleam/time/duration.gleam", 254).
|
||||
?DOC(" Create a duration of a number of milliseconds.\n").
|
||||
-spec milliseconds(integer()) -> duration().
|
||||
milliseconds(Amount) ->
|
||||
Remainder = Amount rem 1000,
|
||||
Overflow = Amount - Remainder,
|
||||
Nanoseconds = Remainder * 1000000,
|
||||
Seconds = Overflow div 1000,
|
||||
_pipe = {duration, Seconds, Nanoseconds},
|
||||
normalise(_pipe).
|
||||
|
||||
-file("src/gleam/time/duration.gleam", 273).
|
||||
?DOC(
|
||||
" Create a duration of a number of nanoseconds.\n"
|
||||
"\n"
|
||||
" # JavaScript int limitations\n"
|
||||
"\n"
|
||||
" Remember that JavaScript can only perfectly represent ints between positive\n"
|
||||
" and negative 9,007,199,254,740,991! If you use a single call to this\n"
|
||||
" function to create durations larger than that number of nanoseconds then\n"
|
||||
" you will likely not get exactly the value you expect. Use `seconds` and\n"
|
||||
" `milliseconds` as much as possible for large durations.\n"
|
||||
).
|
||||
-spec nanoseconds(integer()) -> duration().
|
||||
nanoseconds(Amount) ->
|
||||
_pipe = {duration, 0, Amount},
|
||||
normalise(_pipe).
|
||||
|
||||
-file("src/gleam/time/duration.gleam", 283).
|
||||
?DOC(
|
||||
" Convert the duration to a number of seconds.\n"
|
||||
"\n"
|
||||
" There may be some small loss of precision due to `Duration` being\n"
|
||||
" nanosecond accurate and `Float` not being able to represent this.\n"
|
||||
).
|
||||
-spec to_seconds(duration()) -> float().
|
||||
to_seconds(Duration) ->
|
||||
Seconds = erlang:float(erlang:element(2, Duration)),
|
||||
Nanoseconds = erlang:float(erlang:element(3, Duration)),
|
||||
Seconds + (Nanoseconds / 1000000000.0).
|
||||
|
||||
-file("src/gleam/time/duration.gleam", 292).
|
||||
?DOC(
|
||||
" Convert the duration to a number of seconds and nanoseconds. There is no\n"
|
||||
" loss of precision with this conversion on any target.\n"
|
||||
).
|
||||
-spec to_seconds_and_nanoseconds(duration()) -> {integer(), integer()}.
|
||||
to_seconds_and_nanoseconds(Duration) ->
|
||||
{erlang:element(2, Duration), erlang:element(3, Duration)}.
|
||||
|
||||
-file("src/gleam/time/duration.gleam", 192).
|
||||
?DOC(
|
||||
" Convert the duration to an [ISO8601][1] formatted duration string.\n"
|
||||
"\n"
|
||||
" The ISO8601 duration format is ambiguous without context due to months and\n"
|
||||
" years having different lengths, and because of leap seconds. This function\n"
|
||||
" encodes the duration as days, hours, and seconds without any leap seconds.\n"
|
||||
" Be sure to take this into account when using the duration strings.\n"
|
||||
"\n"
|
||||
" [1]: https://en.wikipedia.org/wiki/ISO_8601#Durations\n"
|
||||
).
|
||||
-spec to_iso8601_string(duration()) -> binary().
|
||||
to_iso8601_string(Duration) ->
|
||||
gleam@bool:guard(
|
||||
Duration =:= {duration, 0, 0},
|
||||
<<"PT0S"/utf8>>,
|
||||
fun() ->
|
||||
Split = fun(Total, Limit) ->
|
||||
Amount = case Limit of
|
||||
0 -> 0;
|
||||
Gleam@denominator -> Total rem Gleam@denominator
|
||||
end,
|
||||
Remainder = case Limit of
|
||||
0 -> 0;
|
||||
Gleam@denominator@1 -> (Total - Amount) div Gleam@denominator@1
|
||||
end,
|
||||
{Amount, Remainder}
|
||||
end,
|
||||
{Seconds, Rest} = Split(erlang:element(2, Duration), 60),
|
||||
{Minutes, Rest@1} = Split(Rest, 60),
|
||||
{Hours, Rest@2} = Split(Rest@1, 24),
|
||||
Days = Rest@2,
|
||||
Add = fun(Out, Value, Unit) -> case Value of
|
||||
0 ->
|
||||
Out;
|
||||
|
||||
_ ->
|
||||
<<<<Out/binary,
|
||||
(erlang:integer_to_binary(Value))/binary>>/binary,
|
||||
Unit/binary>>
|
||||
end end,
|
||||
Output = begin
|
||||
_pipe = <<"P"/utf8>>,
|
||||
_pipe@1 = Add(_pipe, Days, <<"D"/utf8>>),
|
||||
_pipe@2 = gleam@string:append(_pipe@1, <<"T"/utf8>>),
|
||||
_pipe@3 = Add(_pipe@2, Hours, <<"H"/utf8>>),
|
||||
Add(_pipe@3, Minutes, <<"M"/utf8>>)
|
||||
end,
|
||||
case {Seconds, erlang:element(3, Duration)} of
|
||||
{0, 0} ->
|
||||
Output;
|
||||
|
||||
{_, 0} ->
|
||||
<<<<Output/binary,
|
||||
(erlang:integer_to_binary(Seconds))/binary>>/binary,
|
||||
"S"/utf8>>;
|
||||
|
||||
{_, _} ->
|
||||
F = nanosecond_digits(
|
||||
erlang:element(3, Duration),
|
||||
0,
|
||||
<<""/utf8>>
|
||||
),
|
||||
<<<<<<<<Output/binary,
|
||||
(erlang:integer_to_binary(Seconds))/binary>>/binary,
|
||||
"."/utf8>>/binary,
|
||||
F/binary>>/binary,
|
||||
"S"/utf8>>
|
||||
end
|
||||
end
|
||||
).
|
||||
1188
build/packages/gleam_time/src/gleam@time@timestamp.erl
Normal file
1188
build/packages/gleam_time/src/gleam@time@timestamp.erl
Normal file
File diff suppressed because it is too large
Load diff
12
build/packages/gleam_time/src/gleam_time.app.src
Normal file
12
build/packages/gleam_time/src/gleam_time.app.src
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{application, gleam_time, [
|
||||
{vsn, "1.6.0"},
|
||||
{applications, [gleam_stdlib]},
|
||||
{description, "Work with time in Gleam!"},
|
||||
{modules, [gleam@time@calendar,
|
||||
gleam@time@duration,
|
||||
gleam@time@timestamp,
|
||||
gleam_time@@main,
|
||||
gleam_time_ffi,
|
||||
gleam_time_test_ffi]},
|
||||
{registered, []}
|
||||
]}.
|
||||
12
build/packages/gleam_time/src/gleam_time_ffi.erl
Normal file
12
build/packages/gleam_time/src/gleam_time_ffi.erl
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
-module(gleam_time_ffi).
|
||||
-export([system_time/0, local_time_offset_seconds/0]).
|
||||
|
||||
system_time() ->
|
||||
{0, erlang:system_time(nanosecond)}.
|
||||
|
||||
local_time_offset_seconds() ->
|
||||
Utc = calendar:universal_time(),
|
||||
Local = calendar:local_time(),
|
||||
UtcSeconds = calendar:datetime_to_gregorian_seconds(Utc),
|
||||
LocalSeconds = calendar:datetime_to_gregorian_seconds(Local),
|
||||
LocalSeconds - UtcSeconds.
|
||||
11
build/packages/gleam_time/src/gleam_time_ffi.mjs
Normal file
11
build/packages/gleam_time/src/gleam_time_ffi.mjs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
export function system_time() {
|
||||
const now = Date.now();
|
||||
const milliseconds = now % 1_000;
|
||||
const nanoseconds = milliseconds * 1000_000;
|
||||
const seconds = (now - milliseconds) / 1_000;
|
||||
return [seconds, nanoseconds];
|
||||
}
|
||||
|
||||
export function local_time_offset_seconds() {
|
||||
return new Date().getTimezoneOffset() * -60;
|
||||
}
|
||||
191
build/packages/gleeunit/LICENCE
Normal file
191
build/packages/gleeunit/LICENCE
Normal 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 2021, Louis Pilfold <louis@lpil.uk>.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
44
build/packages/gleeunit/README.md
Normal file
44
build/packages/gleeunit/README.md
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
# gleeunit
|
||||
|
||||
A simple test runner for Gleam, using EUnit on Erlang and a custom runner on JS.
|
||||
|
||||
[](https://hex.pm/packages/gleeunit)
|
||||
[](https://hexdocs.pm/gleeunit/)
|
||||
|
||||
|
||||
```sh
|
||||
gleam add gleeunit@1 --dev
|
||||
```
|
||||
```gleam
|
||||
// In test/yourapp_test.gleam
|
||||
import gleeunit
|
||||
|
||||
pub fn main() {
|
||||
gleeunit.main()
|
||||
}
|
||||
```
|
||||
|
||||
Now any public function with a name ending in `_test` in the `test` directory
|
||||
will be found and run as a test.
|
||||
|
||||
```gleam
|
||||
pub fn some_function_test() {
|
||||
assert some_function() == "Hello!"
|
||||
}
|
||||
```
|
||||
|
||||
Run the tests by entering `gleam test` in the command line.
|
||||
|
||||
### Deno
|
||||
|
||||
If using the Deno JavaScript runtime, you will need to add the following to your
|
||||
`gleam.toml`.
|
||||
|
||||
```toml
|
||||
[javascript.deno]
|
||||
allow_read = [
|
||||
"gleam.toml",
|
||||
"test",
|
||||
"build",
|
||||
]
|
||||
```
|
||||
16
build/packages/gleeunit/gleam.toml
Normal file
16
build/packages/gleeunit/gleam.toml
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
name = "gleeunit"
|
||||
version = "1.9.0"
|
||||
licences = ["Apache-2.0"]
|
||||
description = "A simple test runner for Gleam, using EUnit on Erlang"
|
||||
repository = { type = "github", user = "lpil", repo = "gleeunit" }
|
||||
links = [{ title = "Sponsor", href = "https://github.com/sponsors/lpil" }]
|
||||
gleam = ">= 1.13.0"
|
||||
|
||||
[javascript.deno]
|
||||
allow_read = ["gleam.toml", "test", "build"]
|
||||
|
||||
[dependencies]
|
||||
gleam_stdlib = ">= 0.60.0 and < 1.0.0"
|
||||
|
||||
[dev-dependencies]
|
||||
testhelper = { "path" = "./testhelper" }
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
-record(assert, {
|
||||
start :: integer(),
|
||||
'end' :: integer(),
|
||||
expression_start :: integer(),
|
||||
kind :: gleeunit@internal@gleam_panic:assert_kind()
|
||||
}).
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
-record(asserted_expression, {
|
||||
start :: integer(),
|
||||
'end' :: integer(),
|
||||
kind :: gleeunit@internal@gleam_panic:expression_kind()
|
||||
}).
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
-record(binary_operator, {
|
||||
operator :: binary(),
|
||||
left :: gleeunit@internal@gleam_panic:asserted_expression(),
|
||||
right :: gleeunit@internal@gleam_panic:asserted_expression()
|
||||
}).
|
||||
|
|
@ -0,0 +1 @@
|
|||
-record(expression, {value :: gleam@dynamic:dynamic_()}).
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
-record(function_call, {
|
||||
arguments :: list(gleeunit@internal@gleam_panic:asserted_expression())
|
||||
}).
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
-record(gleam_panic, {
|
||||
message :: binary(),
|
||||
file :: binary(),
|
||||
module :: binary(),
|
||||
function :: binary(),
|
||||
line :: integer(),
|
||||
kind :: gleeunit@internal@gleam_panic:panic_kind()
|
||||
}).
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
-record(let_assert, {
|
||||
start :: integer(),
|
||||
'end' :: integer(),
|
||||
pattern_start :: integer(),
|
||||
pattern_end :: integer(),
|
||||
value :: gleam@dynamic:dynamic_()
|
||||
}).
|
||||
|
|
@ -0,0 +1 @@
|
|||
-record(literal, {value :: gleam@dynamic:dynamic_()}).
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
-record(other_expression, {
|
||||
expression :: gleeunit@internal@gleam_panic:asserted_expression()
|
||||
}).
|
||||
|
|
@ -0,0 +1 @@
|
|||
-record(state, {passed :: integer(), failed :: integer(), skipped :: integer()}).
|
||||
16
build/packages/gleeunit/src/gleeunit.app.src
Normal file
16
build/packages/gleeunit/src/gleeunit.app.src
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
{application, gleeunit, [
|
||||
{vsn, "1.9.0"},
|
||||
{applications, [gleam_stdlib]},
|
||||
{description, "A simple test runner for Gleam, using EUnit on Erlang"},
|
||||
{modules, [erlang_test_module,
|
||||
gleeunit,
|
||||
gleeunit@@main,
|
||||
gleeunit@internal@gleam_panic,
|
||||
gleeunit@internal@reporting,
|
||||
gleeunit@should,
|
||||
gleeunit_ffi,
|
||||
gleeunit_gleam_panic_ffi,
|
||||
gleeunit_progress,
|
||||
gleeunit_test_ffi]},
|
||||
{registered, []}
|
||||
]}.
|
||||
89
build/packages/gleeunit/src/gleeunit.erl
Normal file
89
build/packages/gleeunit/src/gleeunit.erl
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
-module(gleeunit).
|
||||
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
|
||||
-define(FILEPATH, "src/gleeunit.gleam").
|
||||
-export([main/0]).
|
||||
-export_type([atom_/0, encoding/0, report_module_name/0, gleeunit_progress_option/0, eunit_option/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 atom_() :: any().
|
||||
|
||||
-type encoding() :: utf8.
|
||||
|
||||
-type report_module_name() :: gleeunit_progress.
|
||||
|
||||
-type gleeunit_progress_option() :: {colored, boolean()}.
|
||||
|
||||
-type eunit_option() :: verbose |
|
||||
no_tty |
|
||||
{report, {report_module_name(), list(gleeunit_progress_option())}} |
|
||||
{scale_timeouts, integer()}.
|
||||
|
||||
-file("src/gleeunit.gleam", 42).
|
||||
-spec gleam_to_erlang_module_name(binary()) -> binary().
|
||||
gleam_to_erlang_module_name(Path) ->
|
||||
case gleam_stdlib:string_ends_with(Path, <<".gleam"/utf8>>) of
|
||||
true ->
|
||||
_pipe = Path,
|
||||
_pipe@1 = gleam@string:replace(
|
||||
_pipe,
|
||||
<<".gleam"/utf8>>,
|
||||
<<""/utf8>>
|
||||
),
|
||||
gleam@string:replace(_pipe@1, <<"/"/utf8>>, <<"@"/utf8>>);
|
||||
|
||||
false ->
|
||||
_pipe@2 = Path,
|
||||
_pipe@3 = gleam@string:split(_pipe@2, <<"/"/utf8>>),
|
||||
_pipe@4 = gleam@list:last(_pipe@3),
|
||||
_pipe@5 = gleam@result:unwrap(_pipe@4, Path),
|
||||
gleam@string:replace(_pipe@5, <<".erl"/utf8>>, <<""/utf8>>)
|
||||
end.
|
||||
|
||||
-file("src/gleeunit.gleam", 18).
|
||||
-spec do_main() -> nil.
|
||||
do_main() ->
|
||||
Options = [verbose,
|
||||
no_tty,
|
||||
{report, {gleeunit_progress, [{colored, true}]}},
|
||||
{scale_timeouts, 10}],
|
||||
Result = begin
|
||||
_pipe = gleeunit_ffi:find_files(
|
||||
<<"**/*.{erl,gleam}"/utf8>>,
|
||||
<<"test"/utf8>>
|
||||
),
|
||||
_pipe@1 = gleam@list:map(_pipe, fun gleam_to_erlang_module_name/1),
|
||||
_pipe@2 = gleam@list:map(
|
||||
_pipe@1,
|
||||
fun(_capture) -> erlang:binary_to_atom(_capture, utf8) end
|
||||
),
|
||||
gleeunit_ffi:run_eunit(_pipe@2, Options)
|
||||
end,
|
||||
Code = case Result of
|
||||
{ok, _} ->
|
||||
0;
|
||||
|
||||
{error, _} ->
|
||||
1
|
||||
end,
|
||||
erlang:halt(Code).
|
||||
|
||||
-file("src/gleeunit.gleam", 13).
|
||||
?DOC(
|
||||
" Find and run all test functions for the current project using Erlang's EUnit\n"
|
||||
" test framework, or a custom JavaScript test runner.\n"
|
||||
"\n"
|
||||
" Any Erlang or Gleam function in the `test` directory with a name ending in\n"
|
||||
" `_test` is considered a test function and will be run.\n"
|
||||
"\n"
|
||||
" A test that panics is considered a failure.\n"
|
||||
).
|
||||
-spec main() -> nil.
|
||||
main() ->
|
||||
do_main().
|
||||
86
build/packages/gleeunit/src/gleeunit.gleam
Normal file
86
build/packages/gleeunit/src/gleeunit.gleam
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
import gleam/list
|
||||
import gleam/result
|
||||
import gleam/string
|
||||
|
||||
/// Find and run all test functions for the current project using Erlang's EUnit
|
||||
/// test framework, or a custom JavaScript test runner.
|
||||
///
|
||||
/// Any Erlang or Gleam function in the `test` directory with a name ending in
|
||||
/// `_test` is considered a test function and will be run.
|
||||
///
|
||||
/// A test that panics is considered a failure.
|
||||
///
|
||||
pub fn main() -> Nil {
|
||||
do_main()
|
||||
}
|
||||
|
||||
@external(javascript, "./gleeunit_ffi.mjs", "main")
|
||||
fn do_main() -> Nil {
|
||||
let options = [
|
||||
Verbose,
|
||||
NoTty,
|
||||
Report(#(GleeunitProgress, [Colored(True)])),
|
||||
ScaleTimeouts(10),
|
||||
]
|
||||
|
||||
let result =
|
||||
find_files(matching: "**/*.{erl,gleam}", in: "test")
|
||||
|> list.map(gleam_to_erlang_module_name)
|
||||
|> list.map(dangerously_convert_string_to_atom(_, Utf8))
|
||||
|> run_eunit(options)
|
||||
|
||||
let code = case result {
|
||||
Ok(_) -> 0
|
||||
Error(_) -> 1
|
||||
}
|
||||
halt(code)
|
||||
}
|
||||
|
||||
@external(erlang, "erlang", "halt")
|
||||
fn halt(a: Int) -> Nil
|
||||
|
||||
fn gleam_to_erlang_module_name(path: String) -> String {
|
||||
case string.ends_with(path, ".gleam") {
|
||||
True ->
|
||||
path
|
||||
|> string.replace(".gleam", "")
|
||||
|> string.replace("/", "@")
|
||||
|
||||
False ->
|
||||
path
|
||||
|> string.split("/")
|
||||
|> list.last
|
||||
|> result.unwrap(path)
|
||||
|> string.replace(".erl", "")
|
||||
}
|
||||
}
|
||||
|
||||
@external(erlang, "gleeunit_ffi", "find_files")
|
||||
fn find_files(matching matching: String, in in: String) -> List(String)
|
||||
|
||||
type Atom
|
||||
|
||||
type Encoding {
|
||||
Utf8
|
||||
}
|
||||
|
||||
@external(erlang, "erlang", "binary_to_atom")
|
||||
fn dangerously_convert_string_to_atom(a: String, b: Encoding) -> Atom
|
||||
|
||||
type ReportModuleName {
|
||||
GleeunitProgress
|
||||
}
|
||||
|
||||
type GleeunitProgressOption {
|
||||
Colored(Bool)
|
||||
}
|
||||
|
||||
type EunitOption {
|
||||
Verbose
|
||||
NoTty
|
||||
Report(#(ReportModuleName, List(GleeunitProgressOption)))
|
||||
ScaleTimeouts(Int)
|
||||
}
|
||||
|
||||
@external(erlang, "gleeunit_ffi", "run_eunit")
|
||||
fn run_eunit(a: List(Atom), b: List(EunitOption)) -> Result(Nil, a)
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
import gleam/dynamic
|
||||
|
||||
pub type GleamPanic {
|
||||
GleamPanic(
|
||||
message: String,
|
||||
file: String,
|
||||
module: String,
|
||||
function: String,
|
||||
line: Int,
|
||||
kind: PanicKind,
|
||||
)
|
||||
}
|
||||
|
||||
pub type PanicKind {
|
||||
Todo
|
||||
Panic
|
||||
LetAssert(
|
||||
start: Int,
|
||||
end: Int,
|
||||
pattern_start: Int,
|
||||
pattern_end: Int,
|
||||
value: dynamic.Dynamic,
|
||||
)
|
||||
Assert(start: Int, end: Int, expression_start: Int, kind: AssertKind)
|
||||
}
|
||||
|
||||
pub type AssertKind {
|
||||
BinaryOperator(
|
||||
operator: String,
|
||||
left: AssertedExpression,
|
||||
right: AssertedExpression,
|
||||
)
|
||||
FunctionCall(arguments: List(AssertedExpression))
|
||||
OtherExpression(expression: AssertedExpression)
|
||||
}
|
||||
|
||||
pub type AssertedExpression {
|
||||
AssertedExpression(start: Int, end: Int, kind: ExpressionKind)
|
||||
}
|
||||
|
||||
pub type ExpressionKind {
|
||||
Literal(value: dynamic.Dynamic)
|
||||
Expression(value: dynamic.Dynamic)
|
||||
Unevaluated
|
||||
}
|
||||
|
||||
@external(erlang, "gleeunit_gleam_panic_ffi", "from_dynamic")
|
||||
@external(javascript, "./gleeunit_gleam_panic_ffi.mjs", "from_dynamic")
|
||||
pub fn from_dynamic(data: dynamic.Dynamic) -> Result(GleamPanic, Nil)
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue