plum

#treesitter#compiler#wasm

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"