plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
da1c377
— Peter John
2026-07-23T20:06:30+05:30
feat(plum-wasm-codegen): compile for-v-in-variadic-param iteration
- examples/functions.plum +4 -1
- plum-wasm-codegen/src/lib.rs +114 -3
- plum-wasm-codegen/tests/codegen_tests.rs +34 -0
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
|
-
|
|
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(
|
|
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
|
-
|
|
1661
|
+
for v in &f.vars {
|
|
1662
|
+
self.bind(v, (**elem).clone());
|
|
1663
|
+
}
|
|
1664
|
+
} else {
|
|
1665
|
+
for v in &f.vars {
|
|
1640
|
-
|
|
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
|
+
}
|