plum

#treesitter#compiler#wasm

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

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


5d8ada1Peter John 2026-07-19T21:40:57+05:30
feat(plum-wasm-codegen): class/method codegen, strings, match lowering
Cargo.lock CHANGED
@@ -2,13 +2,22 @@
2
2
  # It is not intended for manual editing.
3
3
  version = 4
4
4
 
5
+ [[package]]
6
+ name = "addr2line"
7
+ version = "0.24.2"
8
+ source = "registry+https://github.com/rust-lang/crates.io-index"
9
+ checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
10
+ dependencies = [
11
+ "gimli 0.31.1",
12
+ ]
13
+
5
14
  [[package]]
6
15
  name = "addr2line"
7
16
  version = "0.25.1"
8
17
  source = "registry+https://github.com/rust-lang/crates.io-index"
9
18
  checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b"
10
19
  dependencies = [
11
- "gimli",
20
+ "gimli 0.32.3",
12
21
  ]
13
22
 
14
23
  [[package]]
@@ -38,6 +47,12 @@ dependencies = [
38
47
  "memchr",
39
48
  ]
40
49
 
50
+ [[package]]
51
+ name = "allocator-api2"
52
+ version = "0.2.21"
53
+ source = "registry+https://github.com/rust-lang/crates.io-index"
54
+ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
55
+
41
56
  [[package]]
42
57
  name = "anstream"
43
58
  version = "1.0.0"
@@ -74,7 +89,7 @@ version = "1.1.5"
74
89
  source = "registry+https://github.com/rust-lang/crates.io-index"
75
90
  checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
76
91
  dependencies = [
77
- "windows-sys",
92
+ "windows-sys 0.61.2",
78
93
  ]
79
94
 
80
95
  [[package]]
@@ -85,7 +100,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
85
100
  dependencies = [
86
101
  "anstyle",
87
102
  "once_cell_polyfill",
88
- "windows-sys",
103
+ "windows-sys 0.61.2",
89
104
  ]
90
105
 
91
106
  [[package]]
@@ -94,17 +109,43 @@ version = "1.0.104"
94
109
  source = "registry+https://github.com/rust-lang/crates.io-index"
95
110
  checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
96
111
 
112
+ [[package]]
113
+ name = "ar_archive_writer"
114
+ version = "0.5.2"
115
+ source = "registry+https://github.com/rust-lang/crates.io-index"
116
+ checksum = "4087686b4b0a3427190bae57a1d9a478dbb2d40c5dc1bd6e2b6d797913bdd348"
117
+ dependencies = [
118
+ "object 0.37.3",
119
+ ]
120
+
121
+ [[package]]
122
+ name = "arbitrary"
123
+ version = "1.4.2"
124
+ source = "registry+https://github.com/rust-lang/crates.io-index"
125
+ checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
126
+
127
+ [[package]]
128
+ name = "async-trait"
129
+ version = "0.1.91"
130
+ source = "registry+https://github.com/rust-lang/crates.io-index"
131
+ checksum = "ae36dc4177970ef04fde5178d3e2429882def40e57a451f919c098f72baa6cec"
132
+ dependencies = [
133
+ "proc-macro2",
134
+ "quote",
135
+ "syn 3.0.0",
136
+ ]
137
+
97
138
  [[package]]
98
139
  name = "backtrace"
99
140
  version = "0.3.76"
100
141
  source = "registry+https://github.com/rust-lang/crates.io-index"
101
142
  checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6"
102
143
  dependencies = [
103
- "addr2line",
144
+ "addr2line 0.25.1",
104
145
  "cfg-if",
105
146
  "libc",
106
147
  "miniz_oxide",
107
- "object",
148
+ "object 0.37.3",
108
149
  "rustc-demangle",
109
150
  "windows-link",
110
151
  ]
@@ -118,17 +159,41 @@ dependencies = [
118
159
  "backtrace",
119
160
  ]
120
161
 
162
+ [[package]]
163
+ name = "base64"
164
+ version = "0.21.7"
165
+ source = "registry+https://github.com/rust-lang/crates.io-index"
166
+ checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
167
+
121
168
  [[package]]
122
169
  name = "bitflags"
123
170
  version = "2.13.1"
124
171
  source = "registry+https://github.com/rust-lang/crates.io-index"
125
172
  checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
126
173
 
174
+ [[package]]
175
+ name = "block-buffer"
176
+ version = "0.10.4"
177
+ source = "registry+https://github.com/rust-lang/crates.io-index"
178
+ checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
179
+ dependencies = [
180
+ "generic-array",
181
+ ]
182
+
127
183
  [[package]]
128
184
  name = "bumpalo"
129
185
  version = "3.20.3"
130
186
  source = "registry+https://github.com/rust-lang/crates.io-index"
131
187
  checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
188
+ dependencies = [
189
+ "allocator-api2",
190
+ ]
191
+
192
+ [[package]]
193
+ name = "byteorder"
194
+ version = "1.5.0"
195
+ source = "registry+https://github.com/rust-lang/crates.io-index"
196
+ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
132
197
 
133
198
  [[package]]
134
199
  name = "cc"
@@ -137,6 +202,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
137
202
  checksum = "c89588d05638b5b4594a3348a2d6c20277e43a7f5c5202b05cc56888475a47b8"
138
203
  dependencies = [
139
204
  "find-msvc-tools",
205
+ "jobserver",
206
+ "libc",
140
207
  "shlex",
141
208
  ]
142
209
 
@@ -186,12 +253,155 @@ version = "1.1.0"
186
253
  source = "registry+https://github.com/rust-lang/crates.io-index"
187
254
  checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
188
255
 
256
+ [[package]]
257
+ name = "cobs"
258
+ version = "0.3.0"
259
+ source = "registry+https://github.com/rust-lang/crates.io-index"
260
+ checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1"
261
+ dependencies = [
262
+ "thiserror 2.0.19",
263
+ ]
264
+
189
265
  [[package]]
190
266
  name = "colorchoice"
191
267
  version = "1.0.5"
192
268
  source = "registry+https://github.com/rust-lang/crates.io-index"
193
269
  checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
194
270
 
271
+ [[package]]
272
+ name = "cpp_demangle"
273
+ version = "0.4.5"
274
+ source = "registry+https://github.com/rust-lang/crates.io-index"
275
+ checksum = "f2bb79cb74d735044c972aae58ed0aaa9a837e85b01106a54c39e42e97f62253"
276
+ dependencies = [
277
+ "cfg-if",
278
+ ]
279
+
280
+ [[package]]
281
+ name = "cpufeatures"
282
+ version = "0.2.17"
283
+ source = "registry+https://github.com/rust-lang/crates.io-index"
284
+ checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
285
+ dependencies = [
286
+ "libc",
287
+ ]
288
+
289
+ [[package]]
290
+ name = "cranelift-bforest"
291
+ version = "0.115.1"
292
+ source = "registry+https://github.com/rust-lang/crates.io-index"
293
+ checksum = "88c1d02b72b6c411c0a2e92b25ed791ad5d071184193c08a34aa0fdcdf000b72"
294
+ dependencies = [
295
+ "cranelift-entity",
296
+ ]
297
+
298
+ [[package]]
299
+ name = "cranelift-bitset"
300
+ version = "0.115.1"
301
+ source = "registry+https://github.com/rust-lang/crates.io-index"
302
+ checksum = "720b93bd86ebbb23ebfb2db1ed44d54b2ecbdbb2d034d485bc64aa605ee787ab"
303
+ dependencies = [
304
+ "serde",
305
+ "serde_derive",
306
+ ]
307
+
308
+ [[package]]
309
+ name = "cranelift-codegen"
310
+ version = "0.115.1"
311
+ source = "registry+https://github.com/rust-lang/crates.io-index"
312
+ checksum = "aed3d2d9914d30b460eedd7fd507720203023997bef71452ce84873f9c93537c"
313
+ dependencies = [
314
+ "bumpalo",
315
+ "cranelift-bforest",
316
+ "cranelift-bitset",
317
+ "cranelift-codegen-meta",
318
+ "cranelift-codegen-shared",
319
+ "cranelift-control",
320
+ "cranelift-entity",
321
+ "cranelift-isle",
322
+ "gimli 0.31.1",
323
+ "hashbrown 0.14.5",
324
+ "log",
325
+ "regalloc2",
326
+ "rustc-hash",
327
+ "serde",
328
+ "smallvec",
329
+ "target-lexicon",
330
+ ]
331
+
332
+ [[package]]
333
+ name = "cranelift-codegen-meta"
334
+ version = "0.115.1"
335
+ source = "registry+https://github.com/rust-lang/crates.io-index"
336
+ checksum = "888c188d32263ec9e048873ff0b68c700933600d553f4412417916828be25f8e"
337
+ dependencies = [
338
+ "cranelift-codegen-shared",
339
+ ]
340
+
341
+ [[package]]
342
+ name = "cranelift-codegen-shared"
343
+ version = "0.115.1"
344
+ source = "registry+https://github.com/rust-lang/crates.io-index"
345
+ checksum = "4ddd5f4114d04ce7e073dd74e2ad16541fc61970726fcc8b2d5644a154ee4127"
346
+
347
+ [[package]]
348
+ name = "cranelift-control"
349
+ version = "0.115.1"
350
+ source = "registry+https://github.com/rust-lang/crates.io-index"
351
+ checksum = "92cc4c98d6a4256a1600d93ccd3536f3e77da9b4ca2c279de786ac22876e67d6"
352
+ dependencies = [
353
+ "arbitrary",
354
+ ]
355
+
356
+ [[package]]
357
+ name = "cranelift-entity"
358
+ version = "0.115.1"
359
+ source = "registry+https://github.com/rust-lang/crates.io-index"
360
+ checksum = "760af4b5e051b5f82097a27274b917e3751736369fa73660513488248d27f23d"
361
+ dependencies = [
362
+ "cranelift-bitset",
363
+ "serde",
364
+ "serde_derive",
365
+ ]
366
+
367
+ [[package]]
368
+ name = "cranelift-frontend"
369
+ version = "0.115.1"
370
+ source = "registry+https://github.com/rust-lang/crates.io-index"
371
+ checksum = "c0bf77ec0f470621655ec7539860b5c620d4f91326654ab21b075b83900f8831"
372
+ dependencies = [
373
+ "cranelift-codegen",
374
+ "log",
375
+ "smallvec",
376
+ "target-lexicon",
377
+ ]
378
+
379
+ [[package]]
380
+ name = "cranelift-isle"
381
+ version = "0.115.1"
382
+ source = "registry+https://github.com/rust-lang/crates.io-index"
383
+ checksum = "4b665d0a6932c421620be184f9fc7f7adaf1b0bc2fa77bb7ac5177c49abf645b"
384
+
385
+ [[package]]
386
+ name = "cranelift-native"
387
+ version = "0.115.1"
388
+ source = "registry+https://github.com/rust-lang/crates.io-index"
389
+ checksum = "bb2e75d1bd43dfec10924798f15e6474f1dbf63b0024506551aa19394dbe72ab"
390
+ dependencies = [
391
+ "cranelift-codegen",
392
+ "libc",
393
+ "target-lexicon",
394
+ ]
395
+
396
+ [[package]]
397
+ name = "crc32fast"
398
+ version = "1.5.0"
399
+ source = "registry+https://github.com/rust-lang/crates.io-index"
400
+ checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
401
+ dependencies = [
402
+ "cfg-if",
403
+ ]
404
+
195
405
  [[package]]
196
406
  name = "crossbeam-deque"
197
407
  version = "0.8.7"
@@ -217,18 +427,89 @@ version = "0.8.22"
217
427
  source = "registry+https://github.com/rust-lang/crates.io-index"
218
428
  checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17"
219
429
 
430
+ [[package]]
431
+ name = "crypto-common"
432
+ version = "0.1.7"
433
+ source = "registry+https://github.com/rust-lang/crates.io-index"
434
+ checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
435
+ dependencies = [
436
+ "generic-array",
437
+ "typenum",
438
+ ]
439
+
440
+ [[package]]
441
+ name = "debugid"
442
+ version = "0.8.0"
443
+ source = "registry+https://github.com/rust-lang/crates.io-index"
444
+ checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d"
445
+ dependencies = [
446
+ "uuid",
447
+ ]
448
+
220
449
  [[package]]
221
450
  name = "diff"
222
451
  version = "0.1.13"
223
452
  source = "registry+https://github.com/rust-lang/crates.io-index"
224
453
  checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
225
454
 
455
+ [[package]]
456
+ name = "digest"
457
+ version = "0.10.7"
458
+ source = "registry+https://github.com/rust-lang/crates.io-index"
459
+ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
460
+ dependencies = [
461
+ "block-buffer",
462
+ "crypto-common",
463
+ ]
464
+
465
+ [[package]]
466
+ name = "directories-next"
467
+ version = "2.0.0"
468
+ source = "registry+https://github.com/rust-lang/crates.io-index"
469
+ checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc"
470
+ dependencies = [
471
+ "cfg-if",
472
+ "dirs-sys-next",
473
+ ]
474
+
475
+ [[package]]
476
+ name = "dirs-sys-next"
477
+ version = "0.1.2"
478
+ source = "registry+https://github.com/rust-lang/crates.io-index"
479
+ checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
480
+ dependencies = [
481
+ "libc",
482
+ "redox_users",
483
+ "winapi",
484
+ ]
485
+
226
486
  [[package]]
227
487
  name = "either"
228
488
  version = "1.16.0"
229
489
  source = "registry+https://github.com/rust-lang/crates.io-index"
230
490
  checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e"
231
491
 
492
+ [[package]]
493
+ name = "embedded-io"
494
+ version = "0.4.0"
495
+ source = "registry+https://github.com/rust-lang/crates.io-index"
496
+ checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced"
497
+
498
+ [[package]]
499
+ name = "embedded-io"
500
+ version = "0.6.1"
501
+ source = "registry+https://github.com/rust-lang/crates.io-index"
502
+ checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d"
503
+
504
+ [[package]]
505
+ name = "encoding_rs"
506
+ version = "0.8.35"
507
+ source = "registry+https://github.com/rust-lang/crates.io-index"
508
+ checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
509
+ dependencies = [
510
+ "cfg-if",
511
+ ]
512
+
232
513
  [[package]]
233
514
  name = "equivalent"
234
515
  version = "1.0.2"
@@ -242,15 +523,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
242
523
  checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
243
524
  dependencies = [
244
525
  "libc",
245
- "windows-sys",
526
+ "windows-sys 0.61.2",
246
527
  ]
247
528
 
529
+ [[package]]
530
+ name = "fallible-iterator"
531
+ version = "0.3.0"
532
+ source = "registry+https://github.com/rust-lang/crates.io-index"
533
+ checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
534
+
248
535
  [[package]]
249
536
  name = "find-msvc-tools"
250
537
  version = "0.1.9"
251
538
  source = "registry+https://github.com/rust-lang/crates.io-index"
252
539
  checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
253
540
 
541
+ [[package]]
542
+ name = "foldhash"
543
+ version = "0.1.5"
544
+ source = "registry+https://github.com/rust-lang/crates.io-index"
545
+ checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
546
+
254
547
  [[package]]
255
548
  name = "futures"
256
549
  version = "0.3.33"
@@ -339,6 +632,71 @@ dependencies = [
339
632
  "slab",
340
633
  ]
341
634
 
635
+ [[package]]
636
+ name = "fxhash"
637
+ version = "0.2.1"
638
+ source = "registry+https://github.com/rust-lang/crates.io-index"
639
+ checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
640
+ dependencies = [
641
+ "byteorder",
642
+ ]
643
+
644
+ [[package]]
645
+ name = "fxprof-processed-profile"
646
+ version = "0.6.0"
647
+ source = "registry+https://github.com/rust-lang/crates.io-index"
648
+ checksum = "27d12c0aed7f1e24276a241aadc4cb8ea9f83000f34bc062b7cc2d51e3b0fabd"
649
+ dependencies = [
650
+ "bitflags",
651
+ "debugid",
652
+ "fxhash",
653
+ "serde",
654
+ "serde_json",
655
+ ]
656
+
657
+ [[package]]
658
+ name = "generic-array"
659
+ version = "0.14.7"
660
+ source = "registry+https://github.com/rust-lang/crates.io-index"
661
+ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
662
+ dependencies = [
663
+ "typenum",
664
+ "version_check",
665
+ ]
666
+
667
+ [[package]]
668
+ name = "getrandom"
669
+ version = "0.2.17"
670
+ source = "registry+https://github.com/rust-lang/crates.io-index"
671
+ checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
672
+ dependencies = [
673
+ "cfg-if",
674
+ "libc",
675
+ "wasi",
676
+ ]
677
+
678
+ [[package]]
679
+ name = "getrandom"
680
+ version = "0.4.3"
681
+ source = "registry+https://github.com/rust-lang/crates.io-index"
682
+ checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
683
+ dependencies = [
684
+ "cfg-if",
685
+ "libc",
686
+ "r-efi",
687
+ ]
688
+
689
+ [[package]]
690
+ name = "gimli"
691
+ version = "0.31.1"
692
+ source = "registry+https://github.com/rust-lang/crates.io-index"
693
+ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
694
+ dependencies = [
695
+ "fallible-iterator",
696
+ "indexmap",
697
+ "stable_deref_trait",
698
+ ]
699
+
342
700
  [[package]]
343
701
  name = "gimli"
344
702
  version = "0.32.3"
@@ -355,6 +713,16 @@ dependencies = [
355
713
  "serde",
356
714
  ]
357
715
 
716
+ [[package]]
717
+ name = "hashbrown"
718
+ version = "0.15.5"
719
+ source = "registry+https://github.com/rust-lang/crates.io-index"
720
+ checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
721
+ dependencies = [
722
+ "foldhash",
723
+ "serde",
724
+ ]
725
+
358
726
  [[package]]
359
727
  name = "hashbrown"
360
728
  version = "0.17.1"
@@ -367,6 +735,12 @@ version = "0.5.0"
367
735
  source = "registry+https://github.com/rust-lang/crates.io-index"
368
736
  checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
369
737
 
738
+ [[package]]
739
+ name = "id-arena"
740
+ version = "2.3.0"
741
+ source = "registry+https://github.com/rust-lang/crates.io-index"
742
+ checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
743
+
370
744
  [[package]]
371
745
  name = "indexmap"
372
746
  version = "2.14.0"
@@ -400,12 +774,51 @@ dependencies = [
400
774
  "either",
401
775
  ]
402
776
 
777
+ [[package]]
778
+ name = "itertools"
779
+ version = "0.12.1"
780
+ source = "registry+https://github.com/rust-lang/crates.io-index"
781
+ checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
782
+ dependencies = [
783
+ "either",
784
+ ]
785
+
403
786
  [[package]]
404
787
  name = "itoa"
405
788
  version = "1.0.18"
406
789
  source = "registry+https://github.com/rust-lang/crates.io-index"
407
790
  checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
408
791
 
792
+ [[package]]
793
+ name = "ittapi"
794
+ version = "0.4.0"
795
+ source = "registry+https://github.com/rust-lang/crates.io-index"
796
+ checksum = "6b996fe614c41395cdaedf3cf408a9534851090959d90d54a535f675550b64b1"
797
+ dependencies = [
798
+ "anyhow",
799
+ "ittapi-sys",
800
+ "log",
801
+ ]
802
+
803
+ [[package]]
804
+ name = "ittapi-sys"
805
+ version = "0.4.0"
806
+ source = "registry+https://github.com/rust-lang/crates.io-index"
807
+ checksum = "52f5385394064fa2c886205dba02598013ce83d3e92d33dbdc0c52fe0e7bf4fc"
808
+ dependencies = [
809
+ "cc",
810
+ ]
811
+
812
+ [[package]]
813
+ name = "jobserver"
814
+ version = "0.1.35"
815
+ source = "registry+https://github.com/rust-lang/crates.io-index"
816
+ checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3"
817
+ dependencies = [
818
+ "getrandom 0.4.3",
819
+ "libc",
820
+ ]
821
+
409
822
  [[package]]
410
823
  name = "js-sys"
411
824
  version = "0.3.77"
@@ -422,6 +835,12 @@ version = "0.2.7"
422
835
  source = "registry+https://github.com/rust-lang/crates.io-index"
423
836
  checksum = "c83bff1d572d6b9aeef67ddfc8448e4a3737909cb28e81f97c791b9018703e52"
424
837
 
838
+ [[package]]
839
+ name = "leb128fmt"
840
+ version = "0.1.0"
841
+ source = "registry+https://github.com/rust-lang/crates.io-index"
842
+ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
843
+
425
844
  [[package]]
426
845
  name = "libc"
427
846
  version = "0.2.186"
@@ -429,23 +848,62 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
429
848
  checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
430
849
 
431
850
  [[package]]
432
- name = "linux-raw-sys"
851
+ name = "libm"
433
- version = "0.12.1"
852
+ version = "0.2.16"
434
853
  source = "registry+https://github.com/rust-lang/crates.io-index"
435
- checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
854
+ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
436
855
 
437
856
  [[package]]
438
- name = "log"
857
+ name = "libredox"
439
- version = "0.4.33"
858
+ version = "0.1.18"
440
859
  source = "registry+https://github.com/rust-lang/crates.io-index"
860
+ checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652"
861
+ dependencies = [
862
+ "libc",
863
+ ]
864
+
865
+ [[package]]
866
+ name = "linux-raw-sys"
867
+ version = "0.4.15"
868
+ source = "registry+https://github.com/rust-lang/crates.io-index"
869
+ checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
870
+
871
+ [[package]]
872
+ name = "linux-raw-sys"
873
+ version = "0.12.1"
874
+ source = "registry+https://github.com/rust-lang/crates.io-index"
875
+ checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
876
+
877
+ [[package]]
878
+ name = "log"
879
+ version = "0.4.33"
880
+ source = "registry+https://github.com/rust-lang/crates.io-index"
441
- checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
881
+ checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
442
-
882
+
443
- [[package]]
883
+ [[package]]
884
+ name = "mach2"
885
+ version = "0.4.3"
886
+ source = "registry+https://github.com/rust-lang/crates.io-index"
887
+ checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44"
888
+ dependencies = [
889
+ "libc",
890
+ ]
891
+
892
+ [[package]]
444
893
  name = "memchr"
445
894
  version = "2.7.4"
446
895
  source = "registry+https://github.com/rust-lang/crates.io-index"
447
896
  checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
448
897
 
898
+ [[package]]
899
+ name = "memfd"
900
+ version = "0.6.5"
901
+ source = "registry+https://github.com/rust-lang/crates.io-index"
902
+ checksum = "ad38eb12aea514a0466ea40a80fd8cc83637065948eb4a426e4aa46261175227"
903
+ dependencies = [
904
+ "rustix 1.1.4",
905
+ ]
906
+
449
907
  [[package]]
450
908
  name = "miette"
451
909
  version = "7.6.0"
@@ -485,6 +943,18 @@ dependencies = [
485
943
  "adler2",
486
944
  ]
487
945
 
946
+ [[package]]
947
+ name = "object"
948
+ version = "0.36.7"
949
+ source = "registry+https://github.com/rust-lang/crates.io-index"
950
+ checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
951
+ dependencies = [
952
+ "crc32fast",
953
+ "hashbrown 0.15.5",
954
+ "indexmap",
955
+ "memchr",
956
+ ]
957
+
488
958
  [[package]]
489
959
  name = "object"
490
960
  version = "0.37.3"
@@ -512,12 +982,24 @@ version = "4.3.0"
512
982
  source = "registry+https://github.com/rust-lang/crates.io-index"
513
983
  checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d"
514
984
 
985
+ [[package]]
986
+ name = "paste"
987
+ version = "1.0.15"
988
+ source = "registry+https://github.com/rust-lang/crates.io-index"
989
+ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
990
+
515
991
  [[package]]
516
992
  name = "pin-project-lite"
517
993
  version = "0.2.17"
518
994
  source = "registry+https://github.com/rust-lang/crates.io-index"
519
995
  checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
520
996
 
997
+ [[package]]
998
+ name = "pkg-config"
999
+ version = "0.3.33"
1000
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1001
+ checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
1002
+
521
1003
  [[package]]
522
1004
  name = "plum-checker"
523
1005
  version = "0.1.0"
@@ -554,11 +1036,25 @@ dependencies = [
554
1036
  name = "plum-wasm-codegen"
555
1037
  version = "0.1.0"
556
1038
  dependencies = [
1039
+ "plum-checker",
557
1040
  "plum-core",
558
1041
  "tree-sitter",
559
1042
  "tree-sitter-plum",
560
- "wasm-encoder",
1043
+ "wasm-encoder 0.220.1",
1044
+ "wasmparser 0.220.1",
561
- "wasmparser",
1045
+ "wasmtime",
1046
+ ]
1047
+
1048
+ [[package]]
1049
+ name = "postcard"
1050
+ version = "1.1.3"
1051
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1052
+ checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24"
1053
+ dependencies = [
1054
+ "cobs",
1055
+ "embedded-io 0.4.0",
1056
+ "embedded-io 0.6.1",
1057
+ "serde",
562
1058
  ]
563
1059
 
564
1060
  [[package]]
@@ -589,6 +1085,27 @@ dependencies = [
589
1085
  "unicode-ident",
590
1086
  ]
591
1087
 
1088
+ [[package]]
1089
+ name = "psm"
1090
+ version = "0.1.31"
1091
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1092
+ checksum = "645dbe486e346d9b5de3ef16ede18c26e6c70ad97418f4874b8b1889d6e761ea"
1093
+ dependencies = [
1094
+ "ar_archive_writer",
1095
+ "cc",
1096
+ ]
1097
+
1098
+ [[package]]
1099
+ name = "pulley-interpreter"
1100
+ version = "28.0.1"
1101
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1102
+ checksum = "8324e531de91a3c25021a30fb7862d39cc516b61fbb801176acb5ff279ea887b"
1103
+ dependencies = [
1104
+ "cranelift-bitset",
1105
+ "log",
1106
+ "sptr",
1107
+ ]
1108
+
592
1109
  [[package]]
593
1110
  name = "quote"
594
1111
  version = "1.0.47"
@@ -598,6 +1115,12 @@ dependencies = [
598
1115
  "proc-macro2",
599
1116
  ]
600
1117
 
1118
+ [[package]]
1119
+ name = "r-efi"
1120
+ version = "6.0.0"
1121
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1122
+ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
1123
+
601
1124
  [[package]]
602
1125
  name = "rayon"
603
1126
  version = "1.12.0"
@@ -618,6 +1141,31 @@ dependencies = [
618
1141
  "crossbeam-utils",
619
1142
  ]
620
1143
 
1144
+ [[package]]
1145
+ name = "redox_users"
1146
+ version = "0.4.6"
1147
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1148
+ checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
1149
+ dependencies = [
1150
+ "getrandom 0.2.17",
1151
+ "libredox",
1152
+ "thiserror 1.0.69",
1153
+ ]
1154
+
1155
+ [[package]]
1156
+ name = "regalloc2"
1157
+ version = "0.11.2"
1158
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1159
+ checksum = "dc06e6b318142614e4a48bc725abbf08ff166694835c43c9dae5a9009704639a"
1160
+ dependencies = [
1161
+ "allocator-api2",
1162
+ "bumpalo",
1163
+ "hashbrown 0.15.5",
1164
+ "log",
1165
+ "rustc-hash",
1166
+ "smallvec",
1167
+ ]
1168
+
621
1169
  [[package]]
622
1170
  name = "regex"
623
1171
  version = "1.13.1"
@@ -653,6 +1201,25 @@ version = "0.1.28"
653
1201
  source = "registry+https://github.com/rust-lang/crates.io-index"
654
1202
  checksum = "b74b56ffa8bb2830709a538c2cbcae9aa062db0d2a42563bfb09bdaae44020eb"
655
1203
 
1204
+ [[package]]
1205
+ name = "rustc-hash"
1206
+ version = "2.1.3"
1207
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1208
+ checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d"
1209
+
1210
+ [[package]]
1211
+ name = "rustix"
1212
+ version = "0.38.44"
1213
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1214
+ checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
1215
+ dependencies = [
1216
+ "bitflags",
1217
+ "errno",
1218
+ "libc",
1219
+ "linux-raw-sys 0.4.15",
1220
+ "windows-sys 0.59.0",
1221
+ ]
1222
+
656
1223
  [[package]]
657
1224
  name = "rustix"
658
1225
  version = "1.1.4"
@@ -662,8 +1229,8 @@ dependencies = [
662
1229
  "bitflags",
663
1230
  "errno",
664
1231
  "libc",
665
- "linux-raw-sys",
1232
+ "linux-raw-sys 0.12.1",
666
- "windows-sys",
1233
+ "windows-sys 0.61.2",
667
1234
  ]
668
1235
 
669
1236
  [[package]]
@@ -677,6 +1244,10 @@ name = "semver"
677
1244
  version = "1.0.28"
678
1245
  source = "registry+https://github.com/rust-lang/crates.io-index"
679
1246
  checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
1247
+ dependencies = [
1248
+ "serde",
1249
+ "serde_core",
1250
+ ]
680
1251
 
681
1252
  [[package]]
682
1253
  name = "serde"
@@ -722,6 +1293,26 @@ dependencies = [
722
1293
  "zmij",
723
1294
  ]
724
1295
 
1296
+ [[package]]
1297
+ name = "serde_spanned"
1298
+ version = "0.6.9"
1299
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1300
+ checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
1301
+ dependencies = [
1302
+ "serde",
1303
+ ]
1304
+
1305
+ [[package]]
1306
+ name = "sha2"
1307
+ version = "0.10.9"
1308
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1309
+ checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
1310
+ dependencies = [
1311
+ "cfg-if",
1312
+ "cpufeatures",
1313
+ "digest",
1314
+ ]
1315
+
725
1316
  [[package]]
726
1317
  name = "shlex"
727
1318
  version = "2.0.1"
@@ -734,6 +1325,27 @@ version = "0.4.12"
734
1325
  source = "registry+https://github.com/rust-lang/crates.io-index"
735
1326
  checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
736
1327
 
1328
+ [[package]]
1329
+ name = "smallvec"
1330
+ version = "1.15.2"
1331
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1332
+ checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
1333
+ dependencies = [
1334
+ "serde",
1335
+ ]
1336
+
1337
+ [[package]]
1338
+ name = "sptr"
1339
+ version = "0.3.2"
1340
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1341
+ checksum = "3b9b39299b249ad65f3b7e96443bad61c02ca5cd3589f46cb6d610a0fd6c0d6a"
1342
+
1343
+ [[package]]
1344
+ name = "stable_deref_trait"
1345
+ version = "1.2.1"
1346
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1347
+ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
1348
+
737
1349
  [[package]]
738
1350
  name = "streaming-iterator"
739
1351
  version = "0.1.9"
@@ -789,14 +1401,29 @@ dependencies = [
789
1401
  "unicode-ident",
790
1402
  ]
791
1403
 
1404
+ [[package]]
1405
+ name = "target-lexicon"
1406
+ version = "0.12.16"
1407
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1408
+ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
1409
+
1410
+ [[package]]
1411
+ name = "termcolor"
1412
+ version = "1.4.1"
1413
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1414
+ checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
1415
+ dependencies = [
1416
+ "winapi-util",
1417
+ ]
1418
+
792
1419
  [[package]]
793
1420
  name = "terminal_size"
794
1421
  version = "0.4.4"
795
1422
  source = "registry+https://github.com/rust-lang/crates.io-index"
796
1423
  checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874"
797
1424
  dependencies = [
798
- "rustix",
1425
+ "rustix 1.1.4",
799
- "windows-sys",
1426
+ "windows-sys 0.61.2",
800
1427
  ]
801
1428
 
802
1429
  [[package]]
@@ -809,13 +1436,33 @@ dependencies = [
809
1436
  "unicode-width 0.2.2",
810
1437
  ]
811
1438
 
1439
+ [[package]]
1440
+ name = "thiserror"
1441
+ version = "1.0.69"
1442
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1443
+ checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
1444
+ dependencies = [
1445
+ "thiserror-impl 1.0.69",
1446
+ ]
1447
+
812
1448
  [[package]]
813
1449
  name = "thiserror"
814
1450
  version = "2.0.19"
815
1451
  source = "registry+https://github.com/rust-lang/crates.io-index"
816
1452
  checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9"
817
1453
  dependencies = [
818
- "thiserror-impl",
1454
+ "thiserror-impl 2.0.19",
1455
+ ]
1456
+
1457
+ [[package]]
1458
+ name = "thiserror-impl"
1459
+ version = "1.0.69"
1460
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1461
+ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
1462
+ dependencies = [
1463
+ "proc-macro2",
1464
+ "quote",
1465
+ "syn 2.0.119",
819
1466
  ]
820
1467
 
821
1468
  [[package]]
@@ -850,6 +1497,47 @@ dependencies = [
850
1497
  "syn 2.0.119",
851
1498
  ]
852
1499
 
1500
+ [[package]]
1501
+ name = "toml"
1502
+ version = "0.8.23"
1503
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1504
+ checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
1505
+ dependencies = [
1506
+ "serde",
1507
+ "serde_spanned",
1508
+ "toml_datetime",
1509
+ "toml_edit",
1510
+ ]
1511
+
1512
+ [[package]]
1513
+ name = "toml_datetime"
1514
+ version = "0.6.11"
1515
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1516
+ checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
1517
+ dependencies = [
1518
+ "serde",
1519
+ ]
1520
+
1521
+ [[package]]
1522
+ name = "toml_edit"
1523
+ version = "0.22.27"
1524
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1525
+ checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
1526
+ dependencies = [
1527
+ "indexmap",
1528
+ "serde",
1529
+ "serde_spanned",
1530
+ "toml_datetime",
1531
+ "toml_write",
1532
+ "winnow",
1533
+ ]
1534
+
1535
+ [[package]]
1536
+ name = "toml_write"
1537
+ version = "0.1.2"
1538
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1539
+ checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
1540
+
853
1541
  [[package]]
854
1542
  name = "topiary-core"
855
1543
  version = "0.7.3"
@@ -857,7 +1545,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
857
1545
  checksum = "89df094e19f103c5b8e120a1ffa30a6309daa10bef8d186e598a3df633e6a221"
858
1546
  dependencies = [
859
1547
  "futures",
860
- "itertools",
1548
+ "itertools 0.11.0",
861
1549
  "log",
862
1550
  "miette",
863
1551
  "pretty_assertions",
@@ -866,7 +1554,7 @@ dependencies = [
866
1554
  "serde",
867
1555
  "serde_json",
868
1556
  "streaming-iterator",
869
- "thiserror",
1557
+ "thiserror 2.0.19",
870
1558
  "tokio",
871
1559
  "topiary-tree-sitter-facade",
872
1560
  "topiary-web-tree-sitter-sys",
@@ -929,6 +1617,12 @@ dependencies = [
929
1617
  "tree-sitter-language",
930
1618
  ]
931
1619
 
1620
+ [[package]]
1621
+ name = "typenum"
1622
+ version = "1.20.1"
1623
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1624
+ checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
1625
+
932
1626
  [[package]]
933
1627
  name = "unicode-ident"
934
1628
  version = "1.0.24"
@@ -953,18 +1647,40 @@ version = "0.2.2"
953
1647
  source = "registry+https://github.com/rust-lang/crates.io-index"
954
1648
  checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
955
1649
 
1650
+ [[package]]
1651
+ name = "unicode-xid"
1652
+ version = "0.2.6"
1653
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1654
+ checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
1655
+
956
1656
  [[package]]
957
1657
  name = "utf8parse"
958
1658
  version = "0.2.2"
959
1659
  source = "registry+https://github.com/rust-lang/crates.io-index"
960
1660
  checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
961
1661
 
1662
+ [[package]]
1663
+ name = "uuid"
1664
+ version = "1.24.0"
1665
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1666
+ checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239"
1667
+ dependencies = [
1668
+ "js-sys",
1669
+ "wasm-bindgen",
1670
+ ]
1671
+
962
1672
  [[package]]
963
1673
  name = "version_check"
964
1674
  version = "0.9.5"
965
1675
  source = "registry+https://github.com/rust-lang/crates.io-index"
966
1676
  checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
967
1677
 
1678
+ [[package]]
1679
+ name = "wasi"
1680
+ version = "0.11.1+wasi-snapshot-preview1"
1681
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1682
+ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
1683
+
968
1684
  [[package]]
969
1685
  name = "wasm-bindgen"
970
1686
  version = "0.2.100"
@@ -1043,7 +1759,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
1043
1759
  checksum = "e913f9242315ca39eff82aee0e19ee7a372155717ff0eb082c741e435ce25ed1"
1044
1760
  dependencies = [
1045
1761
  "leb128",
1046
- "wasmparser",
1762
+ "wasmparser 0.220.1",
1763
+ ]
1764
+
1765
+ [[package]]
1766
+ name = "wasm-encoder"
1767
+ version = "0.221.3"
1768
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1769
+ checksum = "dc8444fe4920de80a4fe5ab564fff2ae58b6b73166b89751f8c6c93509da32e5"
1770
+ dependencies = [
1771
+ "leb128",
1772
+ "wasmparser 0.221.3",
1773
+ ]
1774
+
1775
+ [[package]]
1776
+ name = "wasm-encoder"
1777
+ version = "0.253.0"
1778
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1779
+ checksum = "59972d6cd272259de647b7c1f1912e45e289c75ffd4be04e10695507cd7e1b59"
1780
+ dependencies = [
1781
+ "leb128fmt",
1782
+ "wasmparser 0.253.0",
1047
1783
  ]
1048
1784
 
1049
1785
  [[package]]
@@ -1060,6 +1796,306 @@ dependencies = [
1060
1796
  "serde",
1061
1797
  ]
1062
1798
 
1799
+ [[package]]
1800
+ name = "wasmparser"
1801
+ version = "0.221.3"
1802
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1803
+ checksum = "d06bfa36ab3ac2be0dee563380147a5b81ba10dd8885d7fbbc9eb574be67d185"
1804
+ dependencies = [
1805
+ "bitflags",
1806
+ "hashbrown 0.15.5",
1807
+ "indexmap",
1808
+ "semver",
1809
+ "serde",
1810
+ ]
1811
+
1812
+ [[package]]
1813
+ name = "wasmparser"
1814
+ version = "0.253.0"
1815
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1816
+ checksum = "19db11f87d2486580e1e8b6f494c54df7e0566b87d0b599db843c24019667339"
1817
+ dependencies = [
1818
+ "bitflags",
1819
+ "indexmap",
1820
+ "semver",
1821
+ ]
1822
+
1823
+ [[package]]
1824
+ name = "wasmprinter"
1825
+ version = "0.221.3"
1826
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1827
+ checksum = "7343c42a97f2926c7819ff81b64012092ae954c5d83ddd30c9fcdefd97d0b283"
1828
+ dependencies = [
1829
+ "anyhow",
1830
+ "termcolor",
1831
+ "wasmparser 0.221.3",
1832
+ ]
1833
+
1834
+ [[package]]
1835
+ name = "wasmtime"
1836
+ version = "28.0.1"
1837
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1838
+ checksum = "edd30973c65eceb0f37dfcc430d83abd5eb24015fdfcab6912f52949287e04f0"
1839
+ dependencies = [
1840
+ "addr2line 0.24.2",
1841
+ "anyhow",
1842
+ "async-trait",
1843
+ "bitflags",
1844
+ "bumpalo",
1845
+ "cc",
1846
+ "cfg-if",
1847
+ "encoding_rs",
1848
+ "fxprof-processed-profile",
1849
+ "gimli 0.31.1",
1850
+ "hashbrown 0.14.5",
1851
+ "indexmap",
1852
+ "ittapi",
1853
+ "libc",
1854
+ "libm",
1855
+ "log",
1856
+ "mach2",
1857
+ "memfd",
1858
+ "object 0.36.7",
1859
+ "once_cell",
1860
+ "paste",
1861
+ "postcard",
1862
+ "psm",
1863
+ "pulley-interpreter",
1864
+ "rayon",
1865
+ "rustix 0.38.44",
1866
+ "semver",
1867
+ "serde",
1868
+ "serde_derive",
1869
+ "serde_json",
1870
+ "smallvec",
1871
+ "sptr",
1872
+ "target-lexicon",
1873
+ "wasm-encoder 0.221.3",
1874
+ "wasmparser 0.221.3",
1875
+ "wasmtime-asm-macros",
1876
+ "wasmtime-cache",
1877
+ "wasmtime-component-macro",
1878
+ "wasmtime-component-util",
1879
+ "wasmtime-cranelift",
1880
+ "wasmtime-environ",
1881
+ "wasmtime-fiber",
1882
+ "wasmtime-jit-debug",
1883
+ "wasmtime-jit-icache-coherence",
1884
+ "wasmtime-slab",
1885
+ "wasmtime-versioned-export-macros",
1886
+ "wasmtime-winch",
1887
+ "wat",
1888
+ "windows-sys 0.59.0",
1889
+ ]
1890
+
1891
+ [[package]]
1892
+ name = "wasmtime-asm-macros"
1893
+ version = "28.0.1"
1894
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1895
+ checksum = "c6c21dd30d1f3f93ee390ac1a7ec304ecdbfdab6390e1add41a1f52727b0992b"
1896
+ dependencies = [
1897
+ "cfg-if",
1898
+ ]
1899
+
1900
+ [[package]]
1901
+ name = "wasmtime-cache"
1902
+ version = "28.0.1"
1903
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1904
+ checksum = "cabd563cfbfe75c5bf514081f624ca8d18391a37520d8c794abce702474e688c"
1905
+ dependencies = [
1906
+ "anyhow",
1907
+ "base64",
1908
+ "directories-next",
1909
+ "log",
1910
+ "postcard",
1911
+ "rustix 0.38.44",
1912
+ "serde",
1913
+ "serde_derive",
1914
+ "sha2",
1915
+ "toml",
1916
+ "windows-sys 0.59.0",
1917
+ "zstd",
1918
+ ]
1919
+
1920
+ [[package]]
1921
+ name = "wasmtime-component-macro"
1922
+ version = "28.0.1"
1923
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1924
+ checksum = "9f948a6ef3119d52c9f12936970de28ddf3f9bea04bc65571f4a92d2e5ab38f4"
1925
+ dependencies = [
1926
+ "anyhow",
1927
+ "proc-macro2",
1928
+ "quote",
1929
+ "syn 2.0.119",
1930
+ "wasmtime-component-util",
1931
+ "wasmtime-wit-bindgen",
1932
+ "wit-parser",
1933
+ ]
1934
+
1935
+ [[package]]
1936
+ name = "wasmtime-component-util"
1937
+ version = "28.0.1"
1938
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1939
+ checksum = "b9275aa01ceaaa2fa6c0ecaa5267518d80b9d6e9ae7c7ea42f4c6e073e6a69ef"
1940
+
1941
+ [[package]]
1942
+ name = "wasmtime-cranelift"
1943
+ version = "28.0.1"
1944
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1945
+ checksum = "0701a44a323267aae4499672dae422b266cee3135a23b640972ec8c0e10a44a2"
1946
+ dependencies = [
1947
+ "anyhow",
1948
+ "cfg-if",
1949
+ "cranelift-codegen",
1950
+ "cranelift-control",
1951
+ "cranelift-entity",
1952
+ "cranelift-frontend",
1953
+ "cranelift-native",
1954
+ "gimli 0.31.1",
1955
+ "itertools 0.12.1",
1956
+ "log",
1957
+ "object 0.36.7",
1958
+ "smallvec",
1959
+ "target-lexicon",
1960
+ "thiserror 1.0.69",
1961
+ "wasmparser 0.221.3",
1962
+ "wasmtime-environ",
1963
+ "wasmtime-versioned-export-macros",
1964
+ ]
1965
+
1966
+ [[package]]
1967
+ name = "wasmtime-environ"
1968
+ version = "28.0.1"
1969
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1970
+ checksum = "264c968c1b81d340355ece2be0bc31a10f567ccb6ce08512c3b7d10e26f3cbe5"
1971
+ dependencies = [
1972
+ "anyhow",
1973
+ "cpp_demangle",
1974
+ "cranelift-bitset",
1975
+ "cranelift-entity",
1976
+ "gimli 0.31.1",
1977
+ "indexmap",
1978
+ "log",
1979
+ "object 0.36.7",
1980
+ "postcard",
1981
+ "rustc-demangle",
1982
+ "semver",
1983
+ "serde",
1984
+ "serde_derive",
1985
+ "smallvec",
1986
+ "target-lexicon",
1987
+ "wasm-encoder 0.221.3",
1988
+ "wasmparser 0.221.3",
1989
+ "wasmprinter",
1990
+ "wasmtime-component-util",
1991
+ ]
1992
+
1993
+ [[package]]
1994
+ name = "wasmtime-fiber"
1995
+ version = "28.0.1"
1996
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1997
+ checksum = "78505221fd5bd7b07b4e1fa2804edea49dc231e626ad6861adc8f531812973e6"
1998
+ dependencies = [
1999
+ "anyhow",
2000
+ "cc",
2001
+ "cfg-if",
2002
+ "rustix 0.38.44",
2003
+ "wasmtime-asm-macros",
2004
+ "wasmtime-versioned-export-macros",
2005
+ "windows-sys 0.59.0",
2006
+ ]
2007
+
2008
+ [[package]]
2009
+ name = "wasmtime-jit-debug"
2010
+ version = "28.0.1"
2011
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2012
+ checksum = "0cec0a8e5620ae71bfcaaec78e3076be5b6ebf869f4e6191925d73242224a915"
2013
+ dependencies = [
2014
+ "object 0.36.7",
2015
+ "rustix 0.38.44",
2016
+ "wasmtime-versioned-export-macros",
2017
+ ]
2018
+
2019
+ [[package]]
2020
+ name = "wasmtime-jit-icache-coherence"
2021
+ version = "28.0.1"
2022
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2023
+ checksum = "9bedb677ca1b549d98f95e9e1f9251b460090d99a2c196a0614228c064bf2e59"
2024
+ dependencies = [
2025
+ "anyhow",
2026
+ "cfg-if",
2027
+ "libc",
2028
+ "windows-sys 0.59.0",
2029
+ ]
2030
+
2031
+ [[package]]
2032
+ name = "wasmtime-slab"
2033
+ version = "28.0.1"
2034
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2035
+ checksum = "564905638c132c275d365c1fa074f0b499790568f43148d29de84ccecfb5cb31"
2036
+
2037
+ [[package]]
2038
+ name = "wasmtime-versioned-export-macros"
2039
+ version = "28.0.1"
2040
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2041
+ checksum = "1e91092e6cf77390eeccee273846a9327f3e8f91c3c6280f60f37809f0e62d29"
2042
+ dependencies = [
2043
+ "proc-macro2",
2044
+ "quote",
2045
+ "syn 2.0.119",
2046
+ ]
2047
+
2048
+ [[package]]
2049
+ name = "wasmtime-winch"
2050
+ version = "28.0.1"
2051
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2052
+ checksum = "b111d909dc604c741bd8ac2f4af373eaa5c68c34b5717271bcb687688212cef8"
2053
+ dependencies = [
2054
+ "anyhow",
2055
+ "cranelift-codegen",
2056
+ "gimli 0.31.1",
2057
+ "object 0.36.7",
2058
+ "target-lexicon",
2059
+ "wasmparser 0.221.3",
2060
+ "wasmtime-cranelift",
2061
+ "wasmtime-environ",
2062
+ "winch-codegen",
2063
+ ]
2064
+
2065
+ [[package]]
2066
+ name = "wasmtime-wit-bindgen"
2067
+ version = "28.0.1"
2068
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2069
+ checksum = "5f38f7a5eb2f06f53fe943e7fb8bf4197f7cf279f1bc52c0ce56e9d3ffd750a4"
2070
+ dependencies = [
2071
+ "anyhow",
2072
+ "heck",
2073
+ "indexmap",
2074
+ "wit-parser",
2075
+ ]
2076
+
2077
+ [[package]]
2078
+ name = "wast"
2079
+ version = "253.0.0"
2080
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2081
+ checksum = "d3264542f8965c5d84fb1085d924bfba9a6314bb228eff13a2de14d7627664d0"
2082
+ dependencies = [
2083
+ "bumpalo",
2084
+ "leb128fmt",
2085
+ "memchr",
2086
+ "unicode-width 0.2.2",
2087
+ "wasm-encoder 0.253.0",
2088
+ ]
2089
+
2090
+ [[package]]
2091
+ name = "wat"
2092
+ version = "1.253.0"
2093
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2094
+ checksum = "4bfc5ce906144200c972ec617470aa35bd847472e170b26dde3e80541c674055"
2095
+ dependencies = [
2096
+ "wast",
2097
+ ]
2098
+
1063
2099
  [[package]]
1064
2100
  name = "web-sys"
1065
2101
  version = "0.3.77"
@@ -1070,12 +2106,69 @@ dependencies = [
1070
2106
  "wasm-bindgen",
1071
2107
  ]
1072
2108
 
2109
+ [[package]]
2110
+ name = "winapi"
2111
+ version = "0.3.9"
2112
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2113
+ checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
2114
+ dependencies = [
2115
+ "winapi-i686-pc-windows-gnu",
2116
+ "winapi-x86_64-pc-windows-gnu",
2117
+ ]
2118
+
2119
+ [[package]]
2120
+ name = "winapi-i686-pc-windows-gnu"
2121
+ version = "0.4.0"
2122
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2123
+ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
2124
+
2125
+ [[package]]
2126
+ name = "winapi-util"
2127
+ version = "0.1.11"
2128
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2129
+ checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
2130
+ dependencies = [
2131
+ "windows-sys 0.61.2",
2132
+ ]
2133
+
2134
+ [[package]]
2135
+ name = "winapi-x86_64-pc-windows-gnu"
2136
+ version = "0.4.0"
2137
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2138
+ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
2139
+
2140
+ [[package]]
2141
+ name = "winch-codegen"
2142
+ version = "28.0.1"
2143
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2144
+ checksum = "6232f40a795be2ce10fc761ed3b403825126a60d12491ac556ea104a932fd18a"
2145
+ dependencies = [
2146
+ "anyhow",
2147
+ "cranelift-codegen",
2148
+ "gimli 0.31.1",
2149
+ "regalloc2",
2150
+ "smallvec",
2151
+ "target-lexicon",
2152
+ "wasmparser 0.221.3",
2153
+ "wasmtime-cranelift",
2154
+ "wasmtime-environ",
2155
+ ]
2156
+
1073
2157
  [[package]]
1074
2158
  name = "windows-link"
1075
2159
  version = "0.2.1"
1076
2160
  source = "registry+https://github.com/rust-lang/crates.io-index"
1077
2161
  checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
1078
2162
 
2163
+ [[package]]
2164
+ name = "windows-sys"
2165
+ version = "0.59.0"
2166
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2167
+ checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
2168
+ dependencies = [
2169
+ "windows-targets",
2170
+ ]
2171
+
1079
2172
  [[package]]
1080
2173
  name = "windows-sys"
1081
2174
  version = "0.61.2"
@@ -1085,6 +2178,97 @@ dependencies = [
1085
2178
  "windows-link",
1086
2179
  ]
1087
2180
 
2181
+ [[package]]
2182
+ name = "windows-targets"
2183
+ version = "0.52.6"
2184
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2185
+ checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
2186
+ dependencies = [
2187
+ "windows_aarch64_gnullvm",
2188
+ "windows_aarch64_msvc",
2189
+ "windows_i686_gnu",
2190
+ "windows_i686_gnullvm",
2191
+ "windows_i686_msvc",
2192
+ "windows_x86_64_gnu",
2193
+ "windows_x86_64_gnullvm",
2194
+ "windows_x86_64_msvc",
2195
+ ]
2196
+
2197
+ [[package]]
2198
+ name = "windows_aarch64_gnullvm"
2199
+ version = "0.52.6"
2200
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2201
+ checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
2202
+
2203
+ [[package]]
2204
+ name = "windows_aarch64_msvc"
2205
+ version = "0.52.6"
2206
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2207
+ checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
2208
+
2209
+ [[package]]
2210
+ name = "windows_i686_gnu"
2211
+ version = "0.52.6"
2212
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2213
+ checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
2214
+
2215
+ [[package]]
2216
+ name = "windows_i686_gnullvm"
2217
+ version = "0.52.6"
2218
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2219
+ checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
2220
+
2221
+ [[package]]
2222
+ name = "windows_i686_msvc"
2223
+ version = "0.52.6"
2224
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2225
+ checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
2226
+
2227
+ [[package]]
2228
+ name = "windows_x86_64_gnu"
2229
+ version = "0.52.6"
2230
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2231
+ checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
2232
+
2233
+ [[package]]
2234
+ name = "windows_x86_64_gnullvm"
2235
+ version = "0.52.6"
2236
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2237
+ checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
2238
+
2239
+ [[package]]
2240
+ name = "windows_x86_64_msvc"
2241
+ version = "0.52.6"
2242
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2243
+ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
2244
+
2245
+ [[package]]
2246
+ name = "winnow"
2247
+ version = "0.7.15"
2248
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2249
+ checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945"
2250
+ dependencies = [
2251
+ "memchr",
2252
+ ]
2253
+
2254
+ [[package]]
2255
+ name = "wit-parser"
2256
+ version = "0.221.3"
2257
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2258
+ checksum = "896112579ed56b4a538b07a3d16e562d101ff6265c46b515ce0c701eef16b2ac"
2259
+ dependencies = [
2260
+ "anyhow",
2261
+ "id-arena",
2262
+ "indexmap",
2263
+ "log",
2264
+ "semver",
2265
+ "serde",
2266
+ "serde_derive",
2267
+ "serde_json",
2268
+ "unicode-xid",
2269
+ "wasmparser 0.221.3",
2270
+ ]
2271
+
1088
2272
  [[package]]
1089
2273
  name = "yansi"
1090
2274
  version = "1.0.1"
@@ -1116,3 +2300,31 @@ name = "zmij"
1116
2300
  version = "1.0.23"
1117
2301
  source = "registry+https://github.com/rust-lang/crates.io-index"
1118
2302
  checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
2303
+
2304
+ [[package]]
2305
+ name = "zstd"
2306
+ version = "0.13.3"
2307
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2308
+ checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a"
2309
+ dependencies = [
2310
+ "zstd-safe",
2311
+ ]
2312
+
2313
+ [[package]]
2314
+ name = "zstd-safe"
2315
+ version = "7.2.4"
2316
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2317
+ checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d"
2318
+ dependencies = [
2319
+ "zstd-sys",
2320
+ ]
2321
+
2322
+ [[package]]
2323
+ name = "zstd-sys"
2324
+ version = "2.0.16+zstd.1.5.7"
2325
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2326
+ checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748"
2327
+ dependencies = [
2328
+ "cc",
2329
+ "pkg-config",
2330
+ ]
plum-wasm-codegen/Cargo.toml CHANGED
@@ -6,8 +6,10 @@ edition = "2021"
6
6
  [dependencies]
7
7
  wasm-encoder = "0.220"
8
8
  plum-core = { path = "../plum-core" }
9
+ plum-checker = { path = "../plum-checker" }
9
10
 
10
11
  [dev-dependencies]
11
12
  wasmparser = "0.220"
12
13
  tree-sitter = "0.26"
13
14
  tree-sitter-plum = { path = "../tooling/tree-sitter-plum" }
15
+ wasmtime = "28"
plum-wasm-codegen/src/lib.rs CHANGED
@@ -1,6 +1,15 @@
1
1
  use wasm_encoder::*;
2
+ use std::cell::RefCell;
2
3
  use std::collections::HashMap;
3
4
  use plum_core::ast;
5
+ use plum_checker::types::{PlumType, TypeEnv, TypeScheme};
6
+ use plum_checker::{ClassEnv, MethodEnv, EnumVariants};
7
+
8
+ /// Bump-allocated heap for class instances starts at the second 64KiB page so it can
9
+ /// never collide with the (small, compile-time-sized) string literal data area below it.
10
+ const HEAP_BASE: u32 = 65536;
11
+ /// String literal data (length-prefixed UTF-8 blobs) is laid out from here upward.
12
+ const STRING_BASE: u32 = 8;
4
13
 
5
14
  pub struct WasmModule {
6
15
  types: Vec<FuncType>,
@@ -172,31 +181,57 @@ pub struct FuncSig {
172
181
  pub ret: Option<ValType>,
173
182
  }
174
183
 
184
+ /// Global, read-only lookup tables shared by every function body being compiled.
175
185
  pub struct CompileCtx {
176
- pub module: WasmModule,
177
186
  pub func_ids: HashMap<String, u32>,
178
187
  pub func_sigs: HashMap<String, FuncSig>,
188
+ pub classes: ClassEnv,
189
+ pub methods: MethodEnv,
179
- pub current_locals: HashMap<String, u32>,
190
+ pub enum_variants: EnumVariants,
180
- pub label_count: u32,
191
+ pub global_env: TypeEnv,
181
- pub bump_offset: u32,
192
+ pub bump_global: u32,
182
193
  }
183
194
 
195
+ /// Per-module state that accumulates as function bodies are compiled: the running
196
+ /// offset for placing string literal bytes, and the resulting data segments.
184
- impl CompileCtx {
197
+ struct ModuleState {
185
- pub fn new() -> Self {
186
- Self {
187
- module: WasmModule::new(),
188
- func_ids: HashMap::new(),
189
- func_sigs: HashMap::new(),
190
- current_locals: HashMap::new(),
191
- label_count: 0,
192
- bump_offset: 0,
198
+ next_string_offset: u32,
199
+ data_segments: Vec<(u32, Vec<u8>)>,
193
- }
200
+ }
201
+
202
+ struct LocalCtx<'a> {
203
+ locals: HashMap<String, u32>,
204
+ /// First local index reserved for `ClassCall` scratch temporaries.
205
+ classcall_scratch_base: u32,
206
+ /// `ClassCall` expr identity (pointer address) -> scratch slot offset.
207
+ classcall_scratch: HashMap<usize, u32>,
208
+ /// First local index reserved for `match` subject scratch temporaries.
209
+ match_scratch_base: u32,
210
+ /// `Match` stmt identity (pointer address) -> scratch slot offset.
211
+ match_scratch_index: HashMap<usize, u32>,
212
+ func_ids: &'a HashMap<String, u32>,
213
+ func_sigs: &'a HashMap<String, FuncSig>,
214
+ classes: &'a ClassEnv,
215
+ methods: &'a MethodEnv,
216
+ enum_variants: &'a EnumVariants,
217
+ /// Tracks each binding's inferred type as compilation proceeds through
218
+ /// statements in order, mirroring `plum-checker`'s own env evolution — needed
219
+ /// to resolve `Attribute`/`ClassCall` targets and pick the right load/store width.
220
+ type_env: RefCell<TypeEnv>,
221
+ bump_global: u32,
222
+ }
223
+
224
+ fn fn_key(f: &ast::Fn) -> String {
225
+ match &f.type_param {
226
+ Some(recv) => format!("{}::{}", recv, f.name),
227
+ None => f.name.clone(),
194
228
  }
195
229
  }
196
230
 
231
+ fn param_type_name(pt: &ast::ParamType) -> &str {
197
- impl Default for CompileCtx {
232
+ match pt {
198
- fn default() -> Self {
233
+ ast::ParamType::Type(t) => t.name.as_str(),
199
- Self::new()
234
+ ast::ParamType::Variadic(t) => t.name.as_str(),
200
235
  }
201
236
  }
202
237
 
@@ -207,7 +242,16 @@ fn ast_type_to_wasm(name: &str) -> Option<ValType> {
207
242
  "Bool" => Some(ValType::I32),
208
243
  "Str" => Some(ValType::I32),
209
244
  "Unit" => None,
245
+ _ => Some(ValType::I32), // class instance / unmodeled type: pointer
246
+ }
247
+ }
248
+
249
+ fn plum_type_to_valtype(t: &PlumType) -> ValType {
250
+ match t {
210
- _ => Some(ValType::I64),
251
+ PlumType::TInt => ValType::I64,
252
+ PlumType::TFloat => ValType::F64,
253
+ PlumType::TBool | PlumType::TStr | PlumType::TNamed(_) => ValType::I32,
254
+ PlumType::TVar(_) | PlumType::TUnit | PlumType::TFun(_, _) => ValType::I64,
211
255
  }
212
256
  }
213
257
 
@@ -231,156 +275,335 @@ fn encode_leb128_u32(mut val: u32) -> Vec<u8> {
231
275
  bytes
232
276
  }
233
277
 
278
+ fn check_ctx_of<'a>(ctx_classes: &'a ClassEnv, ctx_methods: &'a MethodEnv, ctx_enum_variants: &'a EnumVariants) -> plum_checker::CheckCtx<'a> {
279
+ plum_checker::CheckCtx { classes: ctx_classes, methods: ctx_methods, enum_variants: ctx_enum_variants }
280
+ }
281
+
282
+ /// Infers an expression's type using the function's current (mutable, evolving) type
283
+ /// environment. Defaults to `TInt` if inference fails — codegen assumes the source was
284
+ /// already accepted by `plum_checker::check_source`, so a failure here would indicate
285
+ /// codegen is being driven directly on unchecked input (as some tests do).
286
+ fn infer_local_type(expr: &ast::Expr, ctx: &LocalCtx) -> PlumType {
287
+ let env = ctx.type_env.borrow();
288
+ let cctx = check_ctx_of(ctx.classes, ctx.methods, ctx.enum_variants);
289
+ plum_checker::infer_expr(expr, &env, &cctx).unwrap_or(PlumType::TInt)
290
+ }
291
+
234
292
  pub fn compile_source(source: &ast::Source) -> Result<Vec<u8>, String> {
293
+ let (global_env, classes, methods, enum_variants) = plum_checker::build_global_tables(source);
294
+
235
- let mut ctx = CompileCtx::new();
295
+ let mut module = WasmModule::new();
296
+ module.add_memory(2, None);
297
+ let mut bump_init = Vec::new();
298
+ Instruction::I32Const(HEAP_BASE as i32).encode(&mut bump_init);
299
+ let bump_global = module.add_global(ValType::I32, true, &bump_init);
236
300
 
301
+ let mut func_ids: HashMap<String, u32> = HashMap::new();
302
+ let mut func_sigs: HashMap<String, FuncSig> = HashMap::new();
303
+
237
- // First pass: register all function type signatures and allocate function slots
304
+ // Register every function AND method signature up front (methods get an implicit
305
+ // leading `self: pointer` param and are keyed as "Receiver::method").
238
306
  for item in &source.items {
239
307
  if let ast::Item::Fn(f) = item {
308
+ let mut param_types: Vec<ValType> = Vec::new();
240
309
  if f.type_param.is_some() {
241
- continue; // skip method impls in v1
310
+ param_types.push(ValType::I32);
242
- }
311
+ }
243
- let param_types: Vec<ValType> = f
244
- .params
245
- .iter()
246
- .map(|p| {
247
- let name = match &p.ty {
312
+ for p in &f.params {
248
- ast::ParamType::Type(t) => t.name.as_str(),
249
- ast::ParamType::Variadic(t) => t.name.as_str(),
250
- };
251
- ast_type_to_wasm(name).unwrap_or(ValType::I64)
313
+ param_types.push(ast_type_to_wasm(param_type_name(&p.ty)).unwrap_or(ValType::I32));
252
- })
314
+ }
253
- .collect();
254
315
  let ret = ret_type_to_wasm(f.returns.as_ref());
255
316
  let results_vec: Vec<ValType> = ret.into_iter().collect();
256
- let type_idx = ctx.module.add_type(&param_types, &results_vec);
317
+ let type_idx = module.add_type(&param_types, &results_vec);
257
- let func_idx = ctx.module.add_function(type_idx, &[]);
318
+ let func_idx = module.add_function(type_idx, &[]);
319
+ let key = fn_key(f);
258
- ctx.func_ids.insert(f.name.clone(), func_idx);
320
+ func_ids.insert(key.clone(), func_idx);
259
- ctx.func_sigs.insert(f.name.clone(), FuncSig { params: param_types, ret });
321
+ func_sigs.insert(key, FuncSig { params: param_types, ret });
260
322
  }
261
323
  }
262
324
 
263
- // Second pass: compile each function body
325
+ let ctx = CompileCtx { func_ids, func_sigs, classes, methods, enum_variants, global_env, bump_global };
326
+
264
- let fns: Vec<ast::Fn> = source
327
+ let fns: Vec<&ast::Fn> = source.items.iter().filter_map(|item| match item {
265
- .items
266
- .iter()
267
- .filter_map(|item| {
268
- if let ast::Item::Fn(f) = item {
328
+ ast::Item::Fn(f) => Some(f),
269
- if f.type_param.is_none() {
270
- Some(f.clone())
329
+ _ => None,
271
- } else {
272
- None
273
- }
274
- } else {
275
- None
276
- }
277
- })
278
- .collect();
330
+ }).collect();
331
+
332
+ let mut state = ModuleState { next_string_offset: STRING_BASE, data_segments: Vec::new() };
279
333
 
280
334
  let mut compiled_bodies: Vec<(String, Vec<u8>)> = Vec::new();
281
335
  for f in &fns {
282
- let body = compile_fn_body(f, &ctx)?;
336
+ let body = compile_fn_body(f, &ctx, &mut state)?;
283
- compiled_bodies.push((f.name.clone(), body));
337
+ compiled_bodies.push((fn_key(f), body));
338
+ }
339
+
340
+ // Patch compiled bodies back into their pre-registered function slots.
341
+ for (name, body) in &compiled_bodies {
342
+ let idx = *ctx.func_ids.get(name).expect("function was registered in the first pass");
343
+ let slot = (idx - module.func_import_count) as usize;
344
+ module.functions[slot].1 = body.clone();
284
345
  }
285
346
 
286
- // Patch bodies into the module
287
- for (i, (_, body)) in compiled_bodies.iter().enumerate() {
347
+ for (offset, bytes) in &state.data_segments {
288
- ctx.module.functions[i].1 = body.clone();
348
+ module.add_data_segment(*offset, bytes);
289
349
  }
290
350
 
291
- // Export only main
292
351
  if let Some(&main_idx) = ctx.func_ids.get("main") {
293
- ctx.module.add_export("main", ExportKind::Func, main_idx);
352
+ module.add_export("main", ExportKind::Func, main_idx);
294
353
  }
295
354
 
296
- Ok(ctx.module.finish())
355
+ Ok(module.finish())
297
356
  }
298
357
 
358
+ /// Walks a function body once to determine: (1) every locally-assigned/bound name and
359
+ /// its inferred type, (2) how many `ClassCall` scratch temporaries it needs, and (3)
360
+ /// the subject type for every `match` statement (for its own scratch temporary).
361
+ struct Collector<'a> {
362
+ env: TypeEnv,
363
+ cctx: plum_checker::CheckCtx<'a>,
364
+ named: Vec<(String, PlumType)>,
299
- fn collect_local_names(body: &ast::FnBody) -> Vec<String> {
365
+ named_set: std::collections::HashSet<String>,
300
- let mut names = Vec::new();
301
- if let ast::FnBody::Block(block) = body {
302
- collect_block_locals(block, &mut names);
366
+ classcall_scratch: HashMap<usize, u32>,
303
- }
367
+ match_scratch: HashMap<usize, PlumType>,
304
- names
368
+ next_classcall_slot: u32,
305
369
  }
306
370
 
371
+ impl<'a> Collector<'a> {
307
- fn collect_block_locals(block: &ast::Block, names: &mut Vec<String>) {
372
+ fn bind(&mut self, name: &str, ty: PlumType) {
308
- for stmt in &block.stmts {
309
- collect_stmt_locals(stmt, names);
373
+ if self.named_set.insert(name.to_string()) {
374
+ self.named.push((name.to_string(), ty.clone()));
375
+ }
376
+ self.env.insert(name.to_string(), TypeScheme::mono(ty));
310
377
  }
311
- }
312
378
 
379
+ fn walk_block(&mut self, block: &ast::Block) {
380
+ for s in &block.stmts {
381
+ self.walk_stmt(s);
382
+ }
383
+ }
384
+
313
- fn collect_stmt_locals(stmt: &ast::Stmt, names: &mut Vec<String>) {
385
+ fn walk_stmt(&mut self, stmt: &ast::Stmt) {
314
- match stmt {
386
+ match stmt {
315
- ast::Stmt::Assign(a) => {
387
+ ast::Stmt::Assign(a) => {
388
+ for (target, value) in a.targets.iter().zip(a.values.iter()) {
389
+ self.walk_expr(value);
390
+ let ty = plum_checker::infer_expr(value, &self.env, &self.cctx).unwrap_or(PlumType::TInt);
316
- for t in &a.targets {
391
+ self.bind(target, ty);
317
- if !names.contains(t) {
318
- names.push(t.clone());
319
392
  }
320
393
  }
394
+ ast::Stmt::Return(Some(e)) => self.walk_expr(e),
395
+ ast::Stmt::Return(None) => {}
396
+ ast::Stmt::If(i) => {
397
+ self.walk_expr(&i.condition);
398
+ self.walk_block(&i.body);
399
+ for ei in &i.else_ifs {
400
+ self.walk_expr(&ei.condition);
401
+ self.walk_block(&ei.body);
321
- }
402
+ }
322
- ast::Stmt::If(i) => {
323
- collect_block_locals(&i.body, names);
324
- for ei in &i.else_ifs {
403
+ if let Some(e) = &i.else_ {
325
- collect_block_locals(&ei.body, names);
404
+ self.walk_block(e);
405
+ }
326
406
  }
407
+ ast::Stmt::While(w) => {
408
+ self.walk_expr(&w.condition);
409
+ self.walk_block(&w.body);
410
+ }
411
+ ast::Stmt::For(f) => {
327
- if let Some(e) = &i.else_ {
412
+ self.walk_expr(&f.iter);
413
+ for v in &f.vars {
414
+ self.bind(v, PlumType::TInt);
415
+ }
328
- collect_block_locals(e, names);
416
+ self.walk_block(&f.body);
329
417
  }
418
+ ast::Stmt::Expr(e) => self.walk_expr(e),
419
+ ast::Stmt::Assert(e) => self.walk_expr(e),
420
+ ast::Stmt::Match(m) => {
421
+ let subject_ty = if m.subjects.len() == 1 {
422
+ self.walk_expr(&m.subjects[0]);
423
+ plum_checker::infer_expr(&m.subjects[0], &self.env, &self.cctx).unwrap_or(PlumType::TInt)
424
+ } else {
425
+ for s in &m.subjects {
426
+ self.walk_expr(s);
427
+ }
428
+ PlumType::TInt
429
+ };
430
+ self.match_scratch.insert(m as *const ast::Match as usize, subject_ty.clone());
431
+ for case in &m.cases {
432
+ let saved = self.env.clone();
433
+ if m.subjects.len() == 1 {
434
+ if let Some(ast::CasePattern::Name(n)) = case.patterns.first() {
435
+ let is_variant = n.chars().next().map(|c| c.is_uppercase()).unwrap_or(false)
436
+ && self.cctx.enum_variants.contains_key(n);
437
+ if !is_variant {
438
+ self.bind(n, subject_ty.clone());
439
+ }
440
+ }
441
+ }
442
+ self.walk_block(&case.body);
443
+ self.env = saved;
444
+ }
445
+ }
446
+ ast::Stmt::Break | ast::Stmt::Continue | ast::Stmt::Todo => {}
330
447
  }
448
+ }
449
+
450
+ fn walk_expr(&mut self, expr: &ast::Expr) {
451
+ match expr {
452
+ ast::Expr::ClassCall(call) => {
453
+ let idx = self.next_classcall_slot;
454
+ self.next_classcall_slot += 1;
331
- ast::Stmt::While(w) => collect_block_locals(&w.body, names),
455
+ self.classcall_scratch.insert(expr as *const ast::Expr as usize, idx);
456
+ for fa in &call.fields {
457
+ self.walk_expr(&fa.value);
458
+ }
459
+ }
460
+ ast::Expr::Binary(b) => {
461
+ self.walk_expr(&b.left);
462
+ self.walk_expr(&b.right);
463
+ }
332
- ast::Stmt::For(f) => {
464
+ ast::Expr::Bool(b) => {
465
+ self.walk_expr(&b.left);
466
+ self.walk_expr(&b.right);
467
+ }
468
+ ast::Expr::Compare(c) => {
469
+ self.walk_expr(&c.left);
470
+ self.walk_expr(&c.right);
471
+ }
472
+ ast::Expr::Not(inner) => self.walk_expr(inner),
473
+ ast::Expr::Unary(u) => self.walk_expr(&u.operand),
474
+ ast::Expr::Paren(inner) => self.walk_expr(inner),
475
+ ast::Expr::Ternary(t) => {
476
+ self.walk_expr(&t.condition);
477
+ self.walk_expr(&t.then);
478
+ self.walk_expr(&t.else_);
479
+ }
480
+ ast::Expr::FnCall(call) => {
333
- for v in &f.vars {
481
+ for arg in &call.args {
482
+ self.walk_arg(arg);
483
+ }
484
+ }
485
+ ast::Expr::Attribute(a) => {
486
+ self.walk_expr(&a.object);
487
+ if let ast::AttrKind::Method(call) = &a.attr {
334
- if !names.contains(v) {
488
+ for arg in &call.args {
335
- names.push(v.clone());
489
+ self.walk_arg(arg);
490
+ }
336
491
  }
337
492
  }
493
+ ast::Expr::Int(_)
494
+ | ast::Expr::Float(_)
495
+ | ast::Expr::String(_)
496
+ | ast::Expr::Self_
497
+ | ast::Expr::Var(_)
498
+ | ast::Expr::TypeName(_) => {}
499
+ }
500
+ }
501
+
502
+ fn walk_arg(&mut self, arg: &ast::Arg) {
503
+ match arg {
338
- collect_block_locals(&f.body, names);
504
+ ast::Arg::Positional(e) => self.walk_expr(e),
505
+ ast::Arg::Keyword { value, .. } => self.walk_expr(value),
506
+ ast::Arg::Pair { value, .. } => self.walk_expr(value),
339
507
  }
340
- _ => {}
341
508
  }
342
509
  }
343
510
 
344
- fn compile_fn_body(f: &ast::Fn, ctx: &CompileCtx) -> Result<Vec<u8>, String> {
511
+ fn compile_fn_body(f: &ast::Fn, ctx: &CompileCtx, state: &mut ModuleState) -> Result<Vec<u8>, String> {
345
512
  let mut body = Vec::new();
346
- let param_count = f.params.len() as u32;
347
513
 
348
- // Collect extra (non-param) local variable names declared in the body
349
- let extra_locals = collect_local_names(&f.body);
350
- let extra_count = extra_locals.len() as u32;
514
+ let mut base_env = ctx.global_env.clone();
515
+ if let Some(recv) = &f.type_param {
516
+ base_env.insert("self".to_string(), TypeScheme::mono(PlumType::TNamed(recv.clone())));
517
+ }
518
+ for p in &f.params {
519
+ let ty = match &p.ty {
520
+ ast::ParamType::Type(t) => plum_checker::plum_type_from_ast(t),
521
+ ast::ParamType::Variadic(t) => plum_checker::plum_type_from_ast(t),
522
+ };
523
+ base_env.insert(p.name.clone(), TypeScheme::mono(ty));
524
+ }
351
525
 
526
+ let mut collector = Collector {
527
+ env: base_env.clone(),
352
- // Encode local declarations: group count, then each group as (count, type)
528
+ cctx: check_ctx_of(&ctx.classes, &ctx.methods, &ctx.enum_variants),
529
+ named: Vec::new(),
530
+ named_set: Default::default(),
531
+ classcall_scratch: HashMap::new(),
532
+ match_scratch: HashMap::new(),
353
- if extra_count > 0 {
533
+ next_classcall_slot: 0,
354
- body.extend(encode_leb128_u32(1)); // 1 group
355
- body.extend(encode_leb128_u32(extra_count));
534
+ };
356
- ValType::I64.encode(&mut body);
535
+ if let ast::FnBody::Block(block) = &f.body {
357
- } else {
358
- body.push(0); // 0 groups
536
+ collector.walk_block(block);
359
537
  }
360
538
 
361
- // Build locals map: params first, then extra locals
539
+ // ---- assign local indices: [self?][params][named...][classcall scratch...][match scratch...] ----
362
540
  let mut locals: HashMap<String, u32> = HashMap::new();
541
+ let mut groups: Vec<ValType> = Vec::new();
542
+ let mut idx = 0u32;
543
+
363
- for (i, p) in f.params.iter().enumerate() {
544
+ if f.type_param.is_some() {
545
+ locals.insert("self".to_string(), idx);
546
+ idx += 1;
547
+ }
548
+ for p in &f.params {
364
- locals.insert(p.name.clone(), i as u32);
549
+ locals.insert(p.name.clone(), idx);
550
+ idx += 1;
551
+ }
552
+ for (name, ty) in &collector.named {
553
+ let vt = plum_type_to_valtype(ty);
554
+ locals.insert(name.clone(), idx);
555
+ groups.push(vt);
556
+ idx += 1;
557
+ }
558
+
559
+ let classcall_scratch_base = idx;
560
+ let classcall_count = collector.classcall_scratch.values().copied().max().map(|m| m + 1).unwrap_or(0);
561
+ for _ in 0..classcall_count {
562
+ groups.push(ValType::I32);
563
+ idx += 1;
564
+ }
565
+
566
+ let match_scratch_base = idx;
567
+ let mut match_scratch_index: HashMap<usize, u32> = HashMap::new();
568
+ for (ptr, ty) in collector.match_scratch.iter() {
569
+ match_scratch_index.insert(*ptr, idx - match_scratch_base);
570
+ groups.push(plum_type_to_valtype(ty));
571
+ idx += 1;
365
572
  }
573
+
366
- for (i, name) in extra_locals.iter().enumerate() {
574
+ if groups.is_empty() {
575
+ body.push(0);
576
+ } else {
367
- locals.insert(name.clone(), param_count + i as u32);
577
+ body.extend(encode_leb128_u32(groups.len() as u32));
578
+ for g in &groups {
579
+ body.extend(encode_leb128_u32(1));
580
+ g.encode(&mut body);
581
+ }
368
582
  }
369
583
 
370
584
  let local_ctx = LocalCtx {
371
585
  locals,
586
+ classcall_scratch_base,
587
+ classcall_scratch: collector.classcall_scratch,
588
+ match_scratch_base,
589
+ match_scratch_index,
372
590
  func_ids: &ctx.func_ids,
373
591
  func_sigs: &ctx.func_sigs,
592
+ classes: &ctx.classes,
593
+ methods: &ctx.methods,
594
+ enum_variants: &ctx.enum_variants,
595
+ type_env: RefCell::new(base_env),
596
+ bump_global: ctx.bump_global,
374
597
  };
375
598
 
376
599
  let has_return_value = f.returns.as_ref().map(|r| r.name != "Unit").unwrap_or(false);
377
600
 
378
601
  match &f.body {
379
602
  ast::FnBody::Expr(e) => {
380
- compile_expr(e, &mut body, &local_ctx)?;
603
+ compile_expr(e, &mut body, &local_ctx, state)?;
381
604
  }
382
605
  ast::FnBody::Block(block) => {
383
- compile_block_as_fn_body(block, &mut body, &local_ctx, has_return_value)?;
606
+ compile_block_as_fn_body(block, &mut body, &local_ctx, state, has_return_value)?;
384
607
  }
385
608
  }
386
609
 
@@ -388,89 +611,93 @@ fn compile_fn_body(f: &ast::Fn, ctx: &CompileCtx) -> Result<Vec<u8>, String> {
388
611
  Ok(body)
389
612
  }
390
613
 
391
- struct LocalCtx<'a> {
392
- locals: HashMap<String, u32>,
393
- func_ids: &'a HashMap<String, u32>,
394
- func_sigs: &'a HashMap<String, FuncSig>,
395
- }
396
-
397
- fn compile_block(block: &ast::Block, body: &mut Vec<u8>, ctx: &LocalCtx) -> Result<(), String> {
614
+ fn compile_block(block: &ast::Block, body: &mut Vec<u8>, ctx: &LocalCtx, state: &mut ModuleState) -> Result<(), String> {
398
615
  for stmt in &block.stmts {
399
- compile_stmt(stmt, body, ctx)?;
616
+ compile_stmt(stmt, body, ctx, state)?;
400
617
  }
401
618
  Ok(())
402
619
  }
403
620
 
404
- /// Compile a block that is the body of a function. If the function returns a value
621
+ /// Compiles a block that is the body of a function. If the function returns a value
405
622
  /// and the last statement is an expression, that expression's value is left on the
406
623
  /// stack instead of being dropped.
407
624
  fn compile_block_as_fn_body(
408
625
  block: &ast::Block,
409
626
  body: &mut Vec<u8>,
410
627
  ctx: &LocalCtx,
628
+ state: &mut ModuleState,
411
629
  has_return_value: bool,
412
630
  ) -> Result<(), String> {
413
631
  let stmts = &block.stmts;
414
632
  if has_return_value {
415
633
  if let Some((last, rest)) = stmts.split_last() {
416
634
  for stmt in rest {
417
- compile_stmt(stmt, body, ctx)?;
635
+ compile_stmt(stmt, body, ctx, state)?;
418
636
  }
419
- // Last statement: if it's an Expr, leave its value on stack
420
637
  match last {
421
638
  ast::Stmt::Expr(e) => {
422
- compile_expr(e, body, ctx)?;
639
+ compile_expr(e, body, ctx, state)?;
423
640
  // do NOT drop — this is the return value
424
641
  }
642
+ ast::Stmt::Match(m) => {
643
+ compile_match(m, body, ctx, state)?;
644
+ // Every arm of a tail-position match must itself `return`; if a
645
+ // non-exhaustive match falls through, trap rather than continue
646
+ // with the required-but-never-produced result value missing.
647
+ // This also satisfies wasm validation, which requires a value at
648
+ // the function's `end` regardless of whether every branch inside
649
+ // the match's (BlockType::Empty) if/else chain already returned.
650
+ Instruction::Unreachable.encode(body);
651
+ }
425
652
  _ => {
426
- compile_stmt(last, body, ctx)?;
653
+ compile_stmt(last, body, ctx, state)?;
427
654
  }
428
655
  }
429
656
  return Ok(());
430
657
  }
431
658
  }
432
659
  for stmt in stmts {
433
- compile_stmt(stmt, body, ctx)?;
660
+ compile_stmt(stmt, body, ctx, state)?;
434
661
  }
435
662
  Ok(())
436
663
  }
437
664
 
438
- fn compile_stmt(stmt: &ast::Stmt, body: &mut Vec<u8>, ctx: &LocalCtx) -> Result<(), String> {
665
+ fn compile_stmt(stmt: &ast::Stmt, body: &mut Vec<u8>, ctx: &LocalCtx, state: &mut ModuleState) -> Result<(), String> {
439
666
  match stmt {
440
667
  ast::Stmt::Assign(a) => {
441
668
  for (target, value) in a.targets.iter().zip(a.values.iter()) {
669
+ let vty = infer_local_type(value, ctx);
442
- compile_expr(value, body, ctx)?;
670
+ compile_expr(value, body, ctx, state)?;
443
- let idx = *ctx
671
+ let idx = ctx
444
672
  .locals
445
673
  .get(target)
674
+ .copied()
446
675
  .ok_or_else(|| format!("undeclared local '{}'", target))?;
447
676
  Instruction::LocalSet(idx).encode(body);
677
+ ctx.type_env.borrow_mut().insert(target.clone(), TypeScheme::mono(vty));
448
678
  }
449
679
  }
450
680
  ast::Stmt::Return(Some(e)) => {
451
- compile_expr(e, body, ctx)?;
681
+ compile_expr(e, body, ctx, state)?;
452
682
  Instruction::Return.encode(body);
453
683
  }
454
684
  ast::Stmt::Return(None) => {
455
685
  Instruction::Return.encode(body);
456
686
  }
457
687
  ast::Stmt::If(if_) => {
458
- compile_expr(&if_.condition, body, ctx)?;
688
+ compile_expr(&if_.condition, body, ctx, state)?;
459
- // Condition is i64 (Bool is I32 but comparison results in I32)
460
- // Convert to i32 for if: if condition came from compare ops it's already i32
461
- // Use I32WrapI64 if needed — but compare ops return i32 already
462
689
  Instruction::If(BlockType::Empty).encode(body);
463
- compile_block(&if_.body, body, ctx)?;
690
+ compile_block(&if_.body, body, ctx, state)?;
464
691
  if !if_.else_ifs.is_empty() || if_.else_.is_some() {
465
692
  Instruction::Else.encode(body);
466
693
  for ei in &if_.else_ifs {
467
- compile_expr(&ei.condition, body, ctx)?;
694
+ compile_expr(&ei.condition, body, ctx, state)?;
468
695
  Instruction::If(BlockType::Empty).encode(body);
469
- compile_block(&ei.body, body, ctx)?;
696
+ compile_block(&ei.body, body, ctx, state)?;
470
697
  Instruction::Else.encode(body);
471
698
  }
472
699
  if let Some(else_block) = &if_.else_ {
473
- compile_block(else_block, body, ctx)?;
700
+ compile_block(else_block, body, ctx, state)?;
474
701
  }
475
702
  for _ in &if_.else_ifs {
476
703
  Instruction::End.encode(body);
@@ -479,13 +706,12 @@ fn compile_stmt(stmt: &ast::Stmt, body: &mut Vec<u8>, ctx: &LocalCtx) -> Result<
479
706
  Instruction::End.encode(body);
480
707
  }
481
708
  ast::Stmt::While(w) => {
482
- // block { loop { cond; i32.eqz; br_if 1; body; br 0 } }
483
709
  Instruction::Block(BlockType::Empty).encode(body);
484
710
  Instruction::Loop(BlockType::Empty).encode(body);
485
- compile_expr(&w.condition, body, ctx)?;
711
+ compile_expr(&w.condition, body, ctx, state)?;
486
712
  Instruction::I32Eqz.encode(body);
487
713
  Instruction::BrIf(1).encode(body);
488
- compile_block(&w.body, body, ctx)?;
714
+ compile_block(&w.body, body, ctx, state)?;
489
715
  Instruction::Br(0).encode(body);
490
716
  Instruction::End.encode(body);
491
717
  Instruction::End.encode(body);
@@ -494,19 +720,21 @@ fn compile_stmt(stmt: &ast::Stmt, body: &mut Vec<u8>, ctx: &LocalCtx) -> Result<
494
720
  if let ast::Expr::Binary(b) = &f.iter {
495
721
  if matches!(b.op, ast::BinOp::Range) && f.vars.len() == 1 {
496
722
  let var_name = &f.vars[0];
497
- let var_idx = *ctx
723
+ let var_idx = ctx
498
724
  .locals
499
725
  .get(var_name)
726
+ .copied()
500
727
  .ok_or_else(|| format!("undeclared loop var '{}'", var_name))?;
728
+ ctx.type_env.borrow_mut().insert(var_name.clone(), TypeScheme::mono(PlumType::TInt));
501
- compile_expr(&b.left, body, ctx)?;
729
+ compile_expr(&b.left, body, ctx, state)?;
502
730
  Instruction::LocalSet(var_idx).encode(body);
503
731
  Instruction::Block(BlockType::Empty).encode(body);
504
732
  Instruction::Loop(BlockType::Empty).encode(body);
505
733
  Instruction::LocalGet(var_idx).encode(body);
506
- compile_expr(&b.right, body, ctx)?;
734
+ compile_expr(&b.right, body, ctx, state)?;
507
735
  Instruction::I64GeS.encode(body);
508
736
  Instruction::BrIf(1).encode(body);
509
- compile_block(&f.body, body, ctx)?;
737
+ compile_block(&f.body, body, ctx, state)?;
510
738
  Instruction::LocalGet(var_idx).encode(body);
511
739
  Instruction::I64Const(1).encode(body);
512
740
  Instruction::I64Add.encode(body);
@@ -517,12 +745,12 @@ fn compile_stmt(stmt: &ast::Stmt, body: &mut Vec<u8>, ctx: &LocalCtx) -> Result<
517
745
  return Ok(());
518
746
  }
519
747
  }
520
- compile_expr(&f.iter, body, ctx)?;
748
+ compile_expr(&f.iter, body, ctx, state)?;
521
749
  Instruction::Drop.encode(body);
522
750
  }
523
751
  ast::Stmt::Expr(e) => {
524
752
  let has_result = expr_has_result(e, ctx);
525
- compile_expr(e, body, ctx)?;
753
+ compile_expr(e, body, ctx, state)?;
526
754
  if has_result {
527
755
  Instruction::Drop.encode(body);
528
756
  }
@@ -533,7 +761,10 @@ fn compile_stmt(stmt: &ast::Stmt, body: &mut Vec<u8>, ctx: &LocalCtx) -> Result<
533
761
  ast::Stmt::Continue => {
534
762
  Instruction::Br(0).encode(body);
535
763
  }
764
+ ast::Stmt::Match(m) => {
765
+ compile_match(m, body, ctx, state)?;
766
+ }
536
- ast::Stmt::Assert(_) | ast::Stmt::Match(_) | ast::Stmt::Todo => {}
767
+ ast::Stmt::Assert(_) | ast::Stmt::Todo => {}
537
768
  }
538
769
  Ok(())
539
770
  }
@@ -541,14 +772,139 @@ fn compile_stmt(stmt: &ast::Stmt, body: &mut Vec<u8>, ctx: &LocalCtx) -> Result<
541
772
  /// Returns true if the expression leaves a value on the wasm stack.
542
773
  fn expr_has_result(expr: &ast::Expr, ctx: &LocalCtx) -> bool {
543
774
  match expr {
775
+ ast::Expr::FnCall(call) => ctx.func_sigs.get(&call.name).map(|s| s.ret.is_some()).unwrap_or(true),
776
+ ast::Expr::Attribute(attr) => match &attr.attr {
544
- ast::Expr::FnCall(call) => {
777
+ ast::AttrKind::Method(call) => {
778
+ if let PlumType::TNamed(class_name) = infer_local_type(&attr.object, ctx) {
779
+ let key = format!("{}::{}", class_name, call.name);
545
- ctx.func_sigs.get(&call.name).map(|s| s.ret.is_some()).unwrap_or(true)
780
+ ctx.func_sigs.get(&key).map(|s| s.ret.is_some()).unwrap_or(true)
781
+ } else {
782
+ true
546
- }
783
+ }
784
+ }
785
+ ast::AttrKind::Field(_) => true,
786
+ },
547
787
  _ => true,
548
788
  }
549
789
  }
550
790
 
791
+ fn compile_match(m: &ast::Match, body: &mut Vec<u8>, ctx: &LocalCtx, state: &mut ModuleState) -> Result<(), String> {
792
+ if m.subjects.len() != 1 {
793
+ return Err("codegen: multi-subject match is not yet supported".to_string());
794
+ }
795
+ let subject = &m.subjects[0];
796
+ let subject_ty = infer_local_type(subject, ctx);
797
+ let subject_vt = plum_type_to_valtype(&subject_ty);
798
+
799
+ let key = m as *const ast::Match as usize;
800
+ let slot = *ctx
801
+ .match_scratch_index
802
+ .get(&key)
803
+ .ok_or_else(|| "internal codegen error: missing match scratch slot".to_string())?;
804
+ let scratch_local = ctx.match_scratch_base + slot;
805
+
806
+ compile_expr(subject, body, ctx, state)?;
807
+ Instruction::LocalSet(scratch_local).encode(body);
808
+
809
+ compile_match_arms(&m.cases, subject_vt, scratch_local, body, ctx, state)
810
+ }
811
+
812
+ fn compile_match_arms(
813
+ cases: &[ast::Case],
814
+ subject_vt: ValType,
815
+ scratch_local: u32,
816
+ body: &mut Vec<u8>,
817
+ ctx: &LocalCtx,
818
+ state: &mut ModuleState,
819
+ ) -> Result<(), String> {
820
+ let (case, rest) = match cases.split_first() {
821
+ None => return Ok(()),
822
+ Some(pair) => pair,
823
+ };
824
+ let pat = case.patterns.first().ok_or_else(|| "codegen: match case has no pattern".to_string())?;
825
+ match pat {
826
+ ast::CasePattern::Wildcard => {
827
+ // Any cases after a wildcard are unreachable, matching real match semantics.
828
+ compile_block(&case.body, body, ctx, state)
829
+ }
830
+ ast::CasePattern::Name(n) => {
831
+ let is_variant = n.chars().next().map(|c| c.is_uppercase()).unwrap_or(false)
832
+ && ctx.enum_variants.contains_key(n);
833
+ if is_variant {
834
+ compile_variant_eq_arm(n, subject_vt, scratch_local, case, rest, body, ctx, state)
835
+ } else {
836
+ let idx = ctx
837
+ .locals
838
+ .get(n)
839
+ .copied()
840
+ .ok_or_else(|| format!("internal codegen error: missing binding local '{}'", n))?;
841
+ Instruction::LocalGet(scratch_local).encode(body);
842
+ Instruction::LocalSet(idx).encode(body);
843
+ ctx.type_env.borrow_mut().insert(n.clone(), TypeScheme::mono(plum_type_from_valtype_hint(subject_vt)));
844
+ compile_block(&case.body, body, ctx, state)
845
+ // A binding arm always matches — any following cases are unreachable.
846
+ }
847
+ }
848
+ ast::CasePattern::Int(n) => {
849
+ if subject_vt != ValType::I64 {
850
+ return Err("codegen: integer match pattern against a non-Int subject".to_string());
851
+ }
852
+ Instruction::LocalGet(scratch_local).encode(body);
853
+ Instruction::I64Const(*n).encode(body);
854
+ Instruction::I64Eq.encode(body);
855
+ Instruction::If(BlockType::Empty).encode(body);
856
+ compile_block(&case.body, body, ctx, state)?;
857
+ Instruction::Else.encode(body);
858
+ compile_match_arms(rest, subject_vt, scratch_local, body, ctx, state)?;
859
+ Instruction::End.encode(body);
860
+ Ok(())
861
+ }
862
+ ast::CasePattern::String(_) => Err("codegen: string match patterns are not yet supported".to_string()),
863
+ ast::CasePattern::Float(_) => Err("codegen: float match patterns are not yet supported".to_string()),
864
+ ast::CasePattern::Class { .. } => Err("codegen: constructor match patterns are not yet supported".to_string()),
865
+ }
866
+ }
867
+
868
+ #[allow(clippy::too_many_arguments)]
869
+ fn compile_variant_eq_arm(
870
+ name: &str,
871
+ subject_vt: ValType,
872
+ scratch_local: u32,
873
+ case: &ast::Case,
874
+ rest: &[ast::Case],
875
+ body: &mut Vec<u8>,
876
+ ctx: &LocalCtx,
877
+ state: &mut ModuleState,
878
+ ) -> Result<(), String> {
879
+ // Only Bool's own variants have a concrete runtime representation in v1.5.
880
+ let tag = match name {
881
+ "True" => 1i32,
882
+ "False" => 0i32,
883
+ other => return Err(format!("codegen: enum variant pattern '{}' is not yet supported (only True/False)", other)),
884
+ };
885
+ if subject_vt != ValType::I32 {
886
+ return Err("codegen: Bool match pattern against a non-Bool subject".to_string());
887
+ }
888
+ Instruction::LocalGet(scratch_local).encode(body);
889
+ Instruction::I32Const(tag).encode(body);
890
+ Instruction::I32Eq.encode(body);
891
+ Instruction::If(BlockType::Empty).encode(body);
892
+ compile_block(&case.body, body, ctx, state)?;
893
+ Instruction::Else.encode(body);
894
+ compile_match_arms(rest, subject_vt, scratch_local, body, ctx, state)?;
895
+ Instruction::End.encode(body);
896
+ Ok(())
897
+ }
898
+
899
+ fn plum_type_from_valtype_hint(vt: ValType) -> PlumType {
900
+ match vt {
901
+ ValType::I64 => PlumType::TInt,
902
+ ValType::F64 => PlumType::TFloat,
903
+ _ => PlumType::TVar("_".to_string()),
904
+ }
905
+ }
906
+
551
- fn compile_expr(expr: &ast::Expr, body: &mut Vec<u8>, ctx: &LocalCtx) -> Result<(), String> {
907
+ fn compile_expr(expr: &ast::Expr, body: &mut Vec<u8>, ctx: &LocalCtx, state: &mut ModuleState) -> Result<(), String> {
552
908
  match expr {
553
909
  ast::Expr::Int(n) => {
554
910
  Instruction::I64Const(*n).encode(body);
@@ -557,42 +913,40 @@ fn compile_expr(expr: &ast::Expr, body: &mut Vec<u8>, ctx: &LocalCtx) -> Result<
557
913
  Instruction::F64Const(*f).encode(body);
558
914
  }
559
915
  ast::Expr::Var(name) => {
560
- if name == "true" {
561
- Instruction::I32Const(1).encode(body);
562
- } else if name == "false" {
563
- Instruction::I32Const(0).encode(body);
564
- } else {
565
- let idx = *ctx
916
+ let idx = *ctx
566
- .locals
917
+ .locals
567
- .get(name.as_str())
918
+ .get(name.as_str())
568
- .ok_or_else(|| format!("undeclared variable '{}'", name))?;
919
+ .ok_or_else(|| format!("undeclared variable '{}'", name))?;
569
- Instruction::LocalGet(idx).encode(body);
920
+ Instruction::LocalGet(idx).encode(body);
570
- }
571
921
  }
572
922
  ast::Expr::Paren(inner) => {
573
- compile_expr(inner, body, ctx)?;
923
+ compile_expr(inner, body, ctx, state)?;
574
924
  }
575
- ast::Expr::Unary(u) => {
925
+ ast::Expr::Unary(u) => match u.op {
576
- match u.op {
577
- ast::UnOp::Neg => {
926
+ ast::UnOp::Neg => {
927
+ if matches!(infer_local_type(&u.operand, ctx), PlumType::TFloat) {
928
+ compile_expr(&u.operand, body, ctx, state)?;
929
+ Instruction::F64Neg.encode(body);
930
+ } else {
578
- // WASM has no i64.neg; use i64.const(0) - operand
931
+ // WASM has no i64.neg; use 0 - operand.
579
932
  Instruction::I64Const(0).encode(body);
580
- compile_expr(&u.operand, body, ctx)?;
933
+ compile_expr(&u.operand, body, ctx, state)?;
581
934
  Instruction::I64Sub.encode(body);
582
935
  }
583
- ast::UnOp::Pos => {
584
- compile_expr(&u.operand, body, ctx)?;
585
- }
586
936
  }
937
+ ast::UnOp::Pos => {
938
+ compile_expr(&u.operand, body, ctx, state)?;
587
- }
939
+ }
940
+ },
588
941
  ast::Expr::Binary(b) => {
942
+ let is_float = matches!(infer_local_type(&b.left, ctx), PlumType::TFloat);
589
- compile_expr(&b.left, body, ctx)?;
943
+ compile_expr(&b.left, body, ctx, state)?;
590
- compile_expr(&b.right, body, ctx)?;
944
+ compile_expr(&b.right, body, ctx, state)?;
591
945
  match b.op {
592
- ast::BinOp::Add => Instruction::I64Add.encode(body),
946
+ ast::BinOp::Add => if is_float { Instruction::F64Add } else { Instruction::I64Add }.encode(body),
593
- ast::BinOp::Sub => Instruction::I64Sub.encode(body),
947
+ ast::BinOp::Sub => if is_float { Instruction::F64Sub } else { Instruction::I64Sub }.encode(body),
594
- ast::BinOp::Mul => Instruction::I64Mul.encode(body),
948
+ ast::BinOp::Mul => if is_float { Instruction::F64Mul } else { Instruction::I64Mul }.encode(body),
595
- ast::BinOp::Div => Instruction::I64DivS.encode(body),
949
+ ast::BinOp::Div => if is_float { Instruction::F64Div } else { Instruction::I64DivS }.encode(body),
596
950
  ast::BinOp::Mod => Instruction::I64RemS.encode(body),
597
951
  ast::BinOp::BitOr => Instruction::I64Or.encode(body),
598
952
  ast::BinOp::BitAnd => Instruction::I64And.encode(body),
@@ -600,40 +954,55 @@ fn compile_expr(expr: &ast::Expr, body: &mut Vec<u8>, ctx: &LocalCtx) -> Result<
600
954
  ast::BinOp::Shl => Instruction::I64Shl.encode(body),
601
955
  ast::BinOp::Shr => Instruction::I64ShrS.encode(body),
602
956
  ast::BinOp::Range => {
603
- // Range used outside of For: leave end on stack (start consumed)
957
+ // Range used outside of `for`: leave end on stack (start consumed).
604
958
  }
605
959
  }
606
960
  }
607
961
  ast::Expr::Bool(b) => {
608
- compile_expr(&b.left, body, ctx)?;
962
+ compile_expr(&b.left, body, ctx, state)?;
609
- compile_expr(&b.right, body, ctx)?;
963
+ compile_expr(&b.right, body, ctx, state)?;
610
964
  match b.op {
611
965
  ast::BoolOp::And => Instruction::I32And.encode(body),
612
966
  ast::BoolOp::Or => Instruction::I32Or.encode(body),
613
967
  }
614
968
  }
615
969
  ast::Expr::Not(inner) => {
616
- compile_expr(inner, body, ctx)?;
970
+ compile_expr(inner, body, ctx, state)?;
617
971
  Instruction::I32Eqz.encode(body);
618
972
  }
619
973
  ast::Expr::Compare(c) => {
974
+ let is_float = matches!(infer_local_type(&c.left, ctx), PlumType::TFloat);
620
- compile_expr(&c.left, body, ctx)?;
975
+ compile_expr(&c.left, body, ctx, state)?;
621
- compile_expr(&c.right, body, ctx)?;
976
+ compile_expr(&c.right, body, ctx, state)?;
977
+ if is_float {
622
- match c.op {
978
+ match c.op {
979
+ ast::CmpOp::Lt => Instruction::F64Lt,
980
+ ast::CmpOp::Lte => Instruction::F64Le,
981
+ ast::CmpOp::Eq => Instruction::F64Eq,
982
+ ast::CmpOp::Neq | ast::CmpOp::NotEq2 => Instruction::F64Ne,
983
+ ast::CmpOp::Gte => Instruction::F64Ge,
984
+ ast::CmpOp::Gt => Instruction::F64Gt,
985
+ }
986
+ .encode(body);
987
+ } else {
988
+ match c.op {
623
- ast::CmpOp::Lt => Instruction::I64LtS.encode(body),
989
+ ast::CmpOp::Lt => Instruction::I64LtS,
624
- ast::CmpOp::Lte => Instruction::I64LeS.encode(body),
990
+ ast::CmpOp::Lte => Instruction::I64LeS,
625
- ast::CmpOp::Eq => Instruction::I64Eq.encode(body),
991
+ ast::CmpOp::Eq => Instruction::I64Eq,
626
- ast::CmpOp::Neq | ast::CmpOp::NotEq2 => Instruction::I64Ne.encode(body),
992
+ ast::CmpOp::Neq | ast::CmpOp::NotEq2 => Instruction::I64Ne,
627
- ast::CmpOp::Gte => Instruction::I64GeS.encode(body),
993
+ ast::CmpOp::Gte => Instruction::I64GeS,
628
- ast::CmpOp::Gt => Instruction::I64GtS.encode(body),
994
+ ast::CmpOp::Gt => Instruction::I64GtS,
995
+ }
996
+ .encode(body);
629
997
  }
630
998
  }
631
999
  ast::Expr::Ternary(t) => {
1000
+ let result_vt = plum_type_to_valtype(&infer_local_type(&t.then, ctx));
632
- compile_expr(&t.condition, body, ctx)?;
1001
+ compile_expr(&t.condition, body, ctx, state)?;
633
- Instruction::If(BlockType::Result(ValType::I64)).encode(body);
1002
+ Instruction::If(BlockType::Result(result_vt)).encode(body);
634
- compile_expr(&t.then, body, ctx)?;
1003
+ compile_expr(&t.then, body, ctx, state)?;
635
1004
  Instruction::Else.encode(body);
636
- compile_expr(&t.else_, body, ctx)?;
1005
+ compile_expr(&t.else_, body, ctx, state)?;
637
1006
  Instruction::End.encode(body);
638
1007
  }
639
1008
  ast::Expr::FnCall(call) => {
@@ -643,7 +1012,7 @@ fn compile_expr(expr: &ast::Expr, body: &mut Vec<u8>, ctx: &LocalCtx) -> Result<
643
1012
  ast::Arg::Keyword { value, .. } => value,
644
1013
  ast::Arg::Pair { value, .. } => value,
645
1014
  };
646
- compile_expr(arg_expr, body, ctx)?;
1015
+ compile_expr(arg_expr, body, ctx, state)?;
647
1016
  }
648
1017
  let func_idx = ctx
649
1018
  .func_ids
@@ -652,13 +1021,125 @@ fn compile_expr(expr: &ast::Expr, body: &mut Vec<u8>, ctx: &LocalCtx) -> Result<
652
1021
  Instruction::Call(*func_idx).encode(body);
653
1022
  }
654
1023
  ast::Expr::Self_ => {
1024
+ let idx = ctx
1025
+ .locals
1026
+ .get("self")
1027
+ .copied()
655
- return Err("'self' not supported in v1 codegen".to_string());
1028
+ .ok_or_else(|| "codegen: 'self' used outside a method".to_string())?;
1029
+ Instruction::LocalGet(idx).encode(body);
656
1030
  }
657
- ast::Expr::TypeName(_) | ast::Expr::ClassCall(_) | ast::Expr::Attribute(_) => {
1031
+ ast::Expr::TypeName(n) => match n.as_str() {
1032
+ "True" => Instruction::I32Const(1).encode(body),
1033
+ "False" => Instruction::I32Const(0).encode(body),
1034
+ other => return Err(format!("codegen: type name '{}' is not yet supported as a value", other)),
1035
+ },
1036
+ ast::Expr::ClassCall(call) => {
1037
+ let fields = ctx
1038
+ .classes
1039
+ .get(&call.type_name)
1040
+ .ok_or_else(|| format!("codegen: unknown class '{}'", call.type_name))?
1041
+ .clone();
1042
+ let size = (fields.len() as i32) * 8;
1043
+
1044
+ let scratch_key = expr as *const ast::Expr as usize;
1045
+ let scratch_idx = *ctx
1046
+ .classcall_scratch
1047
+ .get(&scratch_key)
1048
+ .ok_or_else(|| "internal codegen error: missing classcall scratch slot".to_string())?;
1049
+ let scratch_local = ctx.classcall_scratch_base + scratch_idx;
1050
+
1051
+ // base = bump_global; bump_global += size (runtime bump allocation, so every
1052
+ // call — even inside a loop — gets a fresh, non-overlapping region).
1053
+ Instruction::GlobalGet(ctx.bump_global).encode(body);
1054
+ Instruction::LocalSet(scratch_local).encode(body);
1055
+ Instruction::GlobalGet(ctx.bump_global).encode(body);
658
- Instruction::I64Const(0).encode(body);
1056
+ Instruction::I32Const(size).encode(body);
1057
+ Instruction::I32Add.encode(body);
1058
+ Instruction::GlobalSet(ctx.bump_global).encode(body);
1059
+
1060
+ for fa in &call.fields {
1061
+ let (field_idx, field_ty) = fields
1062
+ .iter()
1063
+ .position(|(n, _)| n == &fa.name)
1064
+ .map(|i| (i, fields[i].1.clone()))
1065
+ .ok_or_else(|| format!("codegen: unknown field '{}' on class '{}'", fa.name, call.type_name))?;
1066
+ Instruction::LocalGet(scratch_local).encode(body);
1067
+ compile_expr(&fa.value, body, ctx, state)?;
1068
+ let offset = (field_idx as u64) * 8;
1069
+ match plum_type_to_valtype(&field_ty) {
1070
+ ValType::I64 => Instruction::I64Store(MemArg { offset, align: 3, memory_index: 0 }).encode(body),
1071
+ ValType::F64 => Instruction::F64Store(MemArg { offset, align: 3, memory_index: 0 }).encode(body),
1072
+ _ => Instruction::I32Store(MemArg { offset, align: 2, memory_index: 0 }).encode(body),
1073
+ };
1074
+ }
1075
+ Instruction::LocalGet(scratch_local).encode(body);
659
1076
  }
1077
+ ast::Expr::Attribute(attr) => {
1078
+ let obj_ty = infer_local_type(&attr.object, ctx);
1079
+ match &attr.attr {
1080
+ ast::AttrKind::Field(field_name) => {
1081
+ let class_name = match &obj_ty {
1082
+ PlumType::TNamed(n) => n.clone(),
1083
+ other => return Err(format!("codegen: cannot access field '{}' on non-class type {}", field_name, other)),
1084
+ };
1085
+ let fields = ctx
1086
+ .classes
1087
+ .get(&class_name)
1088
+ .ok_or_else(|| format!("codegen: unknown class '{}'", class_name))?;
1089
+ let (field_idx, field_ty) = fields
1090
+ .iter()
1091
+ .position(|(n, _)| n == field_name)
1092
+ .map(|i| (i, fields[i].1.clone()))
1093
+ .ok_or_else(|| format!("codegen: no field '{}' on class '{}'", field_name, class_name))?;
1094
+ compile_expr(&attr.object, body, ctx, state)?;
1095
+ let offset = (field_idx as u64) * 8;
1096
+ match plum_type_to_valtype(&field_ty) {
1097
+ ValType::I64 => Instruction::I64Load(MemArg { offset, align: 3, memory_index: 0 }).encode(body),
1098
+ ValType::F64 => Instruction::F64Load(MemArg { offset, align: 3, memory_index: 0 }).encode(body),
1099
+ _ => Instruction::I32Load(MemArg { offset, align: 2, memory_index: 0 }).encode(body),
1100
+ };
1101
+ }
1102
+ ast::AttrKind::Method(call) => {
1103
+ let class_name = match &obj_ty {
1104
+ PlumType::TNamed(n) => n.clone(),
1105
+ other => return Err(format!("codegen: cannot call method '{}' on non-class type {}", call.name, other)),
1106
+ };
1107
+ let key = format!("{}::{}", class_name, call.name);
1108
+ let func_idx = *ctx
1109
+ .func_ids
1110
+ .get(&key)
1111
+ .ok_or_else(|| format!("codegen: unknown method '{}.{}'", class_name, call.name))?;
1112
+ compile_expr(&attr.object, body, ctx, state)?; // push self
1113
+ for arg in &call.args {
1114
+ let arg_expr = match arg {
1115
+ ast::Arg::Positional(e) => e,
1116
+ ast::Arg::Keyword { value, .. } => value,
1117
+ ast::Arg::Pair { value, .. } => value,
1118
+ };
1119
+ compile_expr(arg_expr, body, ctx, state)?;
1120
+ }
1121
+ Instruction::Call(func_idx).encode(body);
1122
+ }
1123
+ }
1124
+ }
660
- ast::Expr::String(_) => {
1125
+ ast::Expr::String(s) => {
1126
+ let mut text = String::new();
1127
+ for part in &s.parts {
1128
+ match part {
1129
+ ast::StringPart::Text(t) => text.push_str(t),
1130
+ ast::StringPart::Interp(_) => {
1131
+ return Err("codegen: string interpolation is not yet supported".to_string());
1132
+ }
1133
+ }
1134
+ }
1135
+ let bytes = text.as_bytes();
1136
+ let offset = state.next_string_offset;
1137
+ let mut data = Vec::with_capacity(4 + bytes.len());
1138
+ data.extend_from_slice(&(bytes.len() as u32).to_le_bytes());
1139
+ data.extend_from_slice(bytes);
1140
+ state.next_string_offset += data.len() as u32;
1141
+ state.data_segments.push((offset, data));
661
- Instruction::I32Const(0).encode(body);
1142
+ Instruction::I32Const(offset as i32).encode(body);
662
1143
  }
663
1144
  }
664
1145
  Ok(())
plum-wasm-codegen/tests/codegen_tests.rs CHANGED
@@ -31,8 +31,12 @@ fn output_validates() {
31
31
 
32
32
  #[test]
33
33
  fn factorial_compiles() {
34
- // Simple recursive function that the v1 codegen can handle
34
+ let src = "\
35
- let src = "factorial(x: Int) -> Int =\n x\n";
35
+ factorial(x: Int) -> Int =
36
+ if x < 2
37
+ return 1
38
+ return x * factorial(x - 1)
39
+ ";
36
40
  let source = parse(src);
37
41
  let bytes = compile_source(&source).expect("factorial should compile");
38
42
  assert_eq!(&bytes[0..4], b"\0asm");
@@ -49,3 +53,255 @@ fn give42_compiles_and_exports() {
49
53
  let result = wasmparser::validate(&bytes);
50
54
  assert!(result.is_ok(), "wasm validation failed: {:?}", result.err());
51
55
  }
56
+
57
+ fn assert_valid(src: &str) -> Vec<u8> {
58
+ let source = parse(src);
59
+ let bytes = compile_source(&source).unwrap_or_else(|e| panic!("compile failed for {:?}: {}", src, e));
60
+ let result = wasmparser::validate(&bytes);
61
+ assert!(result.is_ok(), "wasm validation failed: {:?}", result.err());
62
+ bytes
63
+ }
64
+
65
+ #[test]
66
+ fn bool_literals_compile() {
67
+ assert_valid("main() -> Bool =\n True\n");
68
+ assert_valid("main() -> Bool =\n False\n");
69
+ }
70
+
71
+ #[test]
72
+ fn string_literal_compiles() {
73
+ assert_valid("main() -> Str =\n \"hello\"\n");
74
+ }
75
+
76
+ #[test]
77
+ fn empty_string_literal_compiles() {
78
+ assert_valid("main() -> Str =\n \"\"\n");
79
+ }
80
+
81
+ #[test]
82
+ fn string_interpolation_is_a_clear_error() {
83
+ let src = "main() -> Str =\n x = 1\n \"{x}\"\n";
84
+ let source = parse(src);
85
+ let err = compile_source(&source).expect_err("interpolation should not silently compile");
86
+ assert!(err.contains("interpolation"), "got: {}", err);
87
+ }
88
+
89
+ #[test]
90
+ fn float_arithmetic_and_negation_compile() {
91
+ assert_valid("main(x: Float) -> Float =\n y = -x\n y + 1.5\n");
92
+ }
93
+
94
+ #[test]
95
+ fn class_field_and_method_compile() {
96
+ let src = "\
97
+ type Cat =
98
+ name: Str
99
+ age: Int
100
+
101
+ getAge<Cat>() -> Int =
102
+ self.age
103
+
104
+ makeCat() -> Int =
105
+ c = Cat(name: \"x\", age: 3)
106
+ c.getAge()
107
+ ";
108
+ assert_valid(src);
109
+ }
110
+
111
+ #[test]
112
+ fn nested_class_call_compiles() {
113
+ let src = "\
114
+ type Pair =
115
+ a: Int
116
+ b: Int
117
+
118
+ type Wrapper =
119
+ inner: Pair
120
+ tag: Int
121
+
122
+ make() -> Int =
123
+ w = Wrapper(inner: Pair(a: 1, b: 2), tag: 9)
124
+ w.tag
125
+ ";
126
+ assert_valid(src);
127
+ }
128
+
129
+ #[test]
130
+ fn match_with_int_and_wildcard_compiles() {
131
+ let src = "main(a: Int) -> Int =\n match a\n 1 =>\n return 10\n _ =>\n return 0\n";
132
+ assert_valid(src);
133
+ }
134
+
135
+ #[test]
136
+ fn match_binding_pattern_compiles() {
137
+ let src = "main(a: Int) -> Int =\n match a\n x =>\n return x\n";
138
+ assert_valid(src);
139
+ }
140
+
141
+ #[test]
142
+ fn match_bool_variant_pattern_compiles() {
143
+ let src = "main(a: Bool) -> Int =\n match a\n True =>\n return 1\n False =>\n return 0\n";
144
+ assert_valid(src);
145
+ }
146
+
147
+ #[test]
148
+ fn match_string_pattern_is_a_clear_error() {
149
+ let src = "main(a: Str) -> Int =\n match a\n \"x\" =>\n 1\n _ =>\n 0\n";
150
+ let source = parse(src);
151
+ let err = compile_source(&source).expect_err("string match patterns are not yet supported");
152
+ assert!(err.contains("string match"), "got: {}", err);
153
+ }
154
+
155
+ /// Runs `main`'s wasm bytes and returns its i64 result. wasm's own validator
156
+ /// (via wasmparser, above) only proves the module is well-formed — it can't catch
157
+ /// wrong *values*, so these tests actually execute the compiled output.
158
+ fn run_main(bytes: &[u8]) -> i64 {
159
+ let engine = wasmtime::Engine::default();
160
+ let module = wasmtime::Module::new(&engine, bytes).expect("module should be loadable");
161
+ let mut store = wasmtime::Store::new(&engine, ());
162
+ let instance = wasmtime::Instance::new(&mut store, &module, &[]).expect("module should instantiate");
163
+ let main = instance
164
+ .get_typed_func::<(), i64>(&mut store, "main")
165
+ .expect("main should have signature () -> i64");
166
+ main.call(&mut store, ()).expect("main should not trap")
167
+ }
168
+
169
+ #[test]
170
+ fn factorial_runs_correctly() {
171
+ let src = "\
172
+ factorial(x: Int) -> Int =
173
+ if x < 2
174
+ return 1
175
+ return x * factorial(x - 1)
176
+
177
+ main() -> Int =
178
+ factorial(5)
179
+ ";
180
+ let source = parse(src);
181
+ let bytes = compile_source(&source).expect("compile failed");
182
+ assert_eq!(run_main(&bytes), 120);
183
+ }
184
+
185
+ #[test]
186
+ fn class_field_and_method_run_correctly() {
187
+ let src = "\
188
+ type Cat =
189
+ name: Str
190
+ age: Int
191
+
192
+ getAge<Cat>() -> Int =
193
+ self.age
194
+
195
+ main() -> Int =
196
+ c = Cat(name: \"x\", age: 7)
197
+ c.getAge()
198
+ ";
199
+ let source = parse(src);
200
+ let bytes = compile_source(&source).expect("compile failed");
201
+ assert_eq!(run_main(&bytes), 7);
202
+ }
203
+
204
+ #[test]
205
+ fn nested_class_call_runs_correctly() {
206
+ let src = "\
207
+ type Pair =
208
+ a: Int
209
+ b: Int
210
+
211
+ type Wrapper =
212
+ inner: Pair
213
+ tag: Int
214
+
215
+ main() -> Int =
216
+ w = Wrapper(inner: Pair(a: 11, b: 22), tag: 99)
217
+ w.inner.b
218
+ ";
219
+ let source = parse(src);
220
+ let bytes = compile_source(&source).expect("compile failed");
221
+ assert_eq!(run_main(&bytes), 22);
222
+ }
223
+
224
+ #[test]
225
+ fn repeated_class_call_in_a_loop_does_not_alias() {
226
+ // Regression test: class instances are bump-allocated at *runtime* (via a
227
+ // mutable wasm global), not at a compile-time-fixed address — otherwise every
228
+ // iteration's `Box(...)` would alias the same memory and this would sum to 5*4=20
229
+ // instead of 0+1+2+3+4=10.
230
+ let src = "\
231
+ type Box =
232
+ v: Int
233
+
234
+ sumBoxes() -> Int =
235
+ total = 0
236
+ for i in 0..5
237
+ b = Box(v: i)
238
+ total = total + b.v
239
+ return total
240
+
241
+ main() -> Int =
242
+ sumBoxes()
243
+ ";
244
+ let source = parse(src);
245
+ let bytes = compile_source(&source).expect("compile failed");
246
+ assert_eq!(run_main(&bytes), 10);
247
+ }
248
+
249
+ #[test]
250
+ fn match_int_and_wildcard_run_correctly() {
251
+ let src = "\
252
+ classify(a: Int) -> Int =
253
+ match a
254
+ 1 =>
255
+ return 100
256
+ 2 =>
257
+ return 200
258
+ _ =>
259
+ return 0
260
+
261
+ main() -> Int =
262
+ classify(2)
263
+ ";
264
+ let source = parse(src);
265
+ let bytes = compile_source(&source).expect("compile failed");
266
+ assert_eq!(run_main(&bytes), 200);
267
+ }
268
+
269
+ #[test]
270
+ fn match_bool_variant_pattern_runs_correctly() {
271
+ // Regression test: True/False are built-in Bool variants and must be treated as
272
+ // tag comparisons, not bindings, even without an explicit `enum Bool` in this
273
+ // source file — otherwise the first arm always "matches" (as a rebinding) and
274
+ // `pick(False)` would wrongly return 1.
275
+ let src = "\
276
+ pick(a: Bool) -> Int =
277
+ match a
278
+ True =>
279
+ return 1
280
+ False =>
281
+ return 0
282
+
283
+ main() -> Int =
284
+ pick(False)
285
+ ";
286
+ let source = parse(src);
287
+ let bytes = compile_source(&source).expect("compile failed");
288
+ assert_eq!(run_main(&bytes), 0);
289
+ }
290
+
291
+ #[test]
292
+ fn lowercase_single_word_function_call_runs_correctly() {
293
+ // Regression test: `factorial(...)` (an all-lowercase, no-uppercase, no-underscore
294
+ // callee) used to fail to parse at all — `var_identifier` and `fn_identifier` both
295
+ // matched its text and the grammar's lexer would nondeterministically commit to
296
+ // `var_identifier`, breaking every such call site.
297
+ let src = "\
298
+ double(n: Int) -> Int =
299
+ n * 2
300
+
301
+ main() -> Int =
302
+ double(21)
303
+ ";
304
+ let source = parse(src);
305
+ let bytes = compile_source(&source).expect("compile failed");
306
+ assert_eq!(run_main(&bytes), 42);
307
+ }