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
a271f34 1
module std
b7071c9 2
import std/ByteSlice
b7071c9 3
import std/Str
73b5e55 4
import std/Bool
ca5fd6f 5
import std/Number
a271f34 6
a271f34 7
# A Buffer is a growable, mutable sequence of bytes for efficiently building
a271f34 8
# up a `Str` piece by piece — modeled on Go's `bytes.Buffer`. Backed by a
a271f34 9
# `[]Byte` (`data`, its CAPACITY) plus a logical `len` (how many of those
a271f34 10
# bytes are actually in use) — `data` may have spare capacity past `len`,
a271f34 11
# doubled whenever a write would overflow it, so a sequence of `n` writes is
a271f34 12
# amortized O(n) overall (each `grow` only recopies existing bytes when
a271f34 13
# capacity actually runs out, not on every single write), matching the real
a271f34 14
# `bytes.Buffer`'s asymptotic behavior — unlike the previous `Str`-backed
a271f34 15
# version of this type, which recopied everything written so far on every
a271f34 16
# write.
5f2f962 17
enum Buffer(ToStr) =
5f2f962 18
  | Buffer(data: []Byte, len: Int)
a271f34 19
4a2384c 20
  # A no-param `init` nested in the type is `Buffer()`'s constructor — a
4a2384c 21
  # zero-arg `Buffer()` call desugars to `Buffer.init()` (see `plum-checker`'s
4a2384c 22
  # and `plum-wasm-codegen`'s `ClassCall` handling), so this is the only place
4a2384c 23
  # that ever needs to know `data`/`len`'s starting values.
4a2384c 24
  fun init() -> Buffer =
4a2384c 25
    Buffer(data: makeBytes(0), len: 0)
4a2384c 26
a271f34 27
  # Appends `s` to the buffer.
a271f34 28
  fun write(self, s: Str) -> Unit =
a271f34 29
    n := s.length()
a271f34 30
    self.ensureCapacity(self.len + n)
a271f34 31
    copyStrToBytes(self.data, self.len, s, 0, n)
a271f34 32
    self.len = self.len + n
a271f34 33
a271f34 34
  # Appends the single raw byte `b` to the buffer.
a271f34 35
  fun writeByte(self, b: Byte) -> Unit =
a271f34 36
    self.ensureCapacity(self.len + 1)
a271f34 37
    self.data.set(self.len, b)
a271f34 38
    self.len = self.len + 1
a271f34 39
a271f34 40
  # Appends the Unicode codepoint `cp`, UTF-8 encoded, to the buffer.
a271f34 41
  fun writeRune(self, cp: Int) -> Unit =
a271f34 42
    self.write(codePointToStr(cp))
a271f34 43
a271f34 44
  # Returns the buffer's contents as a `Str`.
a271f34 45
  fun toStr(self) -> Str =
a271f34 46
    bytesToStr(self.data, 0, self.len)
a271f34 47
a271f34 48
  # Number of bytes currently in the buffer.
a271f34 49
  fun length(self) -> Int =
a271f34 50
    self.len
a271f34 51
a271f34 52
  fun isEmpty(self) -> Bool =
a271f34 53
    self.len == 0
a271f34 54
a271f34 55
  # Discards the buffer's contents, leaving it empty. Keeps its current
a271f34 56
  # capacity — matching Go's `bytes.Buffer.Reset`, which reuses the backing
a271f34 57
  # array rather than releasing it.
a271f34 58
  fun reset(self) -> Unit =
a271f34 59
    self.len = 0
a271f34 60
a271f34 61
  # Grows `data` (doubling — or to exactly `needed` if even doubling isn't
a271f34 62
  # enough) whenever it's too small to hold `needed` bytes. A no-op
a271f34 63
  # otherwise, so a write that fits in the existing capacity costs no
a271f34 64
  # allocation or copy at all.
a271f34 65
  fun ensureCapacity(self, needed: Int) -> Unit =
a271f34 66
    cap := self.data.length()
a271f34 67
    if needed <= cap
a271f34 68
      return
a271f34 69
    new_cap := cap == 0 ? 16 : cap * 2
a271f34 70
    new_cap = needed > new_cap ? needed : new_cap
a271f34 71
    new_data := makeBytes(new_cap)
a271f34 72
    copyBytes(new_data, 0, self.data, 0, self.len)
a271f34 73
    self.data = new_data
a271f34 74
4a2384c 75
fun exerciseBufferInit() -> Str =
4a2384c 76
  b := Buffer()
4a2384c 77
  b.write("hello ")
4a2384c 78
  b.write("world")
4a2384c 79
  b.toStr()
4a2384c 80
4a2384c 81
test "Buffer() with no args runs init() and works immediately"
a9a0147 82
  assert exerciseBufferInit() == "hello world"