plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
plum-std/Buffer.plum
module std
import std/ByteSlice
import std/Str
import std/Bool
import std/Number
# A Buffer is a growable, mutable sequence of bytes for efficiently building
# up a `Str` piece by piece — modeled on Go's `bytes.Buffer`. Backed by a
# `[]Byte` (`data`, its CAPACITY) plus a logical `len` (how many of those
# bytes are actually in use) — `data` may have spare capacity past `len`,
# doubled whenever a write would overflow it, so a sequence of `n` writes is
# amortized O(n) overall (each `grow` only recopies existing bytes when
# capacity actually runs out, not on every single write), matching the real
# `bytes.Buffer`'s asymptotic behavior — unlike the previous `Str`-backed
# version of this type, which recopied everything written so far on every
# write.
enum Buffer(ToStr) =
| Buffer(data: []Byte, len: Int)
# A no-param `init` nested in the type is `Buffer()`'s constructor — a
# zero-arg `Buffer()` call desugars to `Buffer.init()` (see `plum-checker`'s
# and `plum-wasm-codegen`'s `ClassCall` handling), so this is the only place
# that ever needs to know `data`/`len`'s starting values.
fun init() -> Buffer =
Buffer(data: makeBytes(0), len: 0)
# Appends `s` to the buffer.
fun write(self, s: Str) -> Unit =
n := s.length()
self.ensureCapacity(self.len + n)
copyStrToBytes(self.data, self.len, s, 0, n)
self.len = self.len + n
# Appends the single raw byte `b` to the buffer.
fun writeByte(self, b: Byte) -> Unit =
self.ensureCapacity(self.len + 1)
self.data.set(self.len, b)
self.len = self.len + 1
# Appends the Unicode codepoint `cp`, UTF-8 encoded, to the buffer.
fun writeRune(self, cp: Int) -> Unit =
self.write(codePointToStr(cp))
# Returns the buffer's contents as a `Str`.
fun toStr(self) -> Str =
bytesToStr(self.data, 0, self.len)
# Number of bytes currently in the buffer.
fun length(self) -> Int =
self.len
fun isEmpty(self) -> Bool =
self.len == 0
# Discards the buffer's contents, leaving it empty. Keeps its current
# capacity — matching Go's `bytes.Buffer.Reset`, which reuses the backing
# array rather than releasing it.
fun reset(self) -> Unit =
self.len = 0
# Grows `data` (doubling — or to exactly `needed` if even doubling isn't
# enough) whenever it's too small to hold `needed` bytes. A no-op
# otherwise, so a write that fits in the existing capacity costs no
# allocation or copy at all.
fun ensureCapacity(self, needed: Int) -> Unit =
cap := self.data.length()
if needed <= cap
return
new_cap := cap == 0 ? 16 : cap * 2
new_cap = needed > new_cap ? needed : new_cap
new_data := makeBytes(new_cap)
copyBytes(new_data, 0, self.data, 0, self.len)
self.data = new_data
fun exerciseBufferInit() -> Str =
b := Buffer()
b.write("hello ")
b.write("world")
b.toStr()
test "Buffer() with no args runs init() and works immediately"
assert exerciseBufferInit() == "hello world"