723 lines
16 KiB
JavaScript
723 lines
16 KiB
JavaScript
import {
|
||
Ok,
|
||
Error,
|
||
Empty as $Empty,
|
||
prepend as listPrepend,
|
||
CustomType as $CustomType,
|
||
remainderInt,
|
||
divideInt,
|
||
} from "../gleam.mjs";
|
||
import * as $list from "../gleam/list.mjs";
|
||
import * as $option from "../gleam/option.mjs";
|
||
import { None, Some } from "../gleam/option.mjs";
|
||
import * as $order from "../gleam/order.mjs";
|
||
import * as $string_tree from "../gleam/string_tree.mjs";
|
||
import {
|
||
string_length as length,
|
||
lowercase,
|
||
uppercase,
|
||
less_than,
|
||
string_grapheme_slice as grapheme_slice,
|
||
string_byte_slice as unsafe_byte_slice,
|
||
crop_string as crop,
|
||
contains_string as contains,
|
||
starts_with,
|
||
ends_with,
|
||
split_once,
|
||
trim_start,
|
||
trim_end,
|
||
pop_grapheme,
|
||
graphemes as to_graphemes,
|
||
codepoint as unsafe_int_to_utf_codepoint,
|
||
string_to_codepoint_integer_list,
|
||
utf_codepoint_list_to_string as from_utf_codepoints,
|
||
utf_codepoint_to_int,
|
||
inspect as do_inspect,
|
||
byte_size,
|
||
} from "../gleam_stdlib.mjs";
|
||
|
||
export {
|
||
byte_size,
|
||
contains,
|
||
crop,
|
||
ends_with,
|
||
from_utf_codepoints,
|
||
length,
|
||
lowercase,
|
||
pop_grapheme,
|
||
split_once,
|
||
starts_with,
|
||
to_graphemes,
|
||
trim_end,
|
||
trim_start,
|
||
uppercase,
|
||
utf_codepoint_to_int,
|
||
};
|
||
|
||
class Leading extends $CustomType {}
|
||
|
||
class Trailing extends $CustomType {}
|
||
|
||
/**
|
||
* Determines if a `String` is empty.
|
||
*
|
||
* ## Examples
|
||
*
|
||
* ```gleam
|
||
* is_empty("")
|
||
* // -> True
|
||
* ```
|
||
*
|
||
* ```gleam
|
||
* is_empty("the world")
|
||
* // -> False
|
||
* ```
|
||
*/
|
||
export function is_empty(str) {
|
||
return str === "";
|
||
}
|
||
|
||
/**
|
||
* 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"
|
||
* ```
|
||
*/
|
||
export function reverse(string) {
|
||
let _pipe = string;
|
||
let _pipe$1 = $string_tree.from_string(_pipe);
|
||
let _pipe$2 = $string_tree.reverse(_pipe$1);
|
||
return $string_tree.to_string(_pipe$2);
|
||
}
|
||
|
||
/**
|
||
* 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"
|
||
* ```
|
||
*/
|
||
export function replace(string, pattern, substitute) {
|
||
let _pipe = string;
|
||
let _pipe$1 = $string_tree.from_string(_pipe);
|
||
let _pipe$2 = $string_tree.replace(_pipe$1, pattern, substitute);
|
||
return $string_tree.to_string(_pipe$2);
|
||
}
|
||
|
||
/**
|
||
* 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
|
||
* ```
|
||
*/
|
||
export function compare(a, b) {
|
||
let $ = a === b;
|
||
if ($) {
|
||
return new $order.Eq();
|
||
} else {
|
||
let $1 = less_than(a, b);
|
||
if ($1) {
|
||
return new $order.Lt();
|
||
} else {
|
||
return new $order.Gt();
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 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)
|
||
* // -> ""
|
||
* ```
|
||
*/
|
||
export function slice(string, idx, len) {
|
||
let $ = len <= 0;
|
||
if ($) {
|
||
return "";
|
||
} else {
|
||
let $1 = idx < 0;
|
||
if ($1) {
|
||
let translated_idx = length(string) + idx;
|
||
let $2 = translated_idx < 0;
|
||
if ($2) {
|
||
return "";
|
||
} else {
|
||
return grapheme_slice(string, translated_idx, len);
|
||
}
|
||
} else {
|
||
return grapheme_slice(string, idx, len);
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 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"
|
||
* ```
|
||
*/
|
||
export function drop_end(string, num_graphemes) {
|
||
let $ = num_graphemes <= 0;
|
||
if ($) {
|
||
return string;
|
||
} else {
|
||
return slice(string, 0, length(string) - num_graphemes);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 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"
|
||
* ```
|
||
*/
|
||
export function append(first, second) {
|
||
return first + second;
|
||
}
|
||
|
||
function concat_loop(loop$strings, loop$accumulator) {
|
||
while (true) {
|
||
let strings = loop$strings;
|
||
let accumulator = loop$accumulator;
|
||
if (strings instanceof $Empty) {
|
||
return accumulator;
|
||
} else {
|
||
let string = strings.head;
|
||
let strings$1 = strings.tail;
|
||
loop$strings = strings$1;
|
||
loop$accumulator = accumulator + string;
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 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"
|
||
* ```
|
||
*/
|
||
export function concat(strings) {
|
||
return concat_loop(strings, "");
|
||
}
|
||
|
||
function repeat_loop(loop$times, loop$doubling_acc, loop$acc) {
|
||
while (true) {
|
||
let times = loop$times;
|
||
let doubling_acc = loop$doubling_acc;
|
||
let acc = loop$acc;
|
||
let _block;
|
||
let $ = times % 2;
|
||
if ($ === 0) {
|
||
_block = acc;
|
||
} else {
|
||
_block = acc + doubling_acc;
|
||
}
|
||
let acc$1 = _block;
|
||
let times$1 = globalThis.Math.trunc(times / 2);
|
||
let $1 = times$1 <= 0;
|
||
if ($1) {
|
||
return acc$1;
|
||
} else {
|
||
loop$times = times$1;
|
||
loop$doubling_acc = doubling_acc + doubling_acc;
|
||
loop$acc = acc$1;
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 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"
|
||
* ```
|
||
*/
|
||
export function repeat(string, times) {
|
||
let $ = times <= 0;
|
||
if ($) {
|
||
return "";
|
||
} else {
|
||
return repeat_loop(times, string, "");
|
||
}
|
||
}
|
||
|
||
function join_loop(loop$strings, loop$separator, loop$accumulator) {
|
||
while (true) {
|
||
let strings = loop$strings;
|
||
let separator = loop$separator;
|
||
let accumulator = loop$accumulator;
|
||
if (strings instanceof $Empty) {
|
||
return accumulator;
|
||
} else {
|
||
let string = strings.head;
|
||
let strings$1 = strings.tail;
|
||
loop$strings = strings$1;
|
||
loop$separator = separator;
|
||
loop$accumulator = (accumulator + separator) + string;
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 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"
|
||
* ```
|
||
*/
|
||
export function join(strings, separator) {
|
||
if (strings instanceof $Empty) {
|
||
return "";
|
||
} else {
|
||
let first$1 = strings.head;
|
||
let rest = strings.tail;
|
||
return join_loop(rest, separator, first$1);
|
||
}
|
||
}
|
||
|
||
function padding(size, pad_string) {
|
||
let pad_string_length = length(pad_string);
|
||
let num_pads = divideInt(size, pad_string_length);
|
||
let extra = remainderInt(size, pad_string_length);
|
||
return repeat(pad_string, num_pads) + slice(pad_string, 0, extra);
|
||
}
|
||
|
||
/**
|
||
* 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"
|
||
* ```
|
||
*/
|
||
export function pad_start(string, desired_length, pad_string) {
|
||
let current_length = length(string);
|
||
let to_pad_length = desired_length - current_length;
|
||
let $ = to_pad_length <= 0;
|
||
if ($) {
|
||
return string;
|
||
} else {
|
||
return 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"
|
||
* ```
|
||
*/
|
||
export function pad_end(string, desired_length, pad_string) {
|
||
let current_length = length(string);
|
||
let to_pad_length = desired_length - current_length;
|
||
let $ = to_pad_length <= 0;
|
||
if ($) {
|
||
return string;
|
||
} else {
|
||
return string + padding(to_pad_length, pad_string);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 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"
|
||
* ```
|
||
*/
|
||
export function trim(string) {
|
||
let _pipe = string;
|
||
let _pipe$1 = trim_start(_pipe);
|
||
return trim_end(_pipe$1);
|
||
}
|
||
|
||
function to_graphemes_loop(loop$string, loop$acc) {
|
||
while (true) {
|
||
let string = loop$string;
|
||
let acc = loop$acc;
|
||
let $ = pop_grapheme(string);
|
||
if ($ instanceof Ok) {
|
||
let grapheme = $[0][0];
|
||
let rest = $[0][1];
|
||
loop$string = rest;
|
||
loop$acc = listPrepend(grapheme, acc);
|
||
} else {
|
||
return acc;
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 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", ""]
|
||
* ```
|
||
*/
|
||
export function split(x, substring) {
|
||
if (substring === "") {
|
||
return to_graphemes(x);
|
||
} else {
|
||
let _pipe = x;
|
||
let _pipe$1 = $string_tree.from_string(_pipe);
|
||
let _pipe$2 = $string_tree.split(_pipe$1, substring);
|
||
return $list.map(_pipe$2, $string_tree.to_string);
|
||
}
|
||
}
|
||
|
||
function do_to_utf_codepoints(string) {
|
||
let _pipe = string;
|
||
let _pipe$1 = string_to_codepoint_integer_list(_pipe);
|
||
return $list.map(_pipe$1, unsafe_int_to_utf_codepoint);
|
||
}
|
||
|
||
/**
|
||
* 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),
|
||
* // ]
|
||
* ```
|
||
*/
|
||
export function to_utf_codepoints(string) {
|
||
return do_to_utf_codepoints(string);
|
||
}
|
||
|
||
/**
|
||
* Converts an integer to a `UtfCodepoint`.
|
||
*
|
||
* Returns an `Error` if the integer does not represent a valid UTF codepoint.
|
||
*/
|
||
export function utf_codepoint(value) {
|
||
let i = value;
|
||
if (i > 1_114_111) {
|
||
return new Error(undefined);
|
||
} else {
|
||
let i$1 = value;
|
||
if ((i$1 >= 55_296) && (i$1 <= 57_343)) {
|
||
return new Error(undefined);
|
||
} else {
|
||
let i$2 = value;
|
||
if (i$2 < 0) {
|
||
return new Error(undefined);
|
||
} else {
|
||
let i$3 = value;
|
||
return new Ok(unsafe_int_to_utf_codepoint(i$3));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Converts a `String` into `Option(String)` where an empty `String` becomes
|
||
* `None`.
|
||
*
|
||
* ## Examples
|
||
*
|
||
* ```gleam
|
||
* to_option("")
|
||
* // -> None
|
||
* ```
|
||
*
|
||
* ```gleam
|
||
* to_option("hats")
|
||
* // -> Some("hats")
|
||
* ```
|
||
*/
|
||
export function to_option(string) {
|
||
if (string === "") {
|
||
return new None();
|
||
} else {
|
||
return new 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")
|
||
* ```
|
||
*/
|
||
export function first(string) {
|
||
let $ = pop_grapheme(string);
|
||
if ($ instanceof Ok) {
|
||
let first$1 = $[0][0];
|
||
return new Ok(first$1);
|
||
} else {
|
||
return $;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 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")
|
||
* ```
|
||
*/
|
||
export function last(string) {
|
||
let $ = pop_grapheme(string);
|
||
if ($ instanceof Ok) {
|
||
let $1 = $[0][1];
|
||
if ($1 === "") {
|
||
let first$1 = $[0][0];
|
||
return new Ok(first$1);
|
||
} else {
|
||
let rest = $1;
|
||
return new Ok(slice(rest, -1, 1));
|
||
}
|
||
} else {
|
||
return $;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 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"
|
||
* ```
|
||
*/
|
||
export function capitalise(string) {
|
||
let $ = pop_grapheme(string);
|
||
if ($ instanceof Ok) {
|
||
let first$1 = $[0][0];
|
||
let rest = $[0][1];
|
||
return append(uppercase(first$1), lowercase(rest));
|
||
} else {
|
||
return "";
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 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.
|
||
*/
|
||
export function inspect(term) {
|
||
let _pipe = term;
|
||
let _pipe$1 = do_inspect(_pipe);
|
||
return $string_tree.to_string(_pipe$1);
|
||
}
|
||
|
||
/**
|
||
* 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"
|
||
* ```
|
||
*/
|
||
export function drop_start(string, num_graphemes) {
|
||
let $ = num_graphemes <= 0;
|
||
if ($) {
|
||
return string;
|
||
} else {
|
||
let prefix = grapheme_slice(string, 0, num_graphemes);
|
||
let prefix_size = byte_size(prefix);
|
||
return unsafe_byte_slice(
|
||
string,
|
||
prefix_size,
|
||
byte_size(string) - prefix_size,
|
||
);
|
||
}
|
||
}
|