plum
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" |