plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
72527fb
— Peter John
2026-10-26T20:20:49+05:30
refactor: move range into std/Iter, delete std/Range
- README.md +2 -2
- plum-examples/control_flow.plum +1 -1
- plum-examples/loops.plum +1 -1
- plum-examples/typed_arrays.plum +1 -1
- plum-examples/types.plum +1 -1
- plum-std/Array.plum +3 -2
- plum-std/Deque.plum +12 -5
- plum-std/Heap.plum +6 -3
- plum-std/Iter.plum +9 -1
- plum-std/List.plum +42 -15
- plum-std/Map.plum +24 -9
- plum-std/Random.plum +9 -4
- plum-std/Range.plum +0 -12
- plum-std/Set.plum +3 -2
README.md
CHANGED
|
@@ -118,7 +118,7 @@ From tightest to loosest binding:
|
|
|
118
118
|
| | `\|\|` | |
|
|
119
119
|
| lowest | `? :` | ternary |
|
|
120
120
|
|
|
121
|
-
There's no range operator — `for i := range(0, n)` (`range` is a `fun*` from `import std/
|
|
121
|
+
There's no range operator — `for i := range(0, n)` (`range` is a `fun*` from `import std/Iter`) is the way to iterate a numeric range. `{ }` is used to group a sub-expression (not `( )`, which is reserved for calls):
|
|
122
122
|
|
|
123
123
|
```plum
|
|
124
124
|
grouped = {1 + 2} * {3 - 1}
|
|
@@ -212,7 +212,7 @@ fun sumAll(nums: ...Int) -> Int = # variadic param
|
|
|
212
212
|
```plum
|
|
213
213
|
for u := units # a list
|
|
214
214
|
total = total + u.hp
|
|
215
|
-
for i := range(0, 10) # a fun* from std/
|
|
215
|
+
for i := range(0, 10) # a fun* from std/Iter
|
|
216
216
|
total = total + i
|
|
217
217
|
for i, u := indexed(units) # a fun* from std/Iter: two binders
|
|
218
218
|
printLn("{i}: {u.name}")
|
plum-examples/control_flow.plum
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import std/Str
|
|
2
2
|
import std/Number
|
|
3
|
-
import std/
|
|
3
|
+
import std/Iter
|
|
4
4
|
|
|
5
5
|
fun loopSum(limit: Int) -> Int =
|
|
6
6
|
total := 0
|
plum-examples/loops.plum
CHANGED
|
@@ -3,7 +3,7 @@ import std/Number
|
|
|
3
3
|
import std/List
|
|
4
4
|
import std/Array
|
|
5
5
|
import std/Testing
|
|
6
|
-
import std/
|
|
6
|
+
import std/Iter
|
|
7
7
|
|
|
8
8
|
fun sumList(xs: List[Int]) -> Int =
|
|
9
9
|
total := 0
|
plum-examples/typed_arrays.plum
CHANGED
|
@@ -2,7 +2,7 @@ module typed_arrays
|
|
|
2
2
|
import std/Number
|
|
3
3
|
import std/Array
|
|
4
4
|
import std/Grid
|
|
5
|
-
import std/
|
|
5
|
+
import std/Iter
|
|
6
6
|
|
|
7
7
|
# Tile-grid style data: Array[Int] / Array[Float] / Array[Bool] are native
|
|
8
8
|
# unboxed wasm arrays (i64 / f64 / i32 elements), not boxed anyref arrays.
|
plum-examples/types.plum
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import std/Try
|
|
2
2
|
import std/Str
|
|
3
3
|
import std/Number
|
|
4
|
-
import std/
|
|
4
|
+
import std/Iter
|
|
5
5
|
|
|
6
6
|
enum Point =
|
|
7
7
|
| Point(x: Int, y: Int)
|
plum-std/Array.plum
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
module std
|
|
2
2
|
import std/Number
|
|
3
|
-
import std/Range
|
|
4
3
|
|
|
5
4
|
# A fixed-length, O(1)-indexable array of `T`, backed directly by a wasm-gc
|
|
6
5
|
# `array<anyref>` — `Array[T]` is erased, so every `T` shares the SAME
|
|
@@ -71,6 +70,8 @@ enum Array[T] =
|
|
|
71
70
|
# in a native `array (mut i64)` / `(mut f64)` / `(mut i32)`.
|
|
72
71
|
fun filled(n: Int, v: T) -> Array[T] =
|
|
73
72
|
a := Array[T]().resize(n)
|
|
73
|
+
i := 0
|
|
74
|
-
|
|
74
|
+
while i < n
|
|
75
75
|
a.set(i, v)
|
|
76
|
+
i = i + 1
|
|
76
77
|
return a
|
plum-std/Deque.plum
CHANGED
|
@@ -3,7 +3,6 @@ import std/Array
|
|
|
3
3
|
import std/List
|
|
4
4
|
import std/Try
|
|
5
5
|
import std/Number
|
|
6
|
-
import std/Range
|
|
7
6
|
|
|
8
7
|
# A double-ended queue: a ring buffer over `Array[T]`. `buf`'s length is the
|
|
9
8
|
# capacity, `head` the index of the front element, `size` the number in use;
|
|
@@ -29,8 +28,10 @@ enum Deque[T] =
|
|
|
29
28
|
return
|
|
30
29
|
cap := self.buf.length()
|
|
31
30
|
nb := Array[T]().resize(cap * 2)
|
|
31
|
+
i := 0
|
|
32
|
-
|
|
32
|
+
while i < self.size
|
|
33
33
|
nb.set(i, self.buf.get({self.head + i} % cap))
|
|
34
|
+
i = i + 1
|
|
34
35
|
self.buf = nb
|
|
35
36
|
self.head = 0
|
|
36
37
|
|
|
@@ -79,19 +80,23 @@ enum Deque[T] =
|
|
|
79
80
|
test "deque_wraparound: head wraps past the end of the ring and growth unwraps it"
|
|
80
81
|
d := Deque[Int]()
|
|
81
82
|
# keep size small so head keeps advancing around the 8-slot ring
|
|
83
|
+
i := 0
|
|
82
|
-
|
|
84
|
+
while i < 50
|
|
83
85
|
d.pushBack(i)
|
|
84
86
|
d.pushBack(i + 1000)
|
|
85
87
|
assert d.popFront().unwrap() == i
|
|
86
88
|
assert d.popFront().unwrap() == i + 1000
|
|
89
|
+
i = i + 1
|
|
87
90
|
assert d.isEmpty()
|
|
88
91
|
assert d.head != 0 || d.buf.length() > 8
|
|
89
92
|
# fill beyond capacity while head is mid-ring
|
|
90
93
|
d.pushBack(1)
|
|
91
94
|
d.pushBack(2)
|
|
92
95
|
d.popFront()
|
|
96
|
+
i = 0
|
|
93
|
-
|
|
97
|
+
while i < 20
|
|
94
98
|
d.pushBack(10 + i)
|
|
99
|
+
i = i + 1
|
|
95
100
|
assert d.length() == 21
|
|
96
101
|
assert d.get(0).unwrap() == 2
|
|
97
102
|
assert d.get(20).unwrap() == 29
|
|
@@ -99,8 +104,10 @@ test "deque_wraparound: head wraps past the end of the ring and growth unwraps i
|
|
|
99
104
|
|
|
100
105
|
test "pushFront builds the front in reverse order"
|
|
101
106
|
d := Deque[Int]()
|
|
107
|
+
i := 0
|
|
102
|
-
|
|
108
|
+
while i < 12
|
|
103
109
|
d.pushFront(i)
|
|
110
|
+
i = i + 1
|
|
104
111
|
assert d.get(0).unwrap() == 11
|
|
105
112
|
assert d.get(11).unwrap() == 0
|
|
106
113
|
assert d.popFront().unwrap() == 11
|
plum-std/Heap.plum
CHANGED
|
@@ -3,7 +3,6 @@ import std/Array
|
|
|
3
3
|
import std/List
|
|
4
4
|
import std/Try
|
|
5
5
|
import std/Number
|
|
6
|
-
import std/Range
|
|
7
6
|
|
|
8
7
|
# A binary MIN-heap ordered by a key function: `pop` returns the element whose
|
|
9
8
|
# `key(x)` is smallest. Ordering by an explicit `fn(T) -> Float` (rather than a
|
|
@@ -81,15 +80,19 @@ enum Heap[T] =
|
|
|
81
80
|
test "heap_pop_order: 1000 pseudo-random keys pop in ascending order"
|
|
82
81
|
h := Heap[Int](items: List[Int](), key: |x| Float(x))
|
|
83
82
|
x := 12345
|
|
83
|
+
i := 0
|
|
84
|
-
|
|
84
|
+
while i < 1000
|
|
85
85
|
x = {x * 1103515245 + 12345} % 2147483648
|
|
86
86
|
h.push(x % 100000)
|
|
87
|
+
i = i + 1
|
|
87
88
|
assert h.length() == 1000
|
|
88
89
|
prev := -1
|
|
90
|
+
i = 0
|
|
89
|
-
|
|
91
|
+
while i < 1000
|
|
90
92
|
v := h.pop().unwrap()
|
|
91
93
|
assert v >= prev
|
|
92
94
|
prev = v
|
|
95
|
+
i = i + 1
|
|
93
96
|
assert h.isEmpty()
|
|
94
97
|
assert h.pop().isErr()
|
|
95
98
|
|
plum-std/Iter.plum
CHANGED
|
@@ -3,7 +3,6 @@ module std
|
|
|
3
3
|
import std/Number
|
|
4
4
|
import std/List
|
|
5
5
|
import std/Try
|
|
6
|
-
import std/Range
|
|
7
6
|
|
|
8
7
|
# Iterators: `for u := filter(units)` expands the `fun*`'s body at the call
|
|
9
8
|
# site (no closure, no call_indirect), with the `for` body in place of each
|
|
@@ -29,6 +28,15 @@ import std/Range
|
|
|
29
28
|
# may only be passed on to `yield`. `zip`, `reversed` and `chunks` need random
|
|
30
29
|
# access, so they take a List.
|
|
31
30
|
|
|
31
|
+
# `for i := range(0, n)`: start, start + 1, ... end - 1. The counter is bumped
|
|
32
|
+
# before the `yield`, so a `continue` in the body still advances it.
|
|
33
|
+
fun* range(start: Int, end: Int) *Int =
|
|
34
|
+
i := start
|
|
35
|
+
while i < end
|
|
36
|
+
cur := i
|
|
37
|
+
i = i + 1
|
|
38
|
+
yield cur
|
|
39
|
+
|
|
32
40
|
# The elements for which the body is true.
|
|
33
41
|
fun* filter(src: *T) *T -> List[T] =
|
|
34
42
|
out := List()
|
plum-std/List.plum
CHANGED
|
@@ -5,7 +5,6 @@ import std/Array
|
|
|
5
5
|
import std/Buffer
|
|
6
6
|
import std/Number
|
|
7
7
|
import std/Str
|
|
8
|
-
import std/Range
|
|
9
8
|
|
|
10
9
|
# A growable list: `data` is a backing `Array[T]` whose length is the list's
|
|
11
10
|
# capacity, `size` the number of slots in use. Capacity doubles when `push`
|
|
@@ -55,9 +54,11 @@ enum List[T] =
|
|
|
55
54
|
fun equals(self, other: List[T]) -> Bool =
|
|
56
55
|
if self.length() != other.length()
|
|
57
56
|
return False
|
|
57
|
+
i := 0
|
|
58
|
-
|
|
58
|
+
while i < self.length()
|
|
59
59
|
if self.data.get(i) != other.data.get(i)
|
|
60
60
|
return False
|
|
61
|
+
i = i + 1
|
|
61
62
|
return True
|
|
62
63
|
|
|
63
64
|
# appends `v` to the end of the list, doubling the capacity when full
|
|
@@ -92,10 +93,12 @@ enum List[T] =
|
|
|
92
93
|
|
|
93
94
|
# removes the first element equal to v from list
|
|
94
95
|
fun remove(self, v: T) =
|
|
96
|
+
i := 0
|
|
95
|
-
|
|
97
|
+
while i < self.size
|
|
96
98
|
if self.data.get(i) == v
|
|
97
99
|
self.removeAt(i)
|
|
98
100
|
return
|
|
101
|
+
i = i + 1
|
|
99
102
|
|
|
100
103
|
# removes all objects from this list
|
|
101
104
|
fun clear(self) =
|
|
@@ -132,10 +135,12 @@ enum List[T] =
|
|
|
132
135
|
|
|
133
136
|
# Returns the first element equal to `search`, or `Err(NotFound)`.
|
|
134
137
|
fun find(self, search: T) -> T ! [NotFound] =
|
|
138
|
+
i := 0
|
|
135
|
-
|
|
139
|
+
while i < self.size
|
|
136
140
|
value := self.data.get(i)
|
|
137
141
|
if value == search
|
|
138
142
|
return Ok(value)
|
|
143
|
+
i = i + 1
|
|
139
144
|
return Err(NotFound)
|
|
140
145
|
|
|
141
146
|
fun contains(self, v: T) -> Bool =
|
|
@@ -143,15 +148,19 @@ enum List[T] =
|
|
|
143
148
|
|
|
144
149
|
# calls f for each elem in the list
|
|
145
150
|
fun each(self, cb: fn(T)) -> Unit =
|
|
151
|
+
i := 0
|
|
146
|
-
|
|
152
|
+
while i < self.size
|
|
147
153
|
cb(self.data.get(i))
|
|
154
|
+
i = i + 1
|
|
148
155
|
|
|
149
156
|
# Returns a new list with each element transformed by `cb`. `U` is a
|
|
150
157
|
# generic param of `map` itself, separate from `List[T]`'s own `T`.
|
|
151
158
|
fun map(self, cb: fn(T) -> U) -> List[U] =
|
|
152
159
|
result := List[U]()
|
|
160
|
+
i := 0
|
|
153
|
-
|
|
161
|
+
while i < self.size
|
|
154
162
|
result.push(cb(self.data.get(i)))
|
|
163
|
+
i = i + 1
|
|
155
164
|
return result
|
|
156
165
|
|
|
157
166
|
# returns a new list with each element flat-mapped
|
|
@@ -161,31 +170,39 @@ enum List[T] =
|
|
|
161
170
|
# Returns a new list with only the elements `predicate` holds for.
|
|
162
171
|
fun retain(self, predicate: fn(T) -> Bool) -> List[T] =
|
|
163
172
|
result := List[T]()
|
|
173
|
+
i := 0
|
|
164
|
-
|
|
174
|
+
while i < self.size
|
|
165
175
|
value := self.data.get(i)
|
|
166
176
|
if predicate(value)
|
|
167
177
|
result.push(value)
|
|
178
|
+
i = i + 1
|
|
168
179
|
return result
|
|
169
180
|
|
|
170
181
|
# Returns a new list with the elements `predicate` holds for removed.
|
|
171
182
|
fun reject(self, predicate: fn(T) -> Bool) -> List[T] =
|
|
172
183
|
result := List[T]()
|
|
184
|
+
i := 0
|
|
173
|
-
|
|
185
|
+
while i < self.size
|
|
174
186
|
value := self.data.get(i)
|
|
175
187
|
if !predicate(value)
|
|
176
188
|
result.push(value)
|
|
189
|
+
i = i + 1
|
|
177
190
|
return result
|
|
178
191
|
|
|
179
192
|
fun any(self, predicate: fn(T) -> Bool) -> Bool =
|
|
193
|
+
i := 0
|
|
180
|
-
|
|
194
|
+
while i < self.size
|
|
181
195
|
if predicate(self.data.get(i))
|
|
182
196
|
return True
|
|
197
|
+
i = i + 1
|
|
183
198
|
return False
|
|
184
199
|
|
|
185
200
|
fun every(self, predicate: fn(T) -> Bool) -> Bool =
|
|
201
|
+
i := 0
|
|
186
|
-
|
|
202
|
+
while i < self.size
|
|
187
203
|
if !predicate(self.data.get(i))
|
|
188
204
|
return False
|
|
205
|
+
i = i + 1
|
|
189
206
|
return True
|
|
190
207
|
|
|
191
208
|
# Folds `self`'s elements into a single value, starting from `acc` and
|
|
@@ -193,8 +210,10 @@ enum List[T] =
|
|
|
193
210
|
# `reduce`'s own generic param (the accumulator's type).
|
|
194
211
|
fun reduce(self, acc: U, cb: fn(U, T) -> U) -> U =
|
|
195
212
|
result := acc
|
|
213
|
+
i := 0
|
|
196
|
-
|
|
214
|
+
while i < self.size
|
|
197
215
|
result = cb(result, self.data.get(i))
|
|
216
|
+
i = i + 1
|
|
198
217
|
return result
|
|
199
218
|
|
|
200
219
|
# returns the first element in the list
|
|
@@ -284,10 +303,12 @@ enum List[T] =
|
|
|
284
303
|
|
|
285
304
|
fun join(self, sep: Str = ",") -> Str =
|
|
286
305
|
res := Buffer()
|
|
306
|
+
i := 0
|
|
287
|
-
|
|
307
|
+
while i < self.size
|
|
288
308
|
if i > 0
|
|
289
309
|
res.write(sep)
|
|
290
310
|
res.write(self.data.get(i).toStr())
|
|
311
|
+
i = i + 1
|
|
291
312
|
res.toStr()
|
|
292
313
|
|
|
293
314
|
fun toStr(self) -> Str =
|
|
@@ -408,8 +429,10 @@ test "partition splits into elements matching a predicate, then the rest"
|
|
|
408
429
|
|
|
409
430
|
test "list_get_set_push_pop: push 1000, get(999), pop until empty, get on empty is Err"
|
|
410
431
|
l := List[Int]()
|
|
432
|
+
i := 0
|
|
411
|
-
|
|
433
|
+
while i < 1000
|
|
412
434
|
l.push(i)
|
|
435
|
+
i = i + 1
|
|
413
436
|
assert l.length() == 1000
|
|
414
437
|
assert l.get(999).unwrap() == 999
|
|
415
438
|
assert optSumForListTest(l.get(1000)) == -1000
|
|
@@ -427,19 +450,23 @@ test "list_get_set_push_pop: push 1000, get(999), pop until empty, get on empty
|
|
|
427
450
|
|
|
428
451
|
test "list_growth: length stays exact across the doubling boundaries"
|
|
429
452
|
l := List[Int]()
|
|
453
|
+
i := 0
|
|
430
|
-
|
|
454
|
+
while i < 1025
|
|
431
455
|
l.push(i)
|
|
432
456
|
n := i + 1
|
|
433
457
|
if n == 15 || n == 16 || n == 17 || n == 1023 || n == 1024 || n == 1025
|
|
434
458
|
assert l.length() == n
|
|
435
459
|
assert l.get(n - 1).unwrap() == i
|
|
436
460
|
assert l.get(0).unwrap() == 0
|
|
461
|
+
i = i + 1
|
|
437
462
|
assert l.get(16).unwrap() == 16
|
|
438
463
|
assert l.get(1024).unwrap() == 1024
|
|
439
464
|
|
|
440
465
|
test "list of strings keeps its elements through growth"
|
|
441
466
|
l := List[Str]()
|
|
467
|
+
i := 0
|
|
442
|
-
|
|
468
|
+
while i < 40
|
|
443
469
|
l.push("s{i}")
|
|
470
|
+
i = i + 1
|
|
444
471
|
assert l.get(39).unwrap() == "s39"
|
|
445
472
|
assert l.get(16).unwrap() == "s16"
|
plum-std/Map.plum
CHANGED
|
@@ -4,7 +4,6 @@ import std/Try
|
|
|
4
4
|
import std/Array
|
|
5
5
|
import std/Number
|
|
6
6
|
import std/Str
|
|
7
|
-
import std/Range
|
|
8
7
|
|
|
9
8
|
# A Pair is a grouping of a key with a value.
|
|
10
9
|
#
|
|
@@ -54,10 +53,12 @@ enum Map[K, V] =
|
|
|
54
53
|
# Gets the value for `k`, or `Err(KeyNotFound)` if absent.
|
|
55
54
|
fun get(self, k: K) -> V ! [KeyNotFound] =
|
|
56
55
|
bucket := self.buckets.get(self.bucketIndex(k))
|
|
56
|
+
i := 0
|
|
57
|
-
|
|
57
|
+
while i < bucket.size
|
|
58
58
|
pair := bucket.data.get(i)
|
|
59
59
|
if pair.key == k
|
|
60
60
|
return Ok(pair.val)
|
|
61
|
+
i = i + 1
|
|
61
62
|
return Err(KeyNotFound)
|
|
62
63
|
|
|
63
64
|
fun has(self, k: K) -> Bool =
|
|
@@ -69,11 +70,13 @@ enum Map[K, V] =
|
|
|
69
70
|
# unboundedly or leave a stale value reachable by `get`.
|
|
70
71
|
fun set(self, k: K, v: V) -> Unit =
|
|
71
72
|
bucket := self.buckets.get(self.bucketIndex(k))
|
|
73
|
+
i := 0
|
|
72
|
-
|
|
74
|
+
while i < bucket.size
|
|
73
75
|
pair := bucket.data.get(i)
|
|
74
76
|
if pair.key == k
|
|
75
77
|
pair.val = v
|
|
76
78
|
return
|
|
79
|
+
i = i + 1
|
|
77
80
|
bucket.push(Pair(key: k, val: v))
|
|
78
81
|
self.size = self.size + 1
|
|
79
82
|
|
|
@@ -85,11 +88,13 @@ enum Map[K, V] =
|
|
|
85
88
|
# Removes `k`'s entry, if present.
|
|
86
89
|
fun remove(self, k: K) -> Unit =
|
|
87
90
|
bucket := self.buckets.get(self.bucketIndex(k))
|
|
91
|
+
i := 0
|
|
88
|
-
|
|
92
|
+
while i < bucket.size
|
|
89
93
|
if bucket.data.get(i).key == k
|
|
90
94
|
bucket.removeAt(i)
|
|
91
95
|
self.size = self.size - 1
|
|
92
96
|
return
|
|
97
|
+
i = i + 1
|
|
93
98
|
|
|
94
99
|
fun length(self) -> Int =
|
|
95
100
|
return self.size
|
|
@@ -98,19 +103,25 @@ enum Map[K, V] =
|
|
|
98
103
|
return self.size == 0
|
|
99
104
|
|
|
100
105
|
fun clear(self) -> Unit =
|
|
106
|
+
i := 0
|
|
101
|
-
|
|
107
|
+
while i < self.buckets.length()
|
|
102
108
|
self.buckets.get(i).clear()
|
|
109
|
+
i = i + 1
|
|
103
110
|
self.size = 0
|
|
104
111
|
|
|
105
112
|
# Calls `cb` with each key and value in the map. Order is bucket order,
|
|
106
113
|
# then insertion order within a bucket — NOT overall insertion order (that
|
|
107
114
|
# would need a separate side list to track, which nothing here needs today).
|
|
108
115
|
fun each(self, cb: fn(K, V)) -> Unit =
|
|
116
|
+
i := 0
|
|
109
|
-
|
|
117
|
+
while i < self.buckets.length()
|
|
110
118
|
bucket := self.buckets.get(i)
|
|
119
|
+
j := 0
|
|
111
|
-
|
|
120
|
+
while j < bucket.size
|
|
112
121
|
pair := bucket.data.get(j)
|
|
113
122
|
cb(pair.key, pair.val)
|
|
123
|
+
j = j + 1
|
|
124
|
+
i = i + 1
|
|
114
125
|
|
|
115
126
|
fun keys(self) -> List[K] =
|
|
116
127
|
result := List[K]()
|
|
@@ -124,12 +135,16 @@ enum Map[K, V] =
|
|
|
124
135
|
|
|
125
136
|
fun map(self, cb: fn(K, V) -> Pair[X, Y]) -> Map[X, Y] =
|
|
126
137
|
result := Map[X, Y]()
|
|
138
|
+
i := 0
|
|
127
|
-
|
|
139
|
+
while i < self.buckets.length()
|
|
128
140
|
bucket := self.buckets.get(i)
|
|
141
|
+
j := 0
|
|
129
|
-
|
|
142
|
+
while j < bucket.size
|
|
130
143
|
pair := bucket.data.get(j)
|
|
131
144
|
mapped := cb(pair.key, pair.val)
|
|
132
145
|
result.set(mapped.key, mapped.val)
|
|
146
|
+
j = j + 1
|
|
147
|
+
i = i + 1
|
|
133
148
|
return result
|
|
134
149
|
|
|
135
150
|
test "get/set/has round-trip and set replaces an existing key in place"
|
plum-std/Random.plum
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
module std
|
|
2
2
|
import std/Number
|
|
3
|
-
import std/Range
|
|
4
3
|
|
|
5
4
|
# A seeded, deterministic pseudo-random generator (xorshift64*): the same seed
|
|
6
5
|
# always yields the same sequence, so AI and simulation replay exactly. Not
|
|
@@ -41,8 +40,10 @@ enum Random =
|
|
|
41
40
|
test "random_seed_deterministic: same seed gives the same first 100 values"
|
|
42
41
|
a := Random(seed: 12345)
|
|
43
42
|
b := Random(seed: 12345)
|
|
43
|
+
i := 0
|
|
44
|
-
|
|
44
|
+
while i < 100
|
|
45
45
|
assert a.nextInt() == b.nextInt()
|
|
46
|
+
i = i + 1
|
|
46
47
|
|
|
47
48
|
test "different seeds diverge and the stream is not constant"
|
|
48
49
|
a := Random(seed: 1)
|
|
@@ -55,22 +56,26 @@ test "different seeds diverge and the stream is not constant"
|
|
|
55
56
|
test "nextIntBelow stays in range and covers the range"
|
|
56
57
|
r := Random(seed: 99)
|
|
57
58
|
seen := 0
|
|
59
|
+
i := 0
|
|
58
|
-
|
|
60
|
+
while i < 1000
|
|
59
61
|
v := r.nextIntBelow(10)
|
|
60
62
|
assert v >= 0
|
|
61
63
|
assert v < 10
|
|
62
64
|
seen = seen | {1 << v}
|
|
65
|
+
i = i + 1
|
|
63
66
|
assert seen == 1023
|
|
64
67
|
assert r.nextIntBelow(0) == 0
|
|
65
68
|
|
|
66
69
|
test "nextFloat is in [0, 1) with a sane mean"
|
|
67
70
|
r := Random(seed: 5)
|
|
68
71
|
total := 0.0
|
|
72
|
+
i := 0
|
|
69
|
-
|
|
73
|
+
while i < 1000
|
|
70
74
|
f := r.nextFloat()
|
|
71
75
|
assert f >= 0.0
|
|
72
76
|
assert f < 1.0
|
|
73
77
|
total = total + f
|
|
78
|
+
i = i + 1
|
|
74
79
|
assert total > 400.0
|
|
75
80
|
assert total < 600.0
|
|
76
81
|
|
plum-std/Range.plum
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
module std
|
|
2
|
-
|
|
3
|
-
import std/Number
|
|
4
|
-
|
|
5
|
-
# `for i := range(0, n)`: start, start + 1, ... end - 1. The counter is bumped
|
|
6
|
-
# before the `yield`, so a `continue` in the body still advances it.
|
|
7
|
-
fun* range(start: Int, end: Int) *Int =
|
|
8
|
-
i := start
|
|
9
|
-
while i < end
|
|
10
|
-
cur := i
|
|
11
|
-
i = i + 1
|
|
12
|
-
yield cur
|
plum-std/Set.plum
CHANGED
|
@@ -2,7 +2,6 @@ module std
|
|
|
2
2
|
import std/Map
|
|
3
3
|
import std/List
|
|
4
4
|
import std/Number
|
|
5
|
-
import std/Range
|
|
6
5
|
|
|
7
6
|
# A set of unique `T`s, a thin layer over `Map[T, Bool]` (so `T` needs the same
|
|
8
7
|
# duck-typed `hash() -> Int` and `==` that `Map`'s keys do). `Set[Int]()`
|
|
@@ -38,8 +37,10 @@ enum Set[T] =
|
|
|
38
37
|
|
|
39
38
|
test "set_dedup: adding duplicates keeps one copy"
|
|
40
39
|
s := Set[Int]()
|
|
40
|
+
i := 0
|
|
41
|
-
|
|
41
|
+
while i < 100
|
|
42
42
|
s.add(i % 10)
|
|
43
|
+
i = i + 1
|
|
43
44
|
assert s.length() == 10
|
|
44
45
|
assert s.has(3)
|
|
45
46
|
assert s.has(10) == False
|