plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
8ecbf56
— Peter John
2026-07-20T11:47:23+05:30
feat(plum-wasm-codegen): compile general enum match patterns (tags + constructors)
plum-wasm-codegen/src/lib.rs
CHANGED
|
@@ -431,12 +431,25 @@ impl<'a> Collector<'a> {
|
|
|
431
431
|
for case in &m.cases {
|
|
432
432
|
let saved = self.env.clone();
|
|
433
433
|
if m.subjects.len() == 1 {
|
|
434
|
+
match case.patterns.first() {
|
|
434
|
-
|
|
435
|
+
Some(ast::CasePattern::Name(n)) => {
|
|
435
|
-
|
|
436
|
+
let is_variant = n.chars().next().map(|c| c.is_uppercase()).unwrap_or(false)
|
|
436
|
-
|
|
437
|
+
&& self.cctx.enum_variants.contains_key(n);
|
|
437
|
-
|
|
438
|
+
if !is_variant {
|
|
438
|
-
|
|
439
|
+
self.bind(n, subject_ty.clone());
|
|
440
|
+
}
|
|
439
441
|
}
|
|
442
|
+
Some(ast::CasePattern::Class { name, fields }) => {
|
|
443
|
+
if let Some(info) = self.cctx.enum_variants.get(name) {
|
|
444
|
+
let field_types = info.field_types.clone();
|
|
445
|
+
for (f, fty) in fields.iter().zip(field_types.iter()) {
|
|
446
|
+
if let ast::CasePattern::Name(n) = f {
|
|
447
|
+
self.bind(n, fty.clone());
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
_ => {}
|
|
440
453
|
}
|
|
441
454
|
}
|
|
442
455
|
self.walk_block(&case.body);
|
|
@@ -901,7 +914,9 @@ fn compile_match_arms(
|
|
|
901
914
|
}
|
|
902
915
|
ast::CasePattern::String(_) => Err("codegen: string match patterns are not yet supported".to_string()),
|
|
903
916
|
ast::CasePattern::Float(_) => Err("codegen: float match patterns are not yet supported".to_string()),
|
|
917
|
+
ast::CasePattern::Class { name, fields } => {
|
|
904
|
-
|
|
918
|
+
compile_variant_constructor_arm(name, fields, subject_vt, scratch_local, case, rest, body, ctx, state)
|
|
919
|
+
}
|
|
905
920
|
}
|
|
906
921
|
}
|
|
907
922
|
|
|
@@ -916,15 +931,14 @@ fn compile_variant_eq_arm(
|
|
|
916
931
|
ctx: &LocalCtx,
|
|
917
932
|
state: &mut ModuleState,
|
|
918
933
|
) -> Result<(), String> {
|
|
919
|
-
// Only Bool's own variants have a concrete runtime representation in v1.5.
|
|
920
|
-
let
|
|
934
|
+
let info = ctx
|
|
921
|
-
|
|
935
|
+
.enum_variants
|
|
922
|
-
|
|
936
|
+
.get(name)
|
|
923
|
-
|
|
937
|
+
.ok_or_else(|| format!("codegen: unknown enum variant '{}'", name))?;
|
|
924
|
-
};
|
|
925
938
|
if subject_vt != ValType::I32 {
|
|
926
|
-
return Err("codegen:
|
|
939
|
+
return Err(format!("codegen: enum tag pattern '{}' against a non-enum subject", name));
|
|
927
940
|
}
|
|
941
|
+
let tag = info.tag;
|
|
928
942
|
Instruction::LocalGet(scratch_local).encode(body);
|
|
929
943
|
Instruction::I32Const(tag).encode(body);
|
|
930
944
|
Instruction::I32Eq.encode(body);
|
|
@@ -936,6 +950,69 @@ fn compile_variant_eq_arm(
|
|
|
936
950
|
Ok(())
|
|
937
951
|
}
|
|
938
952
|
|
|
953
|
+
#[allow(clippy::too_many_arguments)]
|
|
954
|
+
fn compile_variant_constructor_arm(
|
|
955
|
+
name: &str,
|
|
956
|
+
fields: &[ast::CasePattern],
|
|
957
|
+
subject_vt: ValType,
|
|
958
|
+
scratch_local: u32,
|
|
959
|
+
case: &ast::Case,
|
|
960
|
+
rest: &[ast::Case],
|
|
961
|
+
body: &mut Vec<u8>,
|
|
962
|
+
ctx: &LocalCtx,
|
|
963
|
+
state: &mut ModuleState,
|
|
964
|
+
) -> Result<(), String> {
|
|
965
|
+
let info = ctx
|
|
966
|
+
.enum_variants
|
|
967
|
+
.get(name)
|
|
968
|
+
.ok_or_else(|| format!("codegen: unknown enum variant '{}'", name))?;
|
|
969
|
+
if subject_vt != ValType::I32 {
|
|
970
|
+
return Err(format!("codegen: constructor pattern '{}' against a non-enum subject", name));
|
|
971
|
+
}
|
|
972
|
+
if fields.len() != info.field_types.len() {
|
|
973
|
+
return Err(format!(
|
|
974
|
+
"codegen: constructor pattern '{}' expects {} field(s), got {}",
|
|
975
|
+
name, info.field_types.len(), fields.len()
|
|
976
|
+
));
|
|
977
|
+
}
|
|
978
|
+
let tag = info.tag;
|
|
979
|
+
let field_types = info.field_types.clone();
|
|
980
|
+
|
|
981
|
+
Instruction::LocalGet(scratch_local).encode(body);
|
|
982
|
+
Instruction::I32Load(MemArg { offset: 0, align: 2, memory_index: 0 }).encode(body);
|
|
983
|
+
Instruction::I32Const(tag).encode(body);
|
|
984
|
+
Instruction::I32Eq.encode(body);
|
|
985
|
+
Instruction::If(BlockType::Empty).encode(body);
|
|
986
|
+
for (i, (pat, field_ty)) in fields.iter().zip(field_types.iter()).enumerate() {
|
|
987
|
+
let bind_name = match pat {
|
|
988
|
+
ast::CasePattern::Name(n) => Some(n.as_str()),
|
|
989
|
+
ast::CasePattern::Wildcard => None,
|
|
990
|
+
_ => return Err("codegen: only bare bindings or '_' are supported inside a constructor pattern".to_string()),
|
|
991
|
+
};
|
|
992
|
+
if let Some(n) = bind_name {
|
|
993
|
+
let idx = ctx
|
|
994
|
+
.locals
|
|
995
|
+
.get(n)
|
|
996
|
+
.copied()
|
|
997
|
+
.ok_or_else(|| format!("internal codegen error: missing binding local '{}'", n))?;
|
|
998
|
+
Instruction::LocalGet(scratch_local).encode(body);
|
|
999
|
+
let offset = ((i + 1) as u64) * 8;
|
|
1000
|
+
match plum_type_to_valtype(field_ty) {
|
|
1001
|
+
ValType::I64 => Instruction::I64Load(MemArg { offset, align: 3, memory_index: 0 }),
|
|
1002
|
+
ValType::F64 => Instruction::F64Load(MemArg { offset, align: 3, memory_index: 0 }),
|
|
1003
|
+
_ => Instruction::I32Load(MemArg { offset, align: 2, memory_index: 0 }),
|
|
1004
|
+
}.encode(body);
|
|
1005
|
+
Instruction::LocalSet(idx).encode(body);
|
|
1006
|
+
ctx.type_env.borrow_mut().insert(n.to_string(), TypeScheme::mono(field_ty.clone()));
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
compile_block(&case.body, body, ctx, state)?;
|
|
1010
|
+
Instruction::Else.encode(body);
|
|
1011
|
+
compile_match_arms(rest, subject_vt, scratch_local, body, ctx, state)?;
|
|
1012
|
+
Instruction::End.encode(body);
|
|
1013
|
+
Ok(())
|
|
1014
|
+
}
|
|
1015
|
+
|
|
939
1016
|
fn plum_type_from_valtype_hint(vt: ValType) -> PlumType {
|
|
940
1017
|
match vt {
|
|
941
1018
|
ValType::I64 => PlumType::TInt,
|
plum-wasm-codegen/tests/codegen_tests.rs
CHANGED
|
@@ -422,3 +422,50 @@ main() -> Int =
|
|
|
422
422
|
let bytes = compile_source(&source).expect("compile failed");
|
|
423
423
|
assert_eq!(run_main(&bytes), 12);
|
|
424
424
|
}
|
|
425
|
+
|
|
426
|
+
#[test]
|
|
427
|
+
fn non_bool_bare_tag_pattern_runs_correctly() {
|
|
428
|
+
let src = "\
|
|
429
|
+
enum Color =
|
|
430
|
+
| Red
|
|
431
|
+
| Green
|
|
432
|
+
| Blue
|
|
433
|
+
|
|
434
|
+
code(c: Color) -> Int =
|
|
435
|
+
match c
|
|
436
|
+
Red =>
|
|
437
|
+
return 1
|
|
438
|
+
Green =>
|
|
439
|
+
return 2
|
|
440
|
+
Blue =>
|
|
441
|
+
return 3
|
|
442
|
+
|
|
443
|
+
main() -> Int =
|
|
444
|
+
code(Green)
|
|
445
|
+
";
|
|
446
|
+
let source = parse(src);
|
|
447
|
+
let bytes = compile_source(&source).expect("compile failed");
|
|
448
|
+
assert_eq!(run_main(&bytes), 2);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
#[test]
|
|
452
|
+
fn constructor_pattern_wildcard_field_runs_correctly() {
|
|
453
|
+
let src = "\
|
|
454
|
+
enum Option =
|
|
455
|
+
| Some(Int)
|
|
456
|
+
| None
|
|
457
|
+
|
|
458
|
+
isSome(o: Option) -> Int =
|
|
459
|
+
match o
|
|
460
|
+
Some(_) =>
|
|
461
|
+
return 1
|
|
462
|
+
None =>
|
|
463
|
+
return 0
|
|
464
|
+
|
|
465
|
+
main() -> Int =
|
|
466
|
+
isSome(Some(99))
|
|
467
|
+
";
|
|
468
|
+
let source = parse(src);
|
|
469
|
+
let bytes = compile_source(&source).expect("compile failed");
|
|
470
|
+
assert_eq!(run_main(&bytes), 1);
|
|
471
|
+
}
|