aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLouis Pilfold <louis@lpil.uk>2023-10-19 22:22:51 +0100
committerLouis Pilfold <louis@lpil.uk>2023-10-26 12:24:40 +0100
commit0831802978afac21d7d3b397d83715e46799e30c (patch)
tree44b6b1c371c672450c56a005decb0bc66818e28c
parentb713e6fd185ad8b4da90f4b686aa9ecf0f60f0e4 (diff)
downloadgleam_stdlib-0831802978afac21d7d3b397d83715e46799e30c.tar.gz
gleam_stdlib-0831802978afac21d7d3b397d83715e46799e30c.zip
bytes_builder
-rw-r--r--CHANGELOG.md5
-rw-r--r--src/gleam/bit_builder.gleam238
-rw-r--r--src/gleam/bytes_builder.gleam188
-rw-r--r--test/gleam/bytes_builder_test.gleam94
4 files changed, 320 insertions, 205 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 90304d9..0dfdb6d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,11 @@
- The `map.filter` label `for` was renamed to `keeping`.
- The `iterator.filter` label `for` was renamed to `keeping`.
- The `list.filter` label `for` was renamed to `keeping`.
+- Updated for Gleam v0.32.0 syntax.
+- The `bit_string` module has been deprecated in favour of the new `bit_array`
+ module.
+- The `bit_builder` module has been deprecated in favour of the new
+ `bytes_builder` module.
- Improved performance of `string.to_graphemes` on JavaScript.
- The `iterator` module gains the `map2` function.
- The `list` module gains the `key_filter` function.
diff --git a/src/gleam/bit_builder.gleam b/src/gleam/bit_builder.gleam
index 319a823..ce6fe52 100644
--- a/src/gleam/bit_builder.gleam
+++ b/src/gleam/bit_builder.gleam
@@ -1,252 +1,80 @@
-//// BitBuilder is a type used for efficiently concatenating bits to create bit
-//// strings.
-////
-//// If we append one bit string to another the bit strings 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 strings together.
-////
-//// BitBuilder is different in that it can be joined together in constant
-//// time using minimal memory, and then can be efficiently converted to a
-//// bit string using the `to_bit_string` function.
-////
-//// On Erlang this type is compatible with Erlang's iolists.
+//// This module has been deprecated in favour of `gleam/bytes_builder`.
+import gleam/bytes_builder
import gleam/string_builder.{type StringBuilder}
-@target(javascript)
-import gleam/list
-@target(javascript)
-import gleam/bit_string
-
-@target(erlang)
-pub type BitBuilder
-
-@target(javascript)
-pub opaque type BitBuilder {
- Bits(BitArray)
- Text(StringBuilder)
- Many(List(BitBuilder))
-}
-/// Create an empty `BitBuilder`. Useful as the start of a pipe chaining many
-/// builders together.
-///
+pub type BitBuilder =
+ bytes_builder.BytesBuilder
+
+@deprecated("Please use the `gleam/bytes_builder` module instead.")
pub fn new() -> BitBuilder {
- do_concat([])
+ bytes_builder.new()
}
-/// Prepends a bit string to the start of a builder.
-///
-/// Runs in constant time.
-///
+@deprecated("Please use the `gleam/bytes_builder` module instead.")
pub fn prepend(to: BitBuilder, prefix: BitArray) -> BitBuilder {
- append_builder(from_bit_string(prefix), to)
+ bytes_builder.prepend(to, prefix)
}
-/// Appends a bit string to the end of a builder.
-///
-/// Runs in constant time.
-///
+@deprecated("Please use the `gleam/bytes_builder` module instead.")
pub fn append(to: BitBuilder, suffix: BitArray) -> BitBuilder {
- append_builder(to, from_bit_string(suffix))
+ bytes_builder.append(to, suffix)
}
-/// Prepends a builder onto the start of another.
-///
-/// Runs in constant time.
-///
+@deprecated("Please use the `gleam/bytes_builder` module instead.")
pub fn prepend_builder(to: BitBuilder, prefix: BitBuilder) -> BitBuilder {
- append_builder(prefix, to)
+ bytes_builder.prepend_builder(to, prefix)
}
-/// Appends a builder onto the end of another.
-///
-/// Runs in constant time.
-///
+@deprecated("Please use the `gleam/bytes_builder` module instead.")
pub fn append_builder(
to first: BitBuilder,
suffix second: BitBuilder,
) -> BitBuilder {
- do_append_builder(first, second)
-}
-
-@target(erlang)
-@external(erlang, "gleam_stdlib", "iodata_append")
-fn do_append_builder(to to: BitBuilder, suffix suffix: BitBuilder) -> BitBuilder
-
-@target(javascript)
-fn do_append_builder(first: BitBuilder, second: BitBuilder) -> BitBuilder {
- case second {
- Many(builders) -> Many([first, ..builders])
- _ -> Many([first, second])
- }
+ bytes_builder.append_builder(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.
-///
+@deprecated("Please use the `gleam/bytes_builder` module instead.")
pub fn prepend_string(to: BitBuilder, prefix: String) -> BitBuilder {
- append_builder(from_string(prefix), to)
+ bytes_builder.prepend_string(to, prefix)
}
-/// 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.
-///
+@deprecated("Please use the `gleam/bytes_builder` module instead.")
pub fn append_string(to: BitBuilder, suffix: String) -> BitBuilder {
- append_builder(to, from_string(suffix))
+ bytes_builder.append_string(to, suffix)
}
-/// Joins a list of builders into a single builder.
-///
-/// Runs in constant time.
-///
+@deprecated("Please use the `gleam/bytes_builder` module instead.")
pub fn concat(builders: List(BitBuilder)) -> BitBuilder {
- do_concat(builders)
-}
-
-@target(erlang)
-@external(erlang, "gleam_stdlib", "identity")
-fn do_concat(a: List(BitBuilder)) -> BitBuilder
-
-@target(javascript)
-fn do_concat(builders: List(BitBuilder)) -> BitBuilder {
- Many(builders)
+ bytes_builder.concat(builders)
}
-/// Joins a list of bit strings into a single builder.
-///
-/// Runs in constant time.
-///
+@deprecated("Please use the `gleam/bytes_builder` module instead.")
pub fn concat_bit_strings(bits: List(BitArray)) -> BitBuilder {
- do_concat_bit_strings(bits)
+ bytes_builder.concat_bit_arrays(bits)
}
-@target(erlang)
-@external(erlang, "gleam_stdlib", "identity")
-fn do_concat_bit_strings(a: List(BitArray)) -> BitBuilder
-
-@target(javascript)
-fn do_concat_bit_strings(bits: List(BitArray)) -> BitBuilder {
- bits
- |> list.map(fn(b) { from_bit_string(b) })
- |> concat()
-}
-
-/// Creates a new builder from a string.
-///
-/// Runs in constant time when running on Erlang.
-/// Runs in linear time otherwise.
-///
+@deprecated("Please use the `gleam/bytes_builder` module instead.")
pub fn from_string(string: String) -> BitBuilder {
- do_from_string(string)
-}
-
-@target(erlang)
-@external(erlang, "gleam_stdlib", "wrap_list")
-fn do_from_string(a: String) -> BitBuilder
-
-@target(javascript)
-fn do_from_string(string: String) -> BitBuilder {
- Text(string_builder.from_string(string))
+ bytes_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.
-///
+@deprecated("Please use the `gleam/bytes_builder` module instead.")
pub fn from_string_builder(builder: StringBuilder) -> BitBuilder {
- do_from_string_builder(builder)
-}
-
-@target(erlang)
-@external(erlang, "gleam_stdlib", "wrap_list")
-fn do_from_string_builder(a: StringBuilder) -> BitBuilder
-
-@target(javascript)
-fn do_from_string_builder(builder: StringBuilder) -> BitBuilder {
- Text(builder)
+ bytes_builder.from_string_builder(builder)
}
-/// Creates a new builder from a bit string.
-///
-/// Runs in constant time.
-///
+@deprecated("Please use the `gleam/bytes_builder` module instead.")
pub fn from_bit_string(bits: BitArray) -> BitBuilder {
- do_from_bit_string(bits)
-}
-
-@target(erlang)
-@external(erlang, "gleam_stdlib", "wrap_list")
-fn do_from_bit_string(a: BitArray) -> BitBuilder
-
-@target(javascript)
-fn do_from_bit_string(bits: BitArray) -> BitBuilder {
- Bits(bits)
+ bytes_builder.from_bit_array(bits)
}
-/// Turns an builder into a bit string.
-///
-/// Runs in linear time.
-///
-/// When running on Erlang this function is implemented natively by the
-/// virtual machine and is highly optimised.
-///
+@deprecated("Please use the `gleam/bytes_builder` module instead.")
pub fn to_bit_string(builder: BitBuilder) -> BitArray {
- do_to_bit_string(builder)
-}
-
-@target(erlang)
-@external(erlang, "erlang", "list_to_bitstring")
-fn do_to_bit_string(a: BitBuilder) -> BitArray
-
-@target(javascript)
-fn do_to_bit_string(builder: BitBuilder) -> BitArray {
- [[builder]]
- |> to_list([])
- |> list.reverse
- |> bit_string.concat
-}
-
-@target(javascript)
-fn to_list(stack: List(List(BitBuilder)), acc: List(BitArray)) -> List(BitArray) {
- case stack {
- [] -> acc
-
- [[], ..remaining_stack] -> to_list(remaining_stack, acc)
-
- [[Bits(bits), ..rest], ..remaining_stack] ->
- to_list([rest, ..remaining_stack], [bits, ..acc])
-
- [[Text(builder), ..rest], ..remaining_stack] -> {
- let bits = bit_string.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)
- }
+ bytes_builder.to_bit_array(builder)
}
-/// Returns the size of the builder's content in bytes.
-///
-/// Runs in linear time.
-///
+@deprecated("Please use the `gleam/bytes_builder` module instead.")
pub fn byte_size(builder: BitBuilder) -> Int {
- do_byte_size(builder)
-}
-
-@target(erlang)
-@external(erlang, "erlang", "iolist_size")
-fn do_byte_size(a: BitBuilder) -> Int
-
-@target(javascript)
-fn do_byte_size(builder: BitBuilder) -> Int {
- [[builder]]
- |> to_list([])
- |> list.fold(0, fn(acc, builder) { bit_string.byte_size(builder) + acc })
+ bytes_builder.byte_size(builder)
}
diff --git a/src/gleam/bytes_builder.gleam b/src/gleam/bytes_builder.gleam
new file mode 100644
index 0000000..da341ec
--- /dev/null
+++ b/src/gleam/bytes_builder.gleam
@@ -0,0 +1,188 @@
+//// 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: BytesBuilder, prefix: BitArray) -> BytesBuilder {
+ append_builder(from_bit_array(prefix), to)
+}
+
+/// Appends a bit array to the end of a builder.
+///
+/// Runs in constant time.
+///
+pub fn append(to: BytesBuilder, suffix: BitArray) -> BytesBuilder {
+ append_builder(to, from_bit_array(suffix))
+}
+
+/// Prepends a builder onto the start of another.
+///
+/// Runs in constant time.
+///
+pub fn prepend_builder(to: BytesBuilder, prefix: BytesBuilder) -> BytesBuilder {
+ append_builder(prefix, to)
+}
+
+/// 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: BytesBuilder, prefix: String) -> BytesBuilder {
+ append_builder(from_string(prefix), to)
+}
+
+/// 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: BytesBuilder, suffix: String) -> BytesBuilder {
+ append_builder(to, from_string(suffix))
+}
+
+/// 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 })
+}
diff --git a/test/gleam/bytes_builder_test.gleam b/test/gleam/bytes_builder_test.gleam
new file mode 100644
index 0000000..acea7ab
--- /dev/null
+++ b/test/gleam/bytes_builder_test.gleam
@@ -0,0 +1,94 @@
+import gleam/bytes_builder
+import gleam/string_builder
+import gleam/should
+
+pub fn builder_test() {
+ let data =
+ bytes_builder.from_bit_array(<<1>>)
+ |> bytes_builder.append(<<2>>)
+ |> bytes_builder.append(<<3>>)
+ |> bytes_builder.prepend(<<0>>)
+
+ data
+ |> bytes_builder.to_bit_array
+ |> should.equal(<<0, 1, 2, 3>>)
+
+ data
+ |> bytes_builder.byte_size
+ |> should.equal(4)
+}
+
+pub fn builder_with_strings_test() {
+ let data =
+ bytes_builder.from_bit_array(<<1>>)
+ |> bytes_builder.append_string("2")
+ |> bytes_builder.append_string("3")
+ |> bytes_builder.prepend_string("0")
+
+ data
+ |> bytes_builder.to_bit_array
+ |> should.equal(<<"0":utf8, 1, "2":utf8, "3":utf8>>)
+
+ data
+ |> bytes_builder.byte_size
+ |> should.equal(4)
+}
+
+pub fn builder_with_builders_test() {
+ let data =
+ bytes_builder.from_bit_array(<<1>>)
+ |> bytes_builder.append_builder(bytes_builder.from_bit_array(<<2>>))
+ |> bytes_builder.append_builder(bytes_builder.from_bit_array(<<3>>))
+ |> bytes_builder.prepend_builder(bytes_builder.from_bit_array(<<0>>))
+
+ data
+ |> bytes_builder.to_bit_array
+ |> should.equal(<<0, 1, 2, 3>>)
+
+ data
+ |> bytes_builder.byte_size
+ |> should.equal(4)
+}
+
+pub fn concat_test() {
+ [
+ bytes_builder.from_bit_array(<<1, 2>>),
+ bytes_builder.from_bit_array(<<3, 4>>),
+ bytes_builder.from_bit_array(<<5, 6>>),
+ ]
+ |> bytes_builder.concat
+ |> bytes_builder.to_bit_array
+ |> should.equal(<<1, 2, 3, 4, 5, 6>>)
+}
+
+pub fn concat_bit_strings_test() {
+ bytes_builder.concat_bit_arrays([<<"h":utf8>>, <<"e":utf8>>, <<"y":utf8>>])
+ |> bytes_builder.to_bit_array
+ |> should.equal(<<"hey":utf8>>)
+}
+
+pub fn from_bit_array() {
+ // Regression test: no additional modification of the builder
+ bytes_builder.from_bit_array(<<>>)
+ |> bytes_builder.to_bit_array
+ |> should.equal(<<>>)
+}
+
+pub fn from_string_test() {
+ // Regression test: no additional modification of the builder
+ bytes_builder.from_string("")
+ |> bytes_builder.to_bit_array
+ |> should.equal(<<>>)
+}
+
+pub fn new_test() {
+ bytes_builder.new()
+ |> bytes_builder.to_bit_array
+ |> should.equal(<<>>)
+}
+
+pub fn from_string_builder_test() {
+ string_builder.from_string("hello")
+ |> bytes_builder.from_string_builder
+ |> should.equal(bytes_builder.from_string("hello"))
+}