Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rename string and array builder to tree #737

Merged
merged 5 commits into from
Nov 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
`trim_right` functions, which have been deprecated.
- The `result.nil_error` function has been deprecated in favour of
`result.replace_error`.
- The `gleam/bytes_builder` module has been deprecated in favour of the
`gleam/bytes_tree` module.
- The `gleam/string_builder` module has been deprecated in favour of the
`gleam/string_tree` module.

## v0.41.0 - 2024-10-31

Expand Down
15 changes: 15 additions & 0 deletions src/gleam/bytes_builder.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import gleam/bit_array
import gleam/list
import gleam/string_builder.{type StringBuilder}

@deprecated("The `bytes_builder` module has been deprecated, use the `bytes_tree.BytesTree` type instead.")
pub opaque type BytesBuilder {
Bytes(BitArray)
Text(StringBuilder)
Expand All @@ -33,6 +34,7 @@ pub opaque type BytesBuilder {
/// Create an empty `BytesBuilder`. Useful as the start of a pipe chaining many
/// builders together.
///
@deprecated("The `bytes_builder` module has been deprecated, use `bytes_tree.new` instead.")
pub fn new() -> BytesBuilder {
concat([])
}
Expand All @@ -41,6 +43,7 @@ pub fn new() -> BytesBuilder {
///
/// Runs in constant time.
///
@deprecated("The `bytes_builder` module has been deprecated, use `bytes_tree.prepend` instead.")
pub fn prepend(to second: BytesBuilder, prefix first: BitArray) -> BytesBuilder {
append_builder(from_bit_array(first), second)
}
Expand All @@ -49,6 +52,7 @@ pub fn prepend(to second: BytesBuilder, prefix first: BitArray) -> BytesBuilder
///
/// Runs in constant time.
///
@deprecated("The `bytes_builder` module has been deprecated, use `bytes_tree.append` instead.")
pub fn append(to first: BytesBuilder, suffix second: BitArray) -> BytesBuilder {
append_builder(first, from_bit_array(second))
}
Expand All @@ -57,6 +61,7 @@ pub fn append(to first: BytesBuilder, suffix second: BitArray) -> BytesBuilder {
///
/// Runs in constant time.
///
@deprecated("The `bytes_builder` module has been deprecated, use `bytes_tree.prepend_tree` instead.")
pub fn prepend_builder(
to second: BytesBuilder,
prefix first: BytesBuilder,
Expand All @@ -68,6 +73,7 @@ pub fn prepend_builder(
///
/// Runs in constant time.
///
@deprecated("The `bytes_builder` module has been deprecated, use `bytes_tree.append_tree` instead.")
@external(erlang, "gleam_stdlib", "iodata_append")
pub fn append_builder(
to first: BytesBuilder,
Expand All @@ -84,6 +90,7 @@ pub fn append_builder(
/// Runs in constant time when running on Erlang.
/// Runs in linear time with the length of the string otherwise.
///
@deprecated("The `bytes_builder` module has been deprecated, use `bytes_tree.prepend_string` instead.")
pub fn prepend_string(
to second: BytesBuilder,
prefix first: String,
Expand All @@ -96,6 +103,7 @@ pub fn prepend_string(
/// Runs in constant time when running on Erlang.
/// Runs in linear time with the length of the string otherwise.
///
@deprecated("The `bytes_builder` module has been deprecated, use `bytes_tree.append_string` instead.")
pub fn append_string(
to first: BytesBuilder,
suffix second: String,
Expand All @@ -107,6 +115,7 @@ pub fn append_string(
///
/// Runs in constant time.
///
@deprecated("The `bytes_builder` module has been deprecated, use `bytes_tree.concat` instead.")
@external(erlang, "gleam_stdlib", "identity")
pub fn concat(builders: List(BytesBuilder)) -> BytesBuilder {
Many(builders)
Expand All @@ -116,6 +125,7 @@ pub fn concat(builders: List(BytesBuilder)) -> BytesBuilder {
///
/// Runs in constant time.
///
@deprecated("The `bytes_builder` module has been deprecated, use `bytes_tree.concat_bit_arrays` instead.")
@external(erlang, "gleam_stdlib", "identity")
pub fn concat_bit_arrays(bits: List(BitArray)) -> BytesBuilder {
bits
Expand All @@ -128,6 +138,7 @@ pub fn concat_bit_arrays(bits: List(BitArray)) -> BytesBuilder {
/// Runs in constant time when running on Erlang.
/// Runs in linear time otherwise.
///
@deprecated("The `bytes_builder` module has been deprecated, use `bytes_tree.from_string` instead.")
@external(erlang, "gleam_stdlib", "wrap_list")
pub fn from_string(string: String) -> BytesBuilder {
Text(string_builder.from_string(string))
Expand All @@ -138,6 +149,7 @@ pub fn from_string(string: String) -> BytesBuilder {
/// Runs in constant time when running on Erlang.
/// Runs in linear time otherwise.
///
@deprecated("The `bytes_builder` module has been deprecated, use `bytes_tree.from_string_tree` instead.")
@external(erlang, "gleam_stdlib", "wrap_list")
pub fn from_string_builder(builder: StringBuilder) -> BytesBuilder {
Text(builder)
Expand All @@ -147,6 +159,7 @@ pub fn from_string_builder(builder: StringBuilder) -> BytesBuilder {
///
/// Runs in constant time.
///
@deprecated("The `bytes_builder` module has been deprecated, use `bytes_tree.from_bit_array` instead.")
@external(erlang, "gleam_stdlib", "wrap_list")
pub fn from_bit_array(bits: BitArray) -> BytesBuilder {
Bytes(bits)
Expand All @@ -159,6 +172,7 @@ pub fn from_bit_array(bits: BitArray) -> BytesBuilder {
/// When running on Erlang this function is implemented natively by the
/// virtual machine and is highly optimised.
///
@deprecated("The `bytes_builder` module has been deprecated, use `bytes_tree.to_bit_array` instead.")
@external(erlang, "erlang", "list_to_bitstring")
pub fn to_bit_array(builder: BytesBuilder) -> BitArray {
[[builder]]
Expand Down Expand Up @@ -193,6 +207,7 @@ fn to_list(
///
/// Runs in linear time.
///
@deprecated("The `bytes_builder` module has been deprecated, use `bytes_tree.byte_size` instead.")
@external(erlang, "erlang", "iolist_size")
pub fn byte_size(builder: BytesBuilder) -> Int {
[[builder]]
Expand Down
186 changes: 186 additions & 0 deletions src/gleam/bytes_tree.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
//// `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.

// TODO: pad bit arrays to byte boundaries when adding to a tree.
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])
_ -> 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.
///
@external(erlang, "gleam_stdlib", "identity")
pub fn concat_bit_arrays(bits: List(BitArray)) -> BytesTree {
bits
|> list.map(fn(b) { from_bit_array(b) })
|> 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.
///
@external(erlang, "gleam_stdlib", "wrap_list")
pub fn from_bit_array(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 })
}
10 changes: 5 additions & 5 deletions src/gleam/dynamic.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import gleam/int
import gleam/list
import gleam/option.{type Option, Some}
import gleam/result
import gleam/string_builder
import gleam/string_tree

/// `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
Expand Down Expand Up @@ -498,8 +498,8 @@ fn at_least_decode_tuple_error(
}
let error =
["Tuple of at least ", int.to_string(size), " element", s]
|> string_builder.from_strings
|> string_builder.to_string
|> string_tree.from_strings
|> string_tree.to_string
|> DecodeError(found: classify(data), path: [])
Error([error])
}
Expand Down Expand Up @@ -567,8 +567,8 @@ fn push_path(error: DecodeError, name: t) -> DecodeError {
Ok(name) -> name
Error(_) ->
["<", classify(name), ">"]
|> string_builder.from_strings
|> string_builder.to_string
|> string_tree.from_strings
|> string_tree.to_string
}
DecodeError(..error, path: [name, ..error.path])
}
Expand Down
Loading