plum

#treesitter#compiler#wasm

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

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


da1c377Peter John 2026-07-23T20:06:30+05:30
feat(plum-wasm-codegen): compile for-v-in-variadic-param iteration
examples/functions.plum CHANGED
@@ -8,7 +8,10 @@ withDefault(a: Int, step: Int = 1) -> Int =
8
8
  a + step
9
9
 
10
10
  sumAll(nums: ...Int) -> Int =
11
+ total = 0
12
+ for v in nums
13
+ total = total + v
11
- todo
14
+ total
12
15
 
13
16
  wrap(value: a) -> Bool =
14
17
  True
plum-wasm-codegen/src/lib.rs CHANGED
@@ -299,6 +299,12 @@ struct LocalCtx<'a> {
299
299
  nested_class_scratch_base: u32,
300
300
  /// `CasePattern::Class` identity (pointer address) -> scratch slot offset.
301
301
  nested_class_scratch: HashMap<usize, u32>,
302
+ /// First local index reserved for variadic-`for` scratch temporaries (2 `i32`
303
+ /// slots per `for` statement that iterates a `TVariadic`: count, loop index).
304
+ variadic_for_scratch_base: u32,
305
+ /// `For` stmt identity (pointer address) -> slot number (multiply by 2 and add
306
+ /// `variadic_for_scratch_base` for the count local; +1 more for the index local).
307
+ variadic_for_scratch: HashMap<usize, u32>,
302
308
  func_ids: &'a HashMap<String, u32>,
303
309
  func_sigs: &'a HashMap<String, FuncSig>,
304
310
  closures: &'a HashMap<usize, ClosureInfo>,
@@ -975,8 +981,12 @@ impl<'a, 'c> ClosureWalker<'a, 'c> {
975
981
  }
976
982
  ast::Stmt::For(f) => {
977
983
  self.walk_expr(&f.iter, None);
984
+ let elem_ty = match plum_checker::infer_expr(&f.iter, &self.env, &self.cctx) {
985
+ Ok(PlumType::TVariadic(elem)) => *elem,
986
+ _ => PlumType::TInt,
987
+ };
978
988
  for v in &f.vars {
979
- self.env.insert(v.clone(), TypeScheme::mono(PlumType::TInt));
989
+ self.env.insert(v.clone(), TypeScheme::mono(elem_ty.clone()));
980
990
  self.locals.insert(v.clone());
981
991
  }
982
992
  self.walk_block(&f.body);
@@ -1539,6 +1549,13 @@ struct Collector<'a> {
1539
1549
  /// / `all_subjects`, so this only covers depth >= 1.
1540
1550
  nested_class_scratch: HashMap<usize, u32>,
1541
1551
  next_nested_class_slot: u32,
1552
+ /// `For` stmt identity (pointer address) -> a slot number; each slot reserves 2
1553
+ /// consecutive `i32` scratch locals for variadic iteration (`for v in nums`):
1554
+ /// [count, loop index]. Only `for` statements whose iterable is a `TVariadic`
1555
+ /// use this — an ordinary range `for` reuses its own loop var as the counter
1556
+ /// and needs no extra scratch locals.
1557
+ variadic_for_scratch: HashMap<usize, u32>,
1558
+ next_variadic_for_slot: u32,
1542
1559
  }
1543
1560
 
1544
1561
  impl<'a> Collector<'a> {
@@ -1636,8 +1653,18 @@ impl<'a> Collector<'a> {
1636
1653
  }
1637
1654
  ast::Stmt::For(f) => {
1638
1655
  self.walk_expr(&f.iter);
1656
+ let iter_ty = plum_checker::infer_expr(&f.iter, &self.env, &self.cctx).unwrap_or(PlumType::TInt);
1657
+ if let PlumType::TVariadic(elem) = &iter_ty {
1658
+ let idx = self.next_variadic_for_slot;
1659
+ self.next_variadic_for_slot += 1;
1660
+ self.variadic_for_scratch.insert(f as *const ast::For as usize, idx);
1639
- for v in &f.vars {
1661
+ for v in &f.vars {
1662
+ self.bind(v, (**elem).clone());
1663
+ }
1664
+ } else {
1665
+ for v in &f.vars {
1640
- self.bind(v, PlumType::TInt);
1666
+ self.bind(v, PlumType::TInt);
1667
+ }
1641
1668
  }
1642
1669
  self.walk_block(&f.body);
1643
1670
  }
@@ -1776,6 +1803,8 @@ fn compile_fn_body(f: &ast::Fn, ctx: &CompileCtx, state: &mut ModuleState) -> Re
1776
1803
  next_closure_slot: 0,
1777
1804
  nested_class_scratch: HashMap::new(),
1778
1805
  next_nested_class_slot: 0,
1806
+ variadic_for_scratch: HashMap::new(),
1807
+ next_variadic_for_slot: 0,
1779
1808
  };
1780
1809
  if let ast::FnBody::Block(block) = &f.body {
1781
1810
  collector.walk_block(block);
@@ -1828,6 +1857,14 @@ fn compile_fn_body(f: &ast::Fn, ctx: &CompileCtx, state: &mut ModuleState) -> Re
1828
1857
  idx += 1;
1829
1858
  }
1830
1859
 
1860
+ let variadic_for_scratch_base = idx;
1861
+ let variadic_for_scratch_count = collector.variadic_for_scratch.values().copied().max().map(|m| m + 1).unwrap_or(0);
1862
+ for _ in 0..variadic_for_scratch_count {
1863
+ groups.push(ValType::I32); // count
1864
+ groups.push(ValType::I32); // loop index
1865
+ idx += 2;
1866
+ }
1867
+
1831
1868
  let closure_scratch_base = idx;
1832
1869
  let closure_scratch_count = collector.closure_scratch.values().copied().max().map(|m| m + 2).unwrap_or(0);
1833
1870
  for _ in 0..closure_scratch_count {
@@ -1854,6 +1891,8 @@ fn compile_fn_body(f: &ast::Fn, ctx: &CompileCtx, state: &mut ModuleState) -> Re
1854
1891
  closure_scratch: collector.closure_scratch,
1855
1892
  nested_class_scratch_base,
1856
1893
  nested_class_scratch: collector.nested_class_scratch,
1894
+ variadic_for_scratch_base,
1895
+ variadic_for_scratch: collector.variadic_for_scratch,
1857
1896
  func_ids: &ctx.func_ids,
1858
1897
  func_sigs: &ctx.func_sigs,
1859
1898
  closures: &ctx.closures,
@@ -2118,6 +2157,66 @@ fn compile_stmt(stmt: &ast::Stmt, body: &mut Vec<u8>, ctx: &LocalCtx, state: &mu
2118
2157
  return Ok(());
2119
2158
  }
2120
2159
  }
2160
+ if let PlumType::TVariadic(elem_ty) = infer_local_type(&f.iter, ctx) {
2161
+ if f.vars.len() != 1 {
2162
+ return Err("codegen: for-loop over a variadic param must bind exactly one variable".to_string());
2163
+ }
2164
+ let var_name = &f.vars[0];
2165
+ let var_idx = ctx
2166
+ .locals
2167
+ .get(var_name)
2168
+ .copied()
2169
+ .ok_or_else(|| format!("undeclared loop var '{}'", var_name))?;
2170
+ ctx.type_env.borrow_mut().insert(var_name.clone(), TypeScheme::mono((*elem_ty).clone()));
2171
+
2172
+ let scratch_key = f as *const ast::For as usize;
2173
+ let slot = *ctx
2174
+ .variadic_for_scratch
2175
+ .get(&scratch_key)
2176
+ .ok_or_else(|| "internal codegen error: missing variadic-for scratch slot".to_string())?;
2177
+ let count_local = ctx.variadic_for_scratch_base + slot * 2;
2178
+ let index_local = count_local + 1;
2179
+ let elem_vt = plum_type_to_valtype(&elem_ty);
2180
+
2181
+ // count_local = i32.wrap_i64(load_i64([nums + 0]))
2182
+ compile_expr(&f.iter, body, ctx, state)?;
2183
+ Instruction::I64Load(MemArg { offset: 0, align: 3, memory_index: 0 }).encode(body);
2184
+ Instruction::I32WrapI64.encode(body);
2185
+ Instruction::LocalSet(count_local).encode(body);
2186
+
2187
+ // index_local = 0
2188
+ Instruction::I32Const(0).encode(body);
2189
+ Instruction::LocalSet(index_local).encode(body);
2190
+
2191
+ Instruction::Block(BlockType::Empty).encode(body);
2192
+ Instruction::Loop(BlockType::Empty).encode(body);
2193
+ Instruction::LocalGet(index_local).encode(body);
2194
+ Instruction::LocalGet(count_local).encode(body);
2195
+ Instruction::I32GeS.encode(body);
2196
+ Instruction::BrIf(1).encode(body);
2197
+
2198
+ // var = load_elem([nums + 8 + index * 8])
2199
+ compile_expr(&f.iter, body, ctx, state)?;
2200
+ Instruction::I32Const(8).encode(body);
2201
+ Instruction::I32Add.encode(body);
2202
+ Instruction::LocalGet(index_local).encode(body);
2203
+ Instruction::I32Const(8).encode(body);
2204
+ Instruction::I32Mul.encode(body);
2205
+ Instruction::I32Add.encode(body);
2206
+ emit_load(elem_vt, 0, body);
2207
+ Instruction::LocalSet(var_idx).encode(body);
2208
+
2209
+ compile_block(&f.body, body, ctx, state)?;
2210
+
2211
+ Instruction::LocalGet(index_local).encode(body);
2212
+ Instruction::I32Const(1).encode(body);
2213
+ Instruction::I32Add.encode(body);
2214
+ Instruction::LocalSet(index_local).encode(body);
2215
+ Instruction::Br(0).encode(body);
2216
+ Instruction::End.encode(body);
2217
+ Instruction::End.encode(body);
2218
+ return Ok(());
2219
+ }
2121
2220
  compile_expr(&f.iter, body, ctx, state)?;
2122
2221
  Instruction::Drop.encode(body);
2123
2222
  }
@@ -3248,6 +3347,8 @@ fn compile_closure_body(
3248
3347
  next_closure_slot: 0,
3249
3348
  nested_class_scratch: HashMap::new(),
3250
3349
  next_nested_class_slot: 0,
3350
+ variadic_for_scratch: HashMap::new(),
3351
+ next_variadic_for_slot: 0,
3251
3352
  };
3252
3353
  collector.walk_block(&cl.body);
3253
3354
 
@@ -3300,6 +3401,14 @@ fn compile_closure_body(
3300
3401
  idx += 1;
3301
3402
  }
3302
3403
 
3404
+ let variadic_for_scratch_base = idx;
3405
+ let variadic_for_scratch_count = collector.variadic_for_scratch.values().copied().max().map(|m| m + 1).unwrap_or(0);
3406
+ for _ in 0..variadic_for_scratch_count {
3407
+ groups.push(ValType::I32); // count
3408
+ groups.push(ValType::I32); // loop index
3409
+ idx += 2;
3410
+ }
3411
+
3303
3412
  let closure_scratch_base = idx;
3304
3413
  let closure_scratch_count = collector.closure_scratch.values().copied().max().map(|m| m + 2).unwrap_or(0);
3305
3414
  for _ in 0..closure_scratch_count {
@@ -3334,6 +3443,8 @@ fn compile_closure_body(
3334
3443
  closure_scratch: collector.closure_scratch,
3335
3444
  nested_class_scratch_base,
3336
3445
  nested_class_scratch: collector.nested_class_scratch,
3446
+ variadic_for_scratch_base,
3447
+ variadic_for_scratch: collector.variadic_for_scratch,
3337
3448
  func_ids: &ctx.func_ids,
3338
3449
  func_sigs: &ctx.func_sigs,
3339
3450
  closures: &ctx.closures,
plum-wasm-codegen/tests/codegen_tests.rs CHANGED
@@ -1443,3 +1443,37 @@ main() -> Int =
1443
1443
  let bytes = compile_source(&source).expect("compile failed");
1444
1444
  assert_eq!(run_main(&bytes), 60);
1445
1445
  }
1446
+
1447
+ #[test]
1448
+ fn sum_all_variadic_int_runs_correctly() {
1449
+ let src = "\
1450
+ sumAll(nums: ...Int) -> Int =
1451
+ total = 0
1452
+ for v in nums
1453
+ total = total + v
1454
+ total
1455
+
1456
+ main() -> Int =
1457
+ sumAll(1, 2, 3, 4)
1458
+ ";
1459
+ let source = parse(src);
1460
+ let bytes = compile_source(&source).expect("compile failed");
1461
+ assert_eq!(run_main(&bytes), 10);
1462
+ }
1463
+
1464
+ #[test]
1465
+ fn sum_all_variadic_int_with_zero_args_runs_correctly() {
1466
+ let src = "\
1467
+ sumAll(nums: ...Int) -> Int =
1468
+ total = 0
1469
+ for v in nums
1470
+ total = total + v
1471
+ total
1472
+
1473
+ main() -> Int =
1474
+ sumAll()
1475
+ ";
1476
+ let source = parse(src);
1477
+ let bytes = compile_source(&source).expect("compile failed");
1478
+ assert_eq!(run_main(&bytes), 0);
1479
+ }