Packages
gleam_stdlib
0.35.1
1.0.3
1.0.2
1.0.1
1.0.0
0.71.0
0.70.0
0.69.0
0.68.1
0.68.0
0.67.1
0.67.0
0.65.0
0.64.0
0.63.2
0.63.1
0.63.0
0.62.1
0.62.0
0.61.0
0.60.0
0.59.0
0.58.0
0.57.0
0.56.0
0.55.0
0.54.0
0.53.0
0.52.0
0.51.0
0.50.0
0.49.0
0.48.0
0.47.0
0.46.0
0.45.0
0.44.0
0.43.0
0.42.0
0.41.0
0.40.0
0.39.0
0.38.0
0.37.0
0.36.0
0.35.1
0.35.0
0.34.0
0.33.1
0.33.0
0.32.1
0.32.0
0.31.0
0.30.2
0.30.1
0.30.0
0.29.2
0.29.1
0.29.0
0.28.2
0.28.1
0.28.0
0.27.0
0.26.1
0.26.0
0.25.0
0.24.0
0.23.0
0.22.3
0.22.2
0.22.1
0.22.0
0.21.0
0.20.0
0.19.3
0.19.2
0.19.1
0.19.0
0.18.1
0.18.0
0.18.0-rc1
0.17.1
0.17.0
0.16.0
0.15.0
0.14.0
0.13.0
0.12.0
0.11.0
0.10.1
0.10.0
0.9.0
0.8.0
0.7.0
0.6.0
0.5.0
0.4.0
0.4.0-rc1
0.3.1
0.3.0
0.2.0
retired
A standard library for the Gleam programming language
Current section
Files
Jump to
Current section
Files
src/gleam/bytes_builder.gleam
//// BytesBuilder is a type used for efficiently concatenating bytes together
//// without copying.
////
//// If we append one bit array to another the bit arrays must be copied to a
//// new location in memory so that they can sit together. This behaviour
//// enables efficient reading of the string but copying can be expensive,
//// especially if we want to join many bit arrays together.
////
//// BytesBuilder is different in that it can be joined together in constant
//// time using minimal memory, and then can be efficiently converted to a
//// bit array using the `to_bit_array` function.
////
//// Byte builders are always byte aligned, so that a number of bits that is not
//// divisible by 8 will be padded with 0s.
////
//// On Erlang this type is compatible with Erlang's iolists.
// TODO: pad bit arrays to byte boundaries when adding to a builder.
import gleam/string_builder.{type StringBuilder}
import gleam/list
import gleam/bit_array
pub opaque type BytesBuilder {
Bytes(BitArray)
Text(StringBuilder)
Many(List(BytesBuilder))
}
/// Create an empty `BytesBuilder`. Useful as the start of a pipe chaining many
/// builders together.
///
pub fn new() -> BytesBuilder {
concat([])
}
/// Prepends a bit array to the start of a builder.
///
/// Runs in constant time.
///
pub fn prepend(
to second: BytesBuilder,
prefix first: BitArray,
) -> BytesBuilder {
append_builder(from_bit_array(first), second)
}
/// Appends a bit array to the end of a builder.
///
/// Runs in constant time.
///
pub fn append(to first: BytesBuilder, suffix second: BitArray) -> BytesBuilder {
append_builder(first, from_bit_array(second))
}
/// Prepends a builder onto the start of another.
///
/// Runs in constant time.
///
pub fn prepend_builder(
to second: BytesBuilder,
prefix first: BytesBuilder,
) -> BytesBuilder {
append_builder(first, second)
}
/// Appends a builder onto the end of another.
///
/// Runs in constant time.
///
@external(erlang, "gleam_stdlib", "iodata_append")
pub fn append_builder(to first: BytesBuilder, suffix second: BytesBuilder) -> BytesBuilder {
case second {
Many(builders) -> Many([first, ..builders])
_ -> Many([first, second])
}
}
/// Prepends a string onto the start of a builder.
///
/// Runs in constant time when running on Erlang.
/// Runs in linear time with the length of the string otherwise.
///
pub fn prepend_string(
to second: BytesBuilder,
prefix first: String,
) -> BytesBuilder {
append_builder(from_string(first), second)
}
/// Appends a string onto the end of a builder.
///
/// Runs in constant time when running on Erlang.
/// Runs in linear time with the length of the string otherwise.
///
pub fn append_string(
to first: BytesBuilder,
suffix second: String,
) -> BytesBuilder {
append_builder(first, from_string(second))
}
/// Joins a list of builders into a single builder.
///
/// Runs in constant time.
///
@external(erlang, "gleam_stdlib", "identity")
pub fn concat(builders: List(BytesBuilder)) -> BytesBuilder {
Many(builders)
}
/// Joins a list of bit arrays into a single builder.
///
/// Runs in constant time.
///
@external(erlang, "gleam_stdlib", "identity")
pub fn concat_bit_arrays(bits: List(BitArray)) -> BytesBuilder {
bits
|> list.map(fn(b) { from_bit_array(b) })
|> concat()
}
/// Creates a new builder from a string.
///
/// Runs in constant time when running on Erlang.
/// Runs in linear time otherwise.
///
@external(erlang, "gleam_stdlib", "wrap_list")
pub fn from_string(string: String) -> BytesBuilder {
Text(string_builder.from_string(string))
}
/// Creates a new builder from a string builder.
///
/// Runs in constant time when running on Erlang.
/// Runs in linear time otherwise.
///
@external(erlang, "gleam_stdlib", "wrap_list")
pub fn from_string_builder(builder: StringBuilder) -> BytesBuilder {
Text(builder)
}
/// Creates a new builder from a bit array.
///
/// Runs in constant time.
///
@external(erlang, "gleam_stdlib", "wrap_list")
pub fn from_bit_array(bits: BitArray) -> BytesBuilder {
Bytes(bits)
}
/// Turns an builder into a bit array.
///
/// Runs in linear time.
///
/// When running on Erlang this function is implemented natively by the
/// virtual machine and is highly optimised.
///
@external(erlang, "erlang", "list_to_bitstring")
pub fn to_bit_array(builder: BytesBuilder) -> BitArray {
[[builder]]
|> to_list([])
|> list.reverse
|> bit_array.concat
}
fn to_list(
stack: List(List(BytesBuilder)),
acc: List(BitArray),
) -> List(BitArray) {
case stack {
[] -> acc
[[], ..remaining_stack] -> to_list(remaining_stack, acc)
[[Bytes(bits), ..rest], ..remaining_stack] ->
to_list([rest, ..remaining_stack], [bits, ..acc])
[[Text(builder), ..rest], ..remaining_stack] -> {
let bits = bit_array.from_string(string_builder.to_string(builder))
to_list([rest, ..remaining_stack], [bits, ..acc])
}
[[Many(builders), ..rest], ..remaining_stack] ->
to_list([builders, rest, ..remaining_stack], acc)
}
}
/// Returns the size of the builder's content in bytes.
///
/// Runs in linear time.
///
@external(erlang, "erlang", "iolist_size")
pub fn byte_size(builder: BytesBuilder) -> Int {
[[builder]]
|> to_list([])
|> list.fold(0, fn(acc, builder) { bit_array.byte_size(builder) + acc })
}