plum

#treesitter#compiler#wasm

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"