plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
plum-std/List.plum
| 0e69b5b | 1 | module std |
| 0e69b5b | 2 | |
| b7071c9 | 3 | import std/Option |
| b7071c9 | 4 | import std/Buffer |
| ca5fd6f | 5 | import std/Number |
| 73b5e55 | 6 | import std/Bool |
| 29313cb | 7 | import std/Str |
| 4670182 | 8 | |
| 141de54 | 9 | # A node stores the data in a list and contains pointers to the previous and next sibling nodes |
| 5f2f962 | 10 | enum Node[T] = |
| 5f2f962 | 11 | | Node(value: T, prev: Option[Node[T]], next: Option[Node[T]]) |
| fd085d9 | 12 | |
| 141de54 | 13 | # A list is a data structure describing a contiguous section of an array stored separately from the slice variable itself. |
| 141de54 | 14 | # It contains the pointers to the start and end nodes (head, tail) and maintains the size as well |
| 5f2f962 | 15 | enum List[T: ToStr](ToStr) = |
| 5f2f962 | 16 | | List(head: Option[Node[T]], tail: Option[Node[T]], size: Int) |
| fd085d9 | 17 | |
| 4a2384c | 18 | # `List[T](1, 2, 3)` (or, for an empty list, `List[T]()` — explicit |
| 4a2384c | 19 | # generics required either way, since nothing else in a call this shape |
| 4a2384c | 20 | # pins down `T`) constructs a list via this variadic `init` — see |
| 4a2384c | 21 | # `plum-checker`/`plum-wasm-codegen`'s `ClassCall`/`FnCall` handling, which |
| 4a2384c | 22 | # desugars both call shapes to `List.init(...)`. |
| 4a2384c | 23 | fun init(values: ...T) -> List[T] = |
| 4a2384c | 24 | l := List(head: None, tail: None, size: 0) |
| 4a2384c | 25 | for v := range values |
| 4a2384c | 26 | l.add(v) |
| 4a2384c | 27 | return l |
| 4a2384c | 28 | |
| 58b01fc | 29 | # gets the element at i'th index of the list |
| 58b01fc | 30 | fun get(self, i: Int) -> Option[T] = |
| a271f34 | 31 | current := self.head |
| a271f34 | 32 | index := 0 |
| 58b01fc | 33 | while current != None |
| 58b01fc | 34 | match current |
| 3a2119e | 35 | Some(Node(value, _, next)) => |
| 58b01fc | 36 | if index == i |
| 3a2119e | 37 | return Some(value) |
| 3a2119e | 38 | current = next |
| 58b01fc | 39 | index = index + 1 |
| 58b01fc | 40 | None => |
| 58b01fc | 41 | break |
| 58b01fc | 42 | None |
| 58b01fc | 43 | |
| 02b3582 | 44 | # sets the element at i'th index of the list, returning the old value (or None if i is out of bounds) |
| 58b01fc | 45 | fun set(self, i: Int, v: T) -> Option[T] = |
| a271f34 | 46 | current := self.head |
| a271f34 | 47 | index := 0 |
| 02b3582 | 48 | while current != None |
| 02b3582 | 49 | match current |
| 02b3582 | 50 | Some(node) => |
| 02b3582 | 51 | if index == i |
| a271f34 | 52 | old := node.value |
| 02b3582 | 53 | node.value = v |
| 02b3582 | 54 | return Some(old) |
| 02b3582 | 55 | current = node.next |
| 02b3582 | 56 | index = index + 1 |
| 02b3582 | 57 | None => |
| 02b3582 | 58 | break |
| 02b3582 | 59 | None |
| 58b01fc | 60 | |
| 58b01fc | 61 | # returns the no of elements in the list |
| 58b01fc | 62 | fun length(self) -> Int = |
| 58b01fc | 63 | self.size |
| 58b01fc | 64 | |
| 2ec05c8 | 65 | # Elementwise comparison via `==` — see the caveat on `expectEq` in |
| 2ec05c8 | 66 | # `libs/std/testing.plum` for what that means when `T` is a class/enum |
| 2ec05c8 | 67 | # without its own `equals` (reference identity, not structural equality). |
| 2ec05c8 | 68 | fun equals(self, other: List[T]) -> Bool = |
| 2ec05c8 | 69 | if self.length() != other.length() |
| 2ec05c8 | 70 | return False |
| 2ec05c8 | 71 | for i := range self.length() |
| 2ec05c8 | 72 | if self.get(i).unwrap() != other.get(i).unwrap() |
| 2ec05c8 | 73 | return False |
| 2ec05c8 | 74 | return True |
| 2ec05c8 | 75 | |
| 02b3582 | 76 | # adds the specified elements to the end of the list |
| 58b01fc | 77 | fun add(self, values: ...T) = |
| a271f34 | 78 | for v := range values |
| a271f34 | 79 | node := Node(value: v, prev: self.tail, next: None) |
| 02b3582 | 80 | match self.tail |
| 02b3582 | 81 | Some(t) => |
| 02b3582 | 82 | t.next = Some(node) |
| 02b3582 | 83 | None => |
| 02b3582 | 84 | self.head = Some(node) |
| 02b3582 | 85 | self.tail = Some(node) |
| 02b3582 | 86 | self.size = self.size + 1 |
| 02b3582 | 87 | |
| 02b3582 | 88 | # unlinks node from the list, patching its neighbors (or head/tail) to close the gap |
| a271f34 | 89 | fun unlink(self, node: Node[T]) = |
| 02b3582 | 90 | match node.prev |
| 02b3582 | 91 | Some(p) => |
| 02b3582 | 92 | p.next = node.next |
| 02b3582 | 93 | None => |
| 02b3582 | 94 | self.head = node.next |
| 02b3582 | 95 | match node.next |
| 02b3582 | 96 | Some(n) => |
| 02b3582 | 97 | n.prev = node.prev |
| 02b3582 | 98 | None => |
| 02b3582 | 99 | self.tail = node.prev |
| 02b3582 | 100 | self.size = self.size - 1 |
| 58b01fc | 101 | |
| 58b01fc | 102 | # removes the element at i'th index of the list |
| 58b01fc | 103 | fun removeAt(self, i: Int) = |
| a271f34 | 104 | current := self.head |
| a271f34 | 105 | index := 0 |
| 02b3582 | 106 | while current != None |
| 02b3582 | 107 | match current |
| 02b3582 | 108 | Some(node) => |
| 02b3582 | 109 | if index == i |
| 02b3582 | 110 | self.unlink(node) |
| 02b3582 | 111 | return |
| 02b3582 | 112 | current = node.next |
| 02b3582 | 113 | index = index + 1 |
| 02b3582 | 114 | None => |
| 02b3582 | 115 | break |
| 58b01fc | 116 | |
| 02b3582 | 117 | # removes the first element equal to v from list |
| 58b01fc | 118 | fun remove(self, v: T) = |
| a271f34 | 119 | current := self.head |
| 02b3582 | 120 | while current != None |
| 02b3582 | 121 | match current |
| 02b3582 | 122 | Some(node) => |
| 02b3582 | 123 | if node.value == v |
| 02b3582 | 124 | self.unlink(node) |
| 02b3582 | 125 | return |
| 02b3582 | 126 | current = node.next |
| 02b3582 | 127 | None => |
| 02b3582 | 128 | break |
| 58b01fc | 129 | |
| 58b01fc | 130 | # removes all objects from this list |
| 58b01fc | 131 | fun clear(self) = |
| 02b3582 | 132 | self.head = None |
| 02b3582 | 133 | self.tail = None |
| 02b3582 | 134 | self.size = 0 |
| 58b01fc | 135 | |
| 02b3582 | 136 | # returns the list with the elements relinked in reverse order. |
| 02b3582 | 137 | fun reverse(self) -> List = |
| a271f34 | 138 | current := self.head |
| 02b3582 | 139 | while current != None |
| 02b3582 | 140 | match current |
| 02b3582 | 141 | Some(node) => |
| a271f34 | 142 | next := node.next |
| 02b3582 | 143 | node.next = node.prev |
| 02b3582 | 144 | node.prev = next |
| 02b3582 | 145 | current = next |
| 02b3582 | 146 | None => |
| 02b3582 | 147 | break |
| a271f34 | 148 | old_head := self.head |
| 02b3582 | 149 | self.head = self.tail |
| a271f34 | 150 | self.tail = old_head |
| 02b3582 | 151 | self |
| 58b01fc | 152 | |
| a271f34 | 153 | # Inserts `value` immediately before `node` (which must belong to `self`), |
| a271f34 | 154 | # patching neighbors (or `head`) to make room — the insertion-point half of |
| a271f34 | 155 | # `sort`'s pointer surgery, split out since `unlink`'s counterpart already |
| a271f34 | 156 | # gets its own method. |
| a271f34 | 157 | fun insertBefore(self, node: Node[T], value: T) -> Unit = |
| a271f34 | 158 | new_node := Node(value: value, prev: node.prev, next: Some(node)) |
| a271f34 | 159 | match node.prev |
| a271f34 | 160 | Some(p) => |
| a271f34 | 161 | p.next = Some(new_node) |
| a271f34 | 162 | None => |
| a271f34 | 163 | self.head = Some(new_node) |
| a271f34 | 164 | node.prev = Some(new_node) |
| a271f34 | 165 | self.size = self.size + 1 |
| a271f34 | 166 | |
| a271f34 | 167 | # Inserts `value` into `self` (assumed already sorted by `before`) at the |
| a271f34 | 168 | # position that keeps it sorted — the first position whose existing element |
| a271f34 | 169 | # `before(value, existing)` holds for, or the end if none does. |
| a271f34 | 170 | fun insertSorted(self, value: T, before: fn(T, T) -> Bool) -> Unit = |
| a271f34 | 171 | current := self.head |
| a271f34 | 172 | while current != None |
| a271f34 | 173 | match current |
| a271f34 | 174 | Some(node) => |
| a271f34 | 175 | if before(value, node.value) |
| a271f34 | 176 | self.insertBefore(node, value) |
| a271f34 | 177 | return |
| a271f34 | 178 | current = node.next |
| a271f34 | 179 | None => |
| a271f34 | 180 | break |
| a271f34 | 181 | self.add(value) |
| a271f34 | 182 | |
| a271f34 | 183 | # Returns a NEW list with `self`'s elements sorted, using `before(a, b)` as |
| a271f34 | 184 | # the "does a belong before b" comparator. Insertion sort (O(n^2)) — simple |
| a271f34 | 185 | # and adequate for the list sizes a linked list is already suited to. |
| a271f34 | 186 | fun sort(self, before: fn(T, T) -> Bool) -> List[T] = |
| a271f34 | 187 | result := List(head: None, tail: None, size: 0) |
| a271f34 | 188 | current := self.head |
| a271f34 | 189 | while current != None |
| a271f34 | 190 | match current |
| 3a2119e | 191 | Some(Node(value, _, next)) => |
| 3a2119e | 192 | result.insertSorted(value, before) |
| 3a2119e | 193 | current = next |
| a271f34 | 194 | None => |
| a271f34 | 195 | break |
| a271f34 | 196 | return result |
| 58b01fc | 197 | |
| a271f34 | 198 | # Returns the first element equal to `search`, or `None`. |
| 58b01fc | 199 | fun find(self, search: T) -> Option[T] = |
| a271f34 | 200 | current := self.head |
| a271f34 | 201 | while current != None |
| a271f34 | 202 | match current |
| 3a2119e | 203 | Some(Node(value, _, next)) => |
| 3a2119e | 204 | if value == search |
| 3a2119e | 205 | return Some(value) |
| 3a2119e | 206 | current = next |
| a271f34 | 207 | None => |
| a271f34 | 208 | break |
| a271f34 | 209 | return None |
| 58b01fc | 210 | |
| 58b01fc | 211 | fun contains(self, v: T) -> Bool = |
| a271f34 | 212 | return self.find(v) != None |
| 58b01fc | 213 | |
| 58b01fc | 214 | # calls f for each elem in the list |
| 58b01fc | 215 | fun each(self, cb: fn(T)) -> Unit = |
| a271f34 | 216 | current := self.head |
| 58b01fc | 217 | while current != None |
| 58b01fc | 218 | match current |
| 3a2119e | 219 | Some(Node(value, _, next)) => |
| 3a2119e | 220 | cb(value) |
| 3a2119e | 221 | current = next |
| 58b01fc | 222 | None => |
| 58b01fc | 223 | break |
| 58b01fc | 224 | |
| a271f34 | 225 | # Returns a new list with each element transformed by `cb`. |
| a271f34 | 226 | # |
| a271f34 | 227 | # `U` here is a generic param of `map` ITSELF, separate from `List[T]`'s own |
| a271f34 | 228 | # `T` — resolved per CALL SITE (from `cb`'s own inferred return type), not |
| a271f34 | 229 | # when `List[T]` itself is specialized, via `resolveMethodOwnGenerics` in |
| a271f34 | 230 | # `plum-checker/src/monomorphize.rs`. |
| 58b01fc | 231 | fun map(self, cb: fn(T) -> U) -> List[U] = |
| a271f34 | 232 | result := List(head: None, tail: None, size: 0) |
| a271f34 | 233 | current := self.head |
| 58b01fc | 234 | while current != None |
| 58b01fc | 235 | match current |
| 3a2119e | 236 | Some(Node(value, _, next)) => |
| 3a2119e | 237 | result.add(cb(value)) |
| 3a2119e | 238 | current = next |
| 58b01fc | 239 | None => |
| 58b01fc | 240 | break |
| a271f34 | 241 | return result |
| 58b01fc | 242 | |
| 58b01fc | 243 | # returns a new list with each element flat-mapped |
| 58b01fc | 244 | fun flatMap(self) = |
| 58b01fc | 245 | todo |
| 58b01fc | 246 | |
| a271f34 | 247 | # Returns a new list with only the elements `predicate` holds for. |
| a271f34 | 248 | fun retain(self, predicate: fn(T) -> Bool) -> List[T] = |
| a271f34 | 249 | result := List(head: None, tail: None, size: 0) |
| a271f34 | 250 | current := self.head |
| a271f34 | 251 | while current != None |
| a271f34 | 252 | match current |
| 3a2119e | 253 | Some(Node(value, _, next)) => |
| 3a2119e | 254 | if predicate(value) |
| 3a2119e | 255 | result.add(value) |
| 3a2119e | 256 | current = next |
| a271f34 | 257 | None => |
| a271f34 | 258 | break |
| a271f34 | 259 | return result |
| 58b01fc | 260 | |
| a271f34 | 261 | # Returns a new list with the elements `predicate` holds for removed. |
| a271f34 | 262 | fun reject(self, predicate: fn(T) -> Bool) -> List[T] = |
| a271f34 | 263 | result := List(head: None, tail: None, size: 0) |
| a271f34 | 264 | current := self.head |
| a271f34 | 265 | while current != None |
| a271f34 | 266 | match current |
| 3a2119e | 267 | Some(Node(value, _, next)) => |
| 3a2119e | 268 | if !predicate(value) |
| 3a2119e | 269 | result.add(value) |
| 3a2119e | 270 | current = next |
| a271f34 | 271 | None => |
| a271f34 | 272 | break |
| a271f34 | 273 | return result |
| 58b01fc | 274 | |
| 58b01fc | 275 | fun any(self, predicate: fn(T) -> Bool) -> Bool = |
| a271f34 | 276 | current := self.head |
| a271f34 | 277 | while current != None |
| a271f34 | 278 | match current |
| 3a2119e | 279 | Some(Node(value, _, next)) => |
| 3a2119e | 280 | if predicate(value) |
| a271f34 | 281 | return True |
| 3a2119e | 282 | current = next |
| a271f34 | 283 | None => |
| a271f34 | 284 | break |
| a271f34 | 285 | return False |
| 58b01fc | 286 | |
| 58b01fc | 287 | fun every(self, predicate: fn(T) -> Bool) -> Bool = |
| a271f34 | 288 | current := self.head |
| a271f34 | 289 | while current != None |
| a271f34 | 290 | match current |
| 3a2119e | 291 | Some(Node(value, _, next)) => |
| 3a2119e | 292 | if !predicate(value) |
| a271f34 | 293 | return False |
| 3a2119e | 294 | current = next |
| a271f34 | 295 | None => |
| a271f34 | 296 | break |
| a271f34 | 297 | return True |
| a271f34 | 298 | |
| a271f34 | 299 | # Folds `self`'s elements into a single value, starting from `acc` and |
| a271f34 | 300 | # combining each element in with `cb(accumulatedSoFar, element)`. |
| a271f34 | 301 | # |
| a271f34 | 302 | # `U` is `reduce`'s OWN generic param (the accumulator's type), separate |
| a271f34 | 303 | # from `List[T]`'s own `T` — same per-CALL-SITE resolution as `map`'s `U`. |
| a271f34 | 304 | fun reduce(self, acc: U, cb: fn(U, T) -> U) -> U = |
| a271f34 | 305 | result := acc |
| a271f34 | 306 | current := self.head |
| a271f34 | 307 | while current != None |
| a271f34 | 308 | match current |
| 3a2119e | 309 | Some(Node(value, _, next)) => |
| 3a2119e | 310 | result = cb(result, value) |
| 3a2119e | 311 | current = next |
| a271f34 | 312 | None => |
| a271f34 | 313 | break |
| a271f34 | 314 | return result |
| 58b01fc | 315 | |
| 58b01fc | 316 | # returns the first element in the list |
| 58b01fc | 317 | fun first(self) -> Option[T] = |
| 58b01fc | 318 | match self.head |
| 3a2119e | 319 | Some(Node(value, _, _)) => Some(value) |
| 3a2119e | 320 | None => None |
| 58b01fc | 321 | |
| 58b01fc | 322 | # returns the last element in the list |
| 58b01fc | 323 | fun last(self) -> Option[T] = |
| 58b01fc | 324 | match self.tail |
| 3a2119e | 325 | Some(Node(value, _, _)) => Some(value) |
| 3a2119e | 326 | None => None |
| 58b01fc | 327 | |
| a271f34 | 328 | # Returns a new list of the elements at index `[start, end)`, clamped into |
| a271f34 | 329 | # range (a `start`/`end` outside `[0, length]` is clamped, not an error). |
| a271f34 | 330 | fun sublist(self, start: Int, end: Int) -> List[T] = |
| a271f34 | 331 | result := List(head: None, tail: None, size: 0) |
| a271f34 | 332 | s := start < 0 ? 0 : start |
| a271f34 | 333 | e := end > self.size ? self.size : end |
| a271f34 | 334 | current := self.head |
| a271f34 | 335 | i := 0 |
| a271f34 | 336 | while current != None && i < e |
| a271f34 | 337 | match current |
| 3a2119e | 338 | Some(Node(value, _, next)) => |
| a271f34 | 339 | if i >= s |
| 3a2119e | 340 | result.add(value) |
| 3a2119e | 341 | current = next |
| a271f34 | 342 | i = i + 1 |
| a271f34 | 343 | None => |
| a271f34 | 344 | break |
| a271f34 | 345 | return result |
| a271f34 | 346 | |
| a271f34 | 347 | # The first `n` elements. |
| a271f34 | 348 | fun take(self, n: Int) -> List[T] = |
| a271f34 | 349 | return self.sublist(0, n) |
| a271f34 | 350 | |
| a271f34 | 351 | # Every element AFTER the first `n`. |
| a271f34 | 352 | fun skip(self, n: Int) -> List[T] = |
| a271f34 | 353 | return self.sublist(n, self.size) |
| a271f34 | 354 | |
| a271f34 | 355 | fun drop(self, n: Int) -> List[T] = |
| a271f34 | 356 | return self.skip(n) |
| a271f34 | 357 | |
| a271f34 | 358 | # Returns one uniformly random element, or `None` if `self` is empty. |
| a271f34 | 359 | fun sample(self) -> Option[T] = |
| a271f34 | 360 | if self.size == 0 |
| a271f34 | 361 | return None |
| a271f34 | 362 | return self.get(randomInt(self.size)) |
| a271f34 | 363 | |
| a271f34 | 364 | # Returns a new list with `self`'s elements in random order (Fisher-Yates). |
| a271f34 | 365 | fun shuffle(self) -> List[T] = |
| a271f34 | 366 | result := self.sublist(0, self.size) |
| a271f34 | 367 | i := result.size - 1 |
| a271f34 | 368 | while i > 0 |
| a271f34 | 369 | j := randomInt(i + 1) |
| a271f34 | 370 | vi := result.get(i).unwrap() |
| a271f34 | 371 | vj := result.get(j).unwrap() |
| a271f34 | 372 | result.set(i, vj) |
| a271f34 | 373 | result.set(j, vi) |
| a271f34 | 374 | i = i - 1 |
| a271f34 | 375 | return result |
| 58b01fc | 376 | |
| cde99bd | 377 | # `chunk`/`partition` both return `List[List[T]]` — the SAME generic class |
| cde99bd | 378 | # (`List`) nested inside itself as a method's own return type. This used to |
| cde99bd | 379 | # be an unconditional stack-overflow landmine for the WHOLE COMPILER (see |
| cde99bd | 380 | # `plum-checker/src/monomorphize.rs`'s `isSelfNested`/`methodMentionsTemplate` |
| 5a9e763 | 381 | # doc comments for the full fix) — that danger is now FIXED (2026-09-02). |
| 5a9e763 | 382 | # |
| 5a9e763 | 383 | # A second, more contained bug surfaced once past that: two SEPARATE bare |
| 5a9e763 | 384 | # `List(head: None, tail: None, size: 0)` constructions in the SAME method |
| 5a9e763 | 385 | # body — one for the OUTER `result: List[List[T]]` (matching this |
| 5a9e763 | 386 | # method's own declared return type) and one for an INNER per-chunk |
| 5a9e763 | 387 | # `piece: List[T]` — are ambiguous to `resolveClassInstantiation`'s |
| 5a9e763 | 388 | # `current_return_type` fallback: since `List[T]` and `List[List[T]]` both |
| 5a9e763 | 389 | # have exactly ONE generic argument, the fallback (which only ever |
| 5a9e763 | 390 | # compares ARITY, not identity) can't tell "this bare construction IS the |
| 5a9e763 | 391 | # function's own return value" apart from "this is some OTHER, unrelated |
| 5a9e763 | 392 | # nested construction of the same template that happens to need the same |
| 5a9e763 | 393 | # arity" — so `piece` was silently resolved to the SAME type as `result` |
| 5a9e763 | 394 | # (`List$List$Int` instead of `List$Int`), corrupting its own `add` calls. |
| 5a9e763 | 395 | # Worked around by building `piece` via `self.sublist(...)` instead of a |
| 5a9e763 | 396 | # second bare construction — `sublist` is an ordinary method already |
| 5a9e763 | 397 | # unambiguously resolved through the RECEIVER's own (already-correct) |
| 5a9e763 | 398 | # binding, no `current_return_type` guessing involved at all. |
| 5a9e763 | 399 | fun chunk(self, size: Int) -> List[List[T]] = |
| 5a9e763 | 400 | result := List(head: None, tail: None, size: 0) |
| 5a9e763 | 401 | if size <= 0 |
| 5a9e763 | 402 | return result |
| 5a9e763 | 403 | i := 0 |
| 5a9e763 | 404 | n := self.length() |
| 5a9e763 | 405 | while i < n |
| 5a9e763 | 406 | result.add(self.sublist(i, i + size)) |
| 5a9e763 | 407 | i = i + size |
| 5a9e763 | 408 | return result |
| 58b01fc | 409 | |
| 5a9e763 | 410 | # Splits into two lists: elements `predicate` holds for, then the rest — |
| 5a9e763 | 411 | # in that order. Returns them as a 2-element `List[List[T]]` (`[matched, |
| 5a9e763 | 412 | # unmatched]`) since this language has no tuple type. Deliberately built |
| 5a9e763 | 413 | # from `self.retain(predicate)`/`self.reject(predicate)` rather than two |
| 5a9e763 | 414 | # local `matched`/`unmatched := List(head: None, ...)` bare constructions |
| 5a9e763 | 415 | # — see `chunk`'s doc comment above for exactly why that would be |
| 5a9e763 | 416 | # ambiguous (`matched`/`unmatched` need `List[T]`, but a bare |
| 5a9e763 | 417 | # construction here would resolve via `current_return_type` fallback |
| 5a9e763 | 418 | # against THIS method's own `List[List[T]]` return instead, same arity |
| 5a9e763 | 419 | # coincidence). `retain`/`reject` are each already unambiguous (single |
| 5a9e763 | 420 | # bare construction per method body, matching their own single-level |
| 5a9e763 | 421 | # return type). |
| 5a9e763 | 422 | fun partition(self, predicate: fn(T) -> Bool) -> List[List[T]] = |
| 5a9e763 | 423 | result := List(head: None, tail: None, size: 0) |
| 5a9e763 | 424 | result.add(self.retain(predicate)) |
| 5a9e763 | 425 | result.add(self.reject(predicate)) |
| 5a9e763 | 426 | return result |
| 58b01fc | 427 | |
| cde99bd | 428 | # Grouping by an arbitrary key type would need its own generic param |
| cde99bd | 429 | # (`groupBy<K>(keyFn: fn(T) -> K) -> Map[K, List[T]]`) resolved from a |
| cde99bd | 430 | # closure's return type — the same method-level-generics gap documented on |
| cde99bd | 431 | # `List.map` above, PLUS `Map[K, List[T]]` would make `list.plum` depend on |
| cde99bd | 432 | # `map.plum`, which already depends on `list.plum`. Not implemented. |
| 58b01fc | 433 | fun groupBy(self) = |
| 58b01fc | 434 | todo |
| 58b01fc | 435 | |
| a271f34 | 436 | # Note: NOT written with `each` + a closure-captured "is this the first |
| a271f34 | 437 | # element" flag — a closure's captured variables are snapshotted at |
| a271f34 | 438 | # CREATION time (see `libs/std/list.plum`'s own `add`/`each` usage |
| a271f34 | 439 | # elsewhere), so an assignment to a captured var INSIDE the closure body |
| a271f34 | 440 | # does not persist across that closure's own repeated invocations from one |
| a271f34 | 441 | # `each` call. A plain `while`/`match` traversal (same idiom as `unlink`) |
| a271f34 | 442 | # sidesteps that entirely. |
| 58b01fc | 443 | fun join(self, sep: Str = ",") -> Str = |
| 4a2384c | 444 | res := Buffer() |
| a271f34 | 445 | current := self.head |
| a271f34 | 446 | while current != None |
| a271f34 | 447 | match current |
| 3a2119e | 448 | Some(Node(value, _, next)) => |
| 3a2119e | 449 | res.write(value.toStr()) |
| 3a2119e | 450 | if next != None |
| a271f34 | 451 | res.write(sep) |
| 3a2119e | 452 | current = next |
| a271f34 | 453 | None => |
| a271f34 | 454 | break |
| 58b01fc | 455 | res.toStr() |
| 58b01fc | 456 | |
| bf629a2 | 457 | # `List[T: ToStr](ToStr)` declared implementing `ToStr` |
| 5a9e763 | 458 | # from the start but never actually defined `toStr` — harmless for a |
| 5a9e763 | 459 | # PLAIN `List[Int]`/`List[Str]`/etc (nothing needs to call `.toStr()` on |
| 5a9e763 | 460 | # the LIST ITSELF, just on its elements, which `join` above already |
| 5a9e763 | 461 | # does), but a genuine gap the moment a `List` is used AS another |
| 5a9e763 | 462 | # `List`'s own ELEMENT type (`List[List[T]]`) — the OUTER list's eagerly- |
| 5a9e763 | 463 | # compiled `join`/`toStr` calls `.toStr()` on each element, and an inner |
| 5a9e763 | 464 | # `List[T]` element had no such method to call, failing to compile with |
| 5a9e763 | 465 | # "unknown method 'List$T.toStr'" even for a program that never actually |
| 5a9e763 | 466 | # calls `.toStr()`/`.join()` on the outer list (per the "eager compile |
| 5a9e763 | 467 | # every method for every specialization" architecture noted elsewhere in |
| 5a9e763 | 468 | # this codebase's history). |
| 5a9e763 | 469 | fun toStr(self) -> Str = |
| 5a9e763 | 470 | return "[" + self.join(",") + "]" |
| 5a9e763 | 471 | |
| a43f3af | 472 | # ---- regression tests ---- |
| a43f3af | 473 | |
| a43f3af | 474 | fun optSumForListTest(o: Option[Int]) -> Int = |
| a43f3af | 475 | match o |
| 3a2119e | 476 | Some(v) => v |
| 3a2119e | 477 | None => -1000 |
| a43f3af | 478 | |
| a43f3af | 479 | fun exerciseList() -> Int = |
| 4a2384c | 480 | l = List(1, 2, 3, 4, 5) |
| a43f3af | 481 | a = l.length() |
| a43f3af | 482 | b = optSumForListTest(l.get(0)) |
| a43f3af | 483 | c = optSumForListTest(l.get(4)) |
| a43f3af | 484 | oldVal = optSumForListTest(l.set(2, 30)) |
| a43f3af | 485 | d = optSumForListTest(l.get(2)) |
| a43f3af | 486 | l.removeAt(0) |
| a43f3af | 487 | e = l.length() |
| a43f3af | 488 | f = optSumForListTest(l.get(0)) |
| a43f3af | 489 | l.remove(30) |
| a43f3af | 490 | g = l.length() |
| a43f3af | 491 | l.reverse() |
| a43f3af | 492 | h = optSumForListTest(l.get(0)) |
| a43f3af | 493 | l.clear() |
| a43f3af | 494 | i = l.length() |
| a43f3af | 495 | a + b + c + oldVal + d + e + f + g + h + i |
| a43f3af | 496 | |
| a43f3af | 497 | fun removeAllNodesOneAtATime() -> Int = |
| 4a2384c | 498 | l = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) |
| a43f3af | 499 | i = 0 |
| a43f3af | 500 | while i < 10 |
| a43f3af | 501 | l.removeAt(0) |
| a43f3af | 502 | i = i + 1 |
| a43f3af | 503 | afterLoopLength = l.length() |
| a43f3af | 504 | isEmpty = optSumForListTest(l.get(0)) |
| a43f3af | 505 | l.add(42) |
| a43f3af | 506 | afterReAdd = optSumForListTest(l.get(0)) |
| a43f3af | 507 | afterLoopLength + isEmpty + afterReAdd |
| a43f3af | 508 | |
| 4a2384c | 509 | fun exerciseListInit() -> Int = |
| 4a2384c | 510 | l := List[Int]() |
| 4a2384c | 511 | before := l.length() |
| 4a2384c | 512 | l.add(7) |
| 4a2384c | 513 | before + l.length() + optSumForListTest(l.get(0)) |
| 4a2384c | 514 | |
| 4a2384c | 515 | fun exerciseListInitWithValues() -> Int = |
| 4a2384c | 516 | l := List(10, 20, 30) |
| 4a2384c | 517 | l.length() + optSumForListTest(l.get(0)) + optSumForListTest(l.get(2)) |
| 4a2384c | 518 | |
| 8de13fd | 519 | test "list add set remove at remove clear reverse all work correctly" |
| 8de13fd | 520 | # add(1,2,3,4,5): a=length=5, b=get(0)=1, c=get(4)=5 |
| 8de13fd | 521 | # set(2,30): oldVal=3, d=get(2)=30 -> list [1,2,30,4,5] |
| 8de13fd | 522 | # removeAt(0): e=length=4, f=get(0)=2 -> list [2,30,4,5] |
| 8de13fd | 523 | # remove(30): g=length=3 -> list [2,4,5] |
| 8de13fd | 524 | # reverse(): h=get(0)=5 -> list [5,4,2] |
| 8de13fd | 525 | # clear(): i=length=0 |
| 8de13fd | 526 | # 5+1+5+3+30+4+2+3+5+0 = 58 |
| 8de13fd | 527 | assert exerciseList() == 58 |
| 8de13fd | 528 | |
| 8de13fd | 529 | test "removing every node in a loop leaves an empty correctly functioning list" |
| 8de13fd | 530 | # afterLoopLength=0, isEmpty(get(0) on empty list)=-1000, afterReAdd=42 |
| 8de13fd | 531 | # 0 + -1000 + 42 = -958 |
| 8de13fd | 532 | assert removeAllNodesOneAtATime() == -958 |
| 8de13fd | 533 | |
| 8de13fd | 534 | test "List[Int]() with no args runs init() and produces a working empty list" |
| 8de13fd | 535 | assert exerciseListInit() == 8 |
| 8de13fd | 536 | |
| 4a2384c | 537 | test "List(values) with positional args runs init(values) directly, infers T from args" |
| a9a0147 | 538 | assert exerciseListInitWithValues() == 43 |
| 4a2384c | 539 | |
| a43f3af | 540 | test "list map and reduce work correctly" |
| 4a2384c | 541 | l = List(1, 2, 3, 4) |
| a43f3af | 542 | doubled = l.map(|v| v * 2) |
| a43f3af | 543 | total = doubled.reduce(0, |acc, v| acc + v) |
| a9a0147 | 544 | assert total == 20 |
| a43f3af | 545 | |
| 2ec05c8 | 546 | test "list equals compares elementwise" |
| 4a2384c | 547 | a = List(1, 2, 3) |
| 4a2384c | 548 | b = List(1, 2, 3) |
| 4a2384c | 549 | c = List(1, 2) |
| a9a0147 | 550 | assert a.equals(b) |
| a9a0147 | 551 | assert !a.equals(c) |
| 2ec05c8 | 552 | |
| a43f3af | 553 | test "list sort orders elements correctly" |
| 4a2384c | 554 | l = List(3, 1, 4, 1, 5) |
| a43f3af | 555 | sorted = l.sort(|a, b| a < b) |
| a9a0147 | 556 | assert optSumForListTest(sorted.get(0)) == 1 |
| a9a0147 | 557 | assert optSumForListTest(sorted.get(4)) == 5 |
| a43f3af | 558 | |
| a43f3af | 559 | test "list join renders elements correctly with separator" |
| 4a2384c | 560 | l = List(1, 2, 3) |
| a9a0147 | 561 | assert l.join(",") == "1,2,3" |
| 5a9e763 | 562 | |
| 5a9e763 | 563 | test "list toStr formats a plain list and a nested list of lists" |
| 4a2384c | 564 | l := List(1, 2, 3) |
| a9a0147 | 565 | assert l.toStr() == "[1,2,3]" |
| 5a9e763 | 566 | chunks := l.chunk(2) |
| a9a0147 | 567 | assert chunks.toStr() == "[[1,2],[3]]" |
| 5a9e763 | 568 | |
| 5a9e763 | 569 | test "chunk splits into sublists of at most size elements each" |
| 4a2384c | 570 | l := List(1, 2, 3, 4, 5) |
| 5a9e763 | 571 | chunks := l.chunk(2) |
| a9a0147 | 572 | assert chunks.length() == 3 |
| a9a0147 | 573 | assert chunks.get(0).unwrap().join(",") == "1,2" |
| a9a0147 | 574 | assert chunks.get(1).unwrap().join(",") == "3,4" |
| a9a0147 | 575 | assert chunks.get(2).unwrap().join(",") == "5" |
| a9a0147 | 576 | assert l.chunk(0).length() == 0 |
| 5a9e763 | 577 | |
| 5a9e763 | 578 | test "partition splits into elements matching a predicate, then the rest" |
| 4a2384c | 579 | l := List(1, 2, 3, 4, 5, 6) |
| 5a9e763 | 580 | parts := l.partition(|v| v % 2 == 0) |
| a9a0147 | 581 | assert parts.length() == 2 |
| a9a0147 | 582 | assert parts.get(0).unwrap().join(",") == "2,4,6" |
| a9a0147 | 583 | assert parts.get(1).unwrap().join(",") == "1,3,5" |