plum

#treesitter#compiler#wasm

git clone https://git.pyrossh.dev/plum

A statically typed, imperative programming language inspired by rust, python


1de3be3 — Peter John 2026-10-26T14:09:33+05:30
feat(std): Array.filled, Grid, Set, Deque, Heap, seeded Random; typed consts
plum-checker/src/lib.rs CHANGED
@@ -1225,7 +1225,7 @@ fn checkSourceInner(source: &ast::Source) -> (CheckResult<()>, TypedInfo) {
1225
1225
  // Generic items are checked once, structurally (`TApp`/`TParam`), on the
1226
1226
  // ORIGINAL source.
1227
1227
  let mut errors: Vec<CheckError> = checkBareGenericTypes(source);
1228
- let (global_env, methods, enum_variants, enum_params) = buildGlobalTables(source);
1228
+ let (mut global_env, methods, enum_variants, enum_params) = buildGlobalTables(source);
1229
1229
  let min_required = buildMinRequiredArgs(source);
1230
1230
  let ctx = CheckCtx {
1231
1231
  methods: &methods,
@@ -1233,6 +1233,20 @@ fn checkSourceInner(source: &ast::Source) -> (CheckResult<()>, TypedInfo) {
1233
1233
  enum_params: &enum_params,
1234
1234
  min_required: &min_required,
1235
1235
  };
1236
+ // Constants are inlined at their use sites, so their type is whatever their
1237
+ // initializer infers to (`FLAG = True`, `N = LIMIT * 2 + 1`, `T = List(1, 2)`).
1238
+ // `buildGlobalTables` only had literals to go on; now that every method/enum
1239
+ // table exists, re-infer each one with the full inferer. Two passes let a const
1240
+ // reference one declared later.
1241
+ for _ in 0..2 {
1242
+ for item in &source.items {
1243
+ if let ast::Item::Const(c) = item {
1244
+ if let Ok(ty) = inferExpr(&c.value, &global_env, &ctx) {
1245
+ global_env.insert(c.name.clone(), TypeScheme::mono(ty));
1246
+ }
1247
+ }
1248
+ }
1249
+ }
1236
1250
 
1237
1251
  // A discriminant enum (`enum Foo(n: Int) = ...`) requires every variant to supply
1238
1252
  // exactly one value per declared param, unified against that param's type. An
plum-examples/constants.plum ADDED
@@ -0,0 +1,33 @@
1
+ module constants
2
+ import std/Number
3
+ import std/Str
4
+
5
+ # Top-level constants may appear anywhere at top level (before or after enums
6
+ # and fns), hold Int/Float/Str/Bool literals or expressions over other
7
+ # constants, and are usable inside fns, methods and generics. They are inlined
8
+ # at each use. NOTE: constant names are global across imported modules.
9
+ GRID_W = 8
10
+ GRID_H = GRID_W * 2
11
+ CELL_SCALE = 2.5
12
+ GAME_NAME = "plum"
13
+ DEBUG_DRAW = True
14
+
15
+ enum Tile =
16
+ | Tile(x: Int)
17
+
18
+ fun offset(self) -> Int = GRID_W + self.x
19
+
20
+ enum Wrap[T] =
21
+ | Wrap(v: T)
22
+
23
+ fun area(self) -> Int = GRID_W * GRID_H
24
+
25
+ fun scaled(n: Float) -> Float = n * CELL_SCALE
26
+
27
+ test "consts are usable in fns, methods, generics and derived consts"
28
+ assert GRID_H == 16
29
+ assert Tile(x: 1).offset() == 9
30
+ assert Wrap(v: 3.5).area() == 128
31
+ assert scaled(2.0) == 5.0
32
+ assert GAME_NAME == "plum"
33
+ assert DEBUG_DRAW
plum-examples/typed_arrays.plum ADDED
@@ -0,0 +1,41 @@
1
+ module typed_arrays
2
+ import std/Number
3
+ import std/Array
4
+ import std/Grid
5
+
6
+ # Tile-grid style data: Array[Int] / Array[Float] / Array[Bool] are native
7
+ # unboxed wasm arrays (i64 / f64 / i32 elements), not boxed anyref arrays.
8
+ fun sumInts(n: Int) -> Int =
9
+ a := Array.filled(n, 3)
10
+ total := 0
11
+ for i := range n
12
+ total = total + a.get(i)
13
+ return total
14
+
15
+ fun sumFloats(n: Int) -> Float =
16
+ a := Array.filled(n, 0.5)
17
+ total := 0.0
18
+ for i := range n
19
+ total = total + a.get(i)
20
+ return total
21
+
22
+ fun countTrue(n: Int) -> Int =
23
+ a := Array.filled(n, False)
24
+ a.set(1, True)
25
+ a.set(3, True)
26
+ c := 0
27
+ for i := range n
28
+ if a.get(i)
29
+ c = c + 1
30
+ return c
31
+
32
+ test "array_int_native: filled arrays hold their values"
33
+ assert sumInts(10) == 30
34
+ assert sumFloats(4) == 2.0
35
+ assert countTrue(5) == 2
36
+
37
+ test "Grid[Int] indexes (x, y) row-major"
38
+ g := Grid.filled(3, 2, 0)
39
+ g.set(2, 1, 7)
40
+ assert g.get(2, 1) == 7
41
+ assert g.data.get(5) == 7
plum-std/Array.plum CHANGED
@@ -59,3 +59,13 @@ enum Array[T] =
59
59
  # grows by doubling through this.
60
60
  fun resize(self, n: Int) -> Array[T] =
61
61
  todo
62
+
63
+ # A new array of length `n` with every slot set to `v`:
64
+ # `Array[Int].filled(100, 0)`. Ordinary Plum on top of the intrinsics; for
65
+ # `Int`/`Float`/`Bool` the per-instantiation copy stores the values unboxed
66
+ # in a native `array (mut i64)` / `(mut f64)` / `(mut i32)`.
67
+ fun filled(n: Int, v: T) -> Array[T] =
68
+ a := Array[T]().resize(n)
69
+ for i := range n
70
+ a.set(i, v)
71
+ return a
plum-std/Deque.plum ADDED
@@ -0,0 +1,109 @@
1
+ module std
2
+ import std/Array
3
+ import std/List
4
+ import std/Try
5
+ import std/Number
6
+
7
+ # A double-ended queue: a ring buffer over `Array[T]`. `buf`'s length is the
8
+ # capacity, `head` the index of the front element, `size` the number in use;
9
+ # element `i` (0 = front) lives at `(head + i) % capacity`. Pushes at either end
10
+ # are O(1) amortized — when full, capacity doubles and the elements are copied
11
+ # out in front-to-back order (so the ring unwraps).
12
+ enum Deque[T] =
13
+ | Deque(buf: Array[T], head: Int, size: Int)
14
+
15
+ fun init() -> Deque[T] =
16
+ b := Array[T]().resize(8)
17
+ return Deque(buf: b, head: 0, size: 0)
18
+
19
+ fun length(self) -> Int =
20
+ return self.size
21
+
22
+ fun isEmpty(self) -> Bool =
23
+ return self.size == 0
24
+
25
+ # Doubles capacity when full, relinearizing the ring so `head` becomes 0.
26
+ fun grow(self) -> Unit =
27
+ if self.size < self.buf.length()
28
+ return
29
+ cap := self.buf.length()
30
+ nb := Array[T]().resize(cap * 2)
31
+ for i := range self.size
32
+ nb.set(i, self.buf.get({self.head + i} % cap))
33
+ self.buf = nb
34
+ self.head = 0
35
+
36
+ fun pushBack(self, v: T) -> Unit =
37
+ self.grow()
38
+ self.buf.set({self.head + self.size} % self.buf.length(), v)
39
+ self.size = self.size + 1
40
+
41
+ fun pushFront(self, v: T) -> Unit =
42
+ self.grow()
43
+ cap := self.buf.length()
44
+ self.head = {self.head + cap - 1} % cap
45
+ self.buf.set(self.head, v)
46
+ self.size = self.size + 1
47
+
48
+ # Removes and returns the front element, or `Err(DequeWasEmpty)`.
49
+ fun popFront(self) -> T ! [DequeWasEmpty] =
50
+ if self.size == 0
51
+ return Err(DequeWasEmpty)
52
+ v := self.buf.get(self.head)
53
+ self.head = {self.head + 1} % self.buf.length()
54
+ self.size = self.size - 1
55
+ Ok(v)
56
+
57
+ # Removes and returns the back element, or `Err(DequeWasEmpty)`.
58
+ fun popBack(self) -> T ! [DequeWasEmpty] =
59
+ if self.size == 0
60
+ return Err(DequeWasEmpty)
61
+ self.size = self.size - 1
62
+ Ok(self.buf.get({self.head + self.size} % self.buf.length()))
63
+
64
+ # The `i`'th element from the front, or `Err(OutOfBounds)`.
65
+ fun get(self, i: Int) -> T ! [OutOfBounds] =
66
+ if i < 0 || i >= self.size
67
+ return Err(OutOfBounds)
68
+ Ok(self.buf.get({self.head + i} % self.buf.length()))
69
+
70
+ fun clear(self) -> Unit =
71
+ self.head = 0
72
+ self.size = 0
73
+
74
+ test "deque_wraparound: head wraps past the end of the ring and growth unwraps it"
75
+ d := Deque[Int]()
76
+ # keep size small so head keeps advancing around the 8-slot ring
77
+ for i := range 50
78
+ d.pushBack(i)
79
+ d.pushBack(i + 1000)
80
+ assert d.popFront().unwrap() == i
81
+ assert d.popFront().unwrap() == i + 1000
82
+ assert d.isEmpty()
83
+ assert d.head != 0 || d.buf.length() > 8
84
+ # fill beyond capacity while head is mid-ring
85
+ d.pushBack(1)
86
+ d.pushBack(2)
87
+ d.popFront()
88
+ for i := range 20
89
+ d.pushBack(10 + i)
90
+ assert d.length() == 21
91
+ assert d.get(0).unwrap() == 2
92
+ assert d.get(20).unwrap() == 29
93
+ assert d.popBack().unwrap() == 29
94
+
95
+ test "pushFront builds the front in reverse order"
96
+ d := Deque[Int]()
97
+ for i := range 12
98
+ d.pushFront(i)
99
+ assert d.get(0).unwrap() == 11
100
+ assert d.get(11).unwrap() == 0
101
+ assert d.popFront().unwrap() == 11
102
+ assert d.popBack().unwrap() == 0
103
+ assert d.length() == 10
104
+
105
+ test "pop on empty is an error"
106
+ d := Deque[Int]()
107
+ assert d.popFront().isErr()
108
+ assert d.popBack().isErr()
109
+ assert d.get(0).isErr()
plum-std/Grid.plum ADDED
@@ -0,0 +1,49 @@
1
+ module std
2
+ import std/Array
3
+ import std/Number
4
+
5
+ # A fixed-size 2D grid stored row-major in ONE flat `Array[T]` (so a
6
+ # `Grid[Int]`/`Grid[Float]`/`Grid[Bool]` is a single native unboxed wasm
7
+ # array). Cell `(x, y)` lives at `y * w + x`; `x` is the column in `[0, w)`,
8
+ # `y` the row in `[0, h)`. `get`/`set` do no bounds check of their own beyond
9
+ # the underlying array's trap — use `inBounds` first when coordinates may stray
10
+ # off the map (neighbour scans, pathfinding).
11
+ enum Grid[T] =
12
+ | Grid(w: Int, h: Int, data: Array[T])
13
+
14
+ # `Grid.filled(w, h, v)` — a `w` x `h` grid with every cell set to `v`
15
+ # (`T` is inferred from `v`).
16
+ fun filled(w: Int, h: Int, v: T) -> Grid[T] =
17
+ return Grid(w: w, h: h, data: Array.filled(w * h, v))
18
+
19
+ # Flat index of cell `(x, y)`.
20
+ fun index(self, x: Int, y: Int) -> Int =
21
+ return y * self.w + x
22
+
23
+ fun inBounds(self, x: Int, y: Int) -> Bool =
24
+ return x >= 0 && y >= 0 && x < self.w && y < self.h
25
+
26
+ fun get(self, x: Int, y: Int) -> T =
27
+ return self.data.get(y * self.w + x)
28
+
29
+ fun set(self, x: Int, y: Int, v: T) -> Unit =
30
+ self.data.set(y * self.w + x, v)
31
+
32
+ test "Grid get/set address cells row-major and fill covers every cell"
33
+ g := Grid.filled(4, 3, 0)
34
+ assert g.w == 4
35
+ assert g.h == 3
36
+ g.set(3, 2, 9)
37
+ g.set(0, 1, 5)
38
+ assert g.get(3, 2) == 9
39
+ assert g.get(0, 1) == 5
40
+ assert g.get(1, 1) == 0
41
+ assert g.index(3, 2) == 11
42
+ assert g.data.get(11) == 9
43
+
44
+ test "Grid inBounds rejects negative and too-large coordinates"
45
+ g := Grid.filled(2, 2, 1.5)
46
+ assert g.inBounds(1, 1)
47
+ assert g.inBounds(2, 0) == False
48
+ assert g.inBounds(0, -1) == False
49
+ assert g.get(1, 1) == 1.5
plum-std/Heap.plum ADDED
@@ -0,0 +1,105 @@
1
+ module std
2
+ import std/Array
3
+ import std/List
4
+ import std/Try
5
+ import std/Number
6
+
7
+ # A binary MIN-heap ordered by a key function: `pop` returns the element whose
8
+ # `key(x)` is smallest. Ordering by an explicit `fn(T) -> Float` (rather than a
9
+ # duck-typed `lt` method) lets one type be queued under different priorities and
10
+ # fits A* directly: `Heap[Node](key: |n| n.f)`. The key is recomputed on each
11
+ # comparison, so keep it cheap and don't mutate an element's key while it is in
12
+ # the heap. `push`/`pop` are O(log n); `peek`/`length` are O(1). Equal keys pop in
13
+ # no guaranteed order.
14
+ enum Heap[T] =
15
+ | Heap(items: List[T], key: fn(T) -> Float)
16
+
17
+ fun length(self) -> Int =
18
+ return self.items.length()
19
+
20
+ fun isEmpty(self) -> Bool =
21
+ return self.items.length() == 0
22
+
23
+ fun clear(self) -> Unit =
24
+ self.items.clear()
25
+
26
+ fun swap(self, i: Int, j: Int) -> Unit =
27
+ a := self.items.data.get(i)
28
+ self.items.data.set(i, self.items.data.get(j))
29
+ self.items.data.set(j, a)
30
+
31
+ fun less(self, i: Int, j: Int) -> Bool =
32
+ a := self.items.data.get(i)
33
+ b := self.items.data.get(j)
34
+ return self.key(a) < self.key(b)
35
+
36
+ fun siftUp(self, start: Int) -> Unit =
37
+ i := start
38
+ while i > 0
39
+ p := {i - 1} / 2
40
+ if !self.less(i, p)
41
+ return
42
+ self.swap(i, p)
43
+ i = p
44
+
45
+ fun siftDown(self, start: Int) -> Unit =
46
+ i := start
47
+ n := self.items.length()
48
+ while True
49
+ l := 2 * i + 1
50
+ r := l + 1
51
+ m := i
52
+ if l < n && self.less(l, m)
53
+ m = l
54
+ if r < n && self.less(r, m)
55
+ m = r
56
+ if m == i
57
+ return
58
+ self.swap(i, m)
59
+ i = m
60
+
61
+ fun push(self, v: T) -> Unit =
62
+ self.items.push(v)
63
+ self.siftUp(self.items.length() - 1)
64
+
65
+ # The smallest element without removing it, or `Err(HeapWasEmpty)`.
66
+ fun peek(self) -> T ! [HeapWasEmpty] =
67
+ if self.items.length() == 0
68
+ return Err(HeapWasEmpty)
69
+ Ok(self.items.data.get(0))
70
+
71
+ # Removes and returns the smallest element, or `Err(HeapWasEmpty)`.
72
+ fun pop(self) -> T ! [HeapWasEmpty] =
73
+ n := self.items.length()
74
+ if n == 0
75
+ return Err(HeapWasEmpty)
76
+ top := self.items.data.get(0)
77
+ self.items.data.set(0, self.items.data.get(n - 1))
78
+ self.items.size = n - 1
79
+ self.siftDown(0)
80
+ Ok(top)
81
+
82
+ test "heap_pop_order: 1000 pseudo-random keys pop in ascending order"
83
+ h := Heap[Int](items: List[Int](), key: |x| Float(x))
84
+ x := 12345
85
+ for i := range 1000
86
+ x = {x * 1103515245 + 12345} % 2147483648
87
+ h.push(x % 100000)
88
+ assert h.length() == 1000
89
+ prev := -1
90
+ for i := range 1000
91
+ v := h.pop().unwrap()
92
+ assert v >= prev
93
+ prev = v
94
+ assert h.isEmpty()
95
+ assert h.pop().isErr()
96
+
97
+ test "peek shows the minimum and Float keys order records"
98
+ h := Heap[Float](items: List[Float](), key: |x| 0.0 - x)
99
+ h.push(1.5)
100
+ h.push(9.5)
101
+ h.push(4.0)
102
+ assert h.peek().unwrap() == 9.5
103
+ assert h.pop().unwrap() == 9.5
104
+ assert h.pop().unwrap() == 4.0
105
+ assert h.length() == 1
plum-std/Random.plum ADDED
@@ -0,0 +1,78 @@
1
+ module std
2
+ import std/Number
3
+
4
+ # A seeded, deterministic pseudo-random generator (xorshift64*): the same seed
5
+ # always yields the same sequence, so AI and simulation replay exactly. Not
6
+ # cryptographically secure. For wall-clock randomness use `random()` from
7
+ # `std/Number` instead.
8
+ #
9
+ # `Random(seed: 42)` — `seed` holds the generator's CURRENT state and advances
10
+ # on every draw (a seed of 0 is a fixed point of xorshift, so it is silently
11
+ # replaced by a non-zero constant on first use).
12
+ #
13
+ # `Int` is signed 64-bit and `>>` is arithmetic, so the logical right shifts
14
+ # xorshift needs are done as `{x >> k} & mask`.
15
+ enum Random =
16
+ | Random(seed: Int)
17
+
18
+ # Advances the state and returns 64 well-mixed bits (may be negative).
19
+ fun nextInt(self) -> Int =
20
+ x := self.seed
21
+ if x == 0
22
+ x = 88172645463325252
23
+ x = x ^ { {x >> 12} & 0xFFFFFFFFFFFFF }
24
+ x = x ^ { x << 25 }
25
+ x = x ^ { {x >> 27} & 0x1FFFFFFFFF }
26
+ self.seed = x
27
+ return x * 2685821657736338717
28
+
29
+ # A uniformly distributed Int in [0, n). Returns 0 for n <= 0.
30
+ fun nextIntBelow(self, n: Int) -> Int =
31
+ if n <= 0
32
+ return 0
33
+ return { self.nextInt() & 0x7FFFFFFFFFFFFFFF } % n
34
+
35
+ # A uniformly distributed Float in [0.0, 1.0) with 53 bits of precision.
36
+ fun nextFloat(self) -> Float =
37
+ bits := {self.nextInt() >> 11} & 0x1FFFFFFFFFFFFF
38
+ return Float(bits) / 9007199254740992.0
39
+
40
+ test "random_seed_deterministic: same seed gives the same first 100 values"
41
+ a := Random(seed: 12345)
42
+ b := Random(seed: 12345)
43
+ for i := range 100
44
+ assert a.nextInt() == b.nextInt()
45
+
46
+ test "different seeds diverge and the stream is not constant"
47
+ a := Random(seed: 1)
48
+ b := Random(seed: 2)
49
+ assert a.nextInt() != b.nextInt()
50
+ c := Random(seed: 7)
51
+ first := c.nextInt()
52
+ assert c.nextInt() != first
53
+
54
+ test "nextIntBelow stays in range and covers the range"
55
+ r := Random(seed: 99)
56
+ seen := 0
57
+ for i := range 1000
58
+ v := r.nextIntBelow(10)
59
+ assert v >= 0
60
+ assert v < 10
61
+ seen = seen | {1 << v}
62
+ assert seen == 1023
63
+ assert r.nextIntBelow(0) == 0
64
+
65
+ test "nextFloat is in [0, 1) with a sane mean"
66
+ r := Random(seed: 5)
67
+ total := 0.0
68
+ for i := range 1000
69
+ f := r.nextFloat()
70
+ assert f >= 0.0
71
+ assert f < 1.0
72
+ total = total + f
73
+ assert total > 400.0
74
+ assert total < 600.0
75
+
76
+ test "seed 0 still produces a varying stream"
77
+ r := Random(seed: 0)
78
+ assert r.nextInt() != r.nextInt()
plum-std/Set.plum ADDED
@@ -0,0 +1,68 @@
1
+ module std
2
+ import std/Map
3
+ import std/List
4
+ import std/Number
5
+
6
+ # A set of unique `T`s, a thin layer over `Map[T, Bool]` (so `T` needs the same
7
+ # duck-typed `hash() -> Int` and `==` that `Map`'s keys do). `Set[Int]()`
8
+ # builds an empty one.
9
+ enum Set[T] =
10
+ | Set(map: Map[T, Bool])
11
+
12
+ fun init() -> Set[T] =
13
+ return Set(map: Map[T, Bool]())
14
+
15
+ # Adds `v`; adding a value already present changes nothing.
16
+ fun add(self, v: T) -> Unit =
17
+ self.map.set(v, True)
18
+
19
+ fun has(self, v: T) -> Bool =
20
+ return self.map.has(v)
21
+
22
+ fun remove(self, v: T) -> Unit =
23
+ self.map.remove(v)
24
+
25
+ fun length(self) -> Int =
26
+ return self.map.length()
27
+
28
+ fun isEmpty(self) -> Bool =
29
+ return self.map.isEmpty()
30
+
31
+ fun clear(self) -> Unit =
32
+ self.map.clear()
33
+
34
+ # Every member, in no particular order.
35
+ fun toList(self) -> List[T] =
36
+ return self.map.keys()
37
+
38
+ test "set_dedup: adding duplicates keeps one copy"
39
+ s := Set[Int]()
40
+ for i := range 100
41
+ s.add(i % 10)
42
+ assert s.length() == 10
43
+ assert s.has(3)
44
+ assert s.has(10) == False
45
+ s.add(3)
46
+ assert s.length() == 10
47
+
48
+ test "remove and clear"
49
+ s := Set[Int]()
50
+ s.add(1)
51
+ s.add(2)
52
+ s.remove(1)
53
+ assert s.has(1) == False
54
+ assert s.has(2)
55
+ assert s.length() == 1
56
+ s.remove(99)
57
+ assert s.length() == 1
58
+ s.clear()
59
+ assert s.isEmpty()
60
+
61
+ test "toList returns each member once"
62
+ s := Set[Int]()
63
+ s.add(5)
64
+ s.add(5)
65
+ s.add(7)
66
+ l := s.toList()
67
+ assert l.length() == 2
68
+ assert l.get(0).unwrap() + l.get(1).unwrap() == 12
plum-wasm-codegen/tests/examples_test.rs CHANGED
@@ -210,3 +210,58 @@ fn ioExampleCompilesAndRunsCorrectly() {
210
210
  vec!["hello from plum".to_string(), "hello, world!".to_string()]
211
211
  );
212
212
  }
213
+
214
+ /// L1 `array_int_native`: `Array[Int]`/`Array[Float]`/`Array[Bool]` lower to
215
+ /// native unboxed wasm-gc arrays (`array (mut i64)` / `(mut f64)` / `(mut i32)`),
216
+ /// not the boxed `array (mut anyref)`.
217
+ #[test]
218
+ fn array_int_native() {
219
+ use wasmparser::{CompositeInnerType, Parser, Payload, StorageType, ValType};
220
+ let bytes = assertCompiles("typed_arrays.plum");
221
+ let mut elems: Vec<ValType> = Vec::new();
222
+ for payload in Parser::new(0).parse_all(&bytes) {
223
+ if let Payload::TypeSection(reader) = payload.unwrap() {
224
+ for rec in reader {
225
+ for sub in rec.unwrap().into_types() {
226
+ if let CompositeInnerType::Array(a) = &sub.composite_type.inner {
227
+ if a.0.mutable {
228
+ if let StorageType::Val(v) = a.0.element_type {
229
+ elems.push(v);
230
+ }
231
+ }
232
+ }
233
+ }
234
+ }
235
+ }
236
+ }
237
+ assert!(elems.contains(&ValType::I64), "no (mut i64) array in {:?}", elems);
238
+ assert!(elems.contains(&ValType::F64), "no (mut f64) array in {:?}", elems);
239
+ assert!(elems.contains(&ValType::I32), "no (mut i32) array in {:?}", elems);
240
+ }
241
+
242
+ /// L1 `const_table_1000_entries`: 1000 top-level consts (placed before and
243
+ /// between other decls) plus a 1000-element list literal parse, type-check and
244
+ /// are usable inside methods.
245
+ #[test]
246
+ fn const_table_1000_entries() {
247
+ let mut src = String::from("module t\nimport std/Number\nimport std/List\n\n");
248
+ for i in 0..500 {
249
+ src.push_str(&format!("E{} = {}\n", i, i * 3 + 1));
250
+ }
251
+ src.push_str("enum Holder =\n | Holder(n: Int)\n\n fun at(self) -> Int = E499 + self.n\n\n");
252
+ for i in 500..1000 {
253
+ src.push_str(&format!("E{} = {}\n", i, i * 3 + 1));
254
+ }
255
+ src.push_str("FLAG = True\nDOUBLE = E999 * 2 + 1\n\nfun table() -> List[Int] =\n return List(");
256
+ let items: Vec<String> = (0..1000).map(|i| (i * i).to_string()).collect();
257
+ src.push_str(&items.join(", "));
258
+ src.push_str(")\n\nfun probe() -> Int =\n return E999 + DOUBLE\n");
259
+ let dir = std::env::temp_dir().join(format!("plum_const_table_{}", std::process::id()));
260
+ std::fs::create_dir_all(&dir).unwrap();
261
+ let path = dir.join("t.plum");
262
+ std::fs::write(&path, src).unwrap();
263
+ let source = plum_core::loadAndMerge(&path, &libPath()).expect("1000-const file should load");
264
+ let bytes = compileSource(&source).expect("1000-const file should compile");
265
+ assert!(wasmparser::validate(&bytes).is_ok());
266
+ std::fs::remove_dir_all(&dir).ok();
267
+ }