plum

#treesitter#compiler#wasm

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

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


0e1801aPeter John 2026-07-19T21:52:28+05:30
test: add example programs covering the language's current syntax
examples/basics.plum ADDED
@@ -0,0 +1,28 @@
1
+ module basics
2
+
3
+ import std/io
4
+
5
+ MAX_RETRIES = 3
6
+ PI = 3.14159
7
+ GREETING = "hello"
8
+
9
+ main() =
10
+ dec = 42
11
+ hex = 0xFF
12
+ bin = 0b1010
13
+ flt = 3.14
14
+ flt2 = 12.0f
15
+ name = "plum"
16
+ yes = True
17
+ no = False
18
+
19
+ sum = 1 + 2 * 3 - 4 / 2
20
+ bits = 0b1100 & 0b1010 | 0b0001
21
+ shifted = 1 << 4
22
+ cmp = sum > 0 && bits != 0 || False
23
+ grouped = {1 + 2} * {3 - 1}
24
+ picked = cmp ? sum : bits
25
+ negated = -sum
26
+ positive = +sum
27
+ inverted = !cmp
28
+ a, b = 1, 2
examples/control_flow.plum ADDED
@@ -0,0 +1,29 @@
1
+ loopSum(limit: Int) -> Int =
2
+ total = 0
3
+ for i in 0..limit
4
+ if i == 3
5
+ continue
6
+ if i == 8
7
+ break
8
+ total = total + i
9
+ return total
10
+
11
+ classify(n: Int) -> Str =
12
+ if n < 0
13
+ return "negative"
14
+ else if n == 0
15
+ return "zero"
16
+ else
17
+ return "positive"
18
+
19
+ countdown(start: Int) -> Int =
20
+ i = start
21
+ while i > 0
22
+ i = i - 1
23
+ return i
24
+
25
+ placeholder() =
26
+ todo
27
+
28
+ checkPositive(n: Int) =
29
+ assert n > 0
examples/functions.plum ADDED
@@ -0,0 +1,17 @@
1
+ addInts(a: Int, b: Int) -> Int =
2
+ a + b
3
+
4
+ greet() =
5
+ todo
6
+
7
+ withDefault(a: Int, step: Int = 1) -> Int =
8
+ a + step
9
+
10
+ sumAll(nums: ...Int) -> Int =
11
+ todo
12
+
13
+ wrap(value: a) -> Bool =
14
+ True
15
+
16
+ pair(first: a, second: b) -> Bool =
17
+ True
examples/match.plum ADDED
@@ -0,0 +1,45 @@
1
+ enum Color =
2
+ | Red
3
+ | Green
4
+ | Blue
5
+
6
+ enum Option =
7
+ | Some(Int)
8
+ | None
9
+
10
+ describeNumber(n: Int) -> Str =
11
+ match n
12
+ 0 =>
13
+ "zero"
14
+ 1 =>
15
+ "one"
16
+ _ =>
17
+ "many"
18
+
19
+ describeBool(b: Bool) -> Int =
20
+ match b
21
+ True =>
22
+ 1
23
+ False =>
24
+ 0
25
+
26
+ bindExample(n: Int) -> Int =
27
+ match n
28
+ x =>
29
+ x
30
+
31
+ describeColor(c: Color) -> Str =
32
+ match c
33
+ Red =>
34
+ "red"
35
+ Green =>
36
+ "green"
37
+ Blue =>
38
+ "blue"
39
+
40
+ describeOption(opt: Option) -> Int =
41
+ match opt
42
+ Some(v) =>
43
+ v
44
+ None =>
45
+ 0
examples/methods.plum ADDED
@@ -0,0 +1,26 @@
1
+ type Cat =
2
+ name: Str
3
+ age: Int
4
+
5
+ getAge<Cat>() -> Int =
6
+ self.age
7
+
8
+ birthday<Cat>() -> Int =
9
+ self.age + 1
10
+
11
+ type Wrapper =
12
+ inner: Cat
13
+ tag: Int
14
+
15
+ innerAge<Wrapper>() -> Int =
16
+ self.inner.age
17
+
18
+ makeCat() -> Cat =
19
+ Cat(name: "Whiskers", age: 3)
20
+
21
+ main() -> Int =
22
+ c = makeCat()
23
+ a = c.getAge()
24
+ b = c.birthday()
25
+ w = Wrapper(inner: c, tag: 1)
26
+ a + b + w.innerAge()
examples/strings.plum ADDED
@@ -0,0 +1,11 @@
1
+ greet(name: Str) -> Str =
2
+ "Hello, {name}!"
3
+
4
+ report(count: Int, total: Int) -> Str =
5
+ "{count} of {total} complete"
6
+
7
+ empty() -> Str =
8
+ ""
9
+
10
+ escaped() -> Str =
11
+ "line one\nline two\ttabbed \"quoted\""
examples/types.plum ADDED
@@ -0,0 +1,25 @@
1
+ type Point =
2
+ x: Int
3
+ y: Int
4
+
5
+ type Named(Stringable) =
6
+ name: Str
7
+
8
+ type Box(a) =
9
+ value: a
10
+
11
+ trait Shape =
12
+ area() -> Float
13
+ perimeter() -> Float
14
+
15
+ trait Comparable(a: Ord) =
16
+ compareTo(other: a) -> Int
17
+
18
+ enum Color =
19
+ | Red
20
+ | Green
21
+ | Blue
22
+
23
+ enum Option =
24
+ | Some(Int)
25
+ | None
plum-checker/tests/examples_test.rs ADDED
@@ -0,0 +1,48 @@
1
+ use plum_checker::check_source;
2
+ use plum_core::AstParser;
3
+
4
+ fn examples_dir() -> std::path::PathBuf {
5
+ std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples")
6
+ }
7
+
8
+ fn example_files() -> Vec<std::path::PathBuf> {
9
+ let mut files: Vec<_> = std::fs::read_dir(examples_dir())
10
+ .expect("examples/ directory should exist")
11
+ .filter_map(|e| e.ok())
12
+ .map(|e| e.path())
13
+ .filter(|p| p.extension().and_then(|e| e.to_str()) == Some("plum"))
14
+ .collect();
15
+ files.sort();
16
+ assert!(!files.is_empty(), "examples/ should contain at least one .plum file");
17
+ files
18
+ }
19
+
20
+ /// Every example must parse with zero ERROR/MISSING nodes and pass `check_source`.
21
+ /// These files exist specifically to prove each piece of the currently-supported
22
+ /// grammar surface actually works end to end, not just in isolated unit tests.
23
+ #[test]
24
+ fn every_example_parses_and_type_checks() {
25
+ let mut parser = tree_sitter::Parser::new();
26
+ parser.set_language(&tree_sitter_plum::LANGUAGE.into()).unwrap();
27
+
28
+ for path in example_files() {
29
+ let src = std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("failed to read {}: {}", path.display(), e));
30
+ let tree = parser.parse(&src, None).unwrap_or_else(|| panic!("failed to parse {}", path.display()));
31
+ assert!(
32
+ !tree.root_node().has_error(),
33
+ "{} has a parse error:\n{}",
34
+ path.display(),
35
+ tree.root_node().to_sexp()
36
+ );
37
+
38
+ let ap = AstParser::new(&src);
39
+ let source = ap.parse_source(tree.root_node());
40
+ let result = check_source(&source);
41
+ assert!(
42
+ result.is_ok(),
43
+ "{} failed type checking: {:?}",
44
+ path.display(),
45
+ result.err().map(|errs| errs.into_iter().map(|e| e.message).collect::<Vec<_>>())
46
+ );
47
+ }
48
+ }
plum-wasm-codegen/tests/examples_test.rs ADDED
@@ -0,0 +1,85 @@
1
+ use plum_wasm_codegen::compile_source;
2
+ use plum_core::AstParser;
3
+
4
+ fn examples_dir() -> std::path::PathBuf {
5
+ std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples")
6
+ }
7
+
8
+ fn parse_file(name: &str) -> plum_core::ast::Source {
9
+ let path = examples_dir().join(name);
10
+ let src = std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("failed to read {}: {}", path.display(), e));
11
+ let mut parser = tree_sitter::Parser::new();
12
+ parser.set_language(&tree_sitter_plum::LANGUAGE.into()).unwrap();
13
+ let tree = parser.parse(&src, None).unwrap_or_else(|| panic!("failed to parse {}", path.display()));
14
+ assert!(!tree.root_node().has_error(), "{} has a parse error", path.display());
15
+ let ap = AstParser::new(&src);
16
+ ap.parse_source(tree.root_node())
17
+ }
18
+
19
+ fn assert_compiles(name: &str) -> Vec<u8> {
20
+ let source = parse_file(name);
21
+ let bytes = compile_source(&source).unwrap_or_else(|e| panic!("{} failed to compile: {}", name, e));
22
+ let result = wasmparser::validate(&bytes);
23
+ assert!(result.is_ok(), "{} produced invalid wasm: {:?}", name, result.err());
24
+ bytes
25
+ }
26
+
27
+ /// Examples that stick to currently-supported codegen features (primitives, control
28
+ /// flow, classes/methods) must actually compile to valid wasm — not just parse and
29
+ /// type-check.
30
+ #[test]
31
+ fn basics_compiles() {
32
+ assert_compiles("basics.plum");
33
+ }
34
+
35
+ #[test]
36
+ fn control_flow_compiles() {
37
+ assert_compiles("control_flow.plum");
38
+ }
39
+
40
+ #[test]
41
+ fn functions_compiles() {
42
+ assert_compiles("functions.plum");
43
+ }
44
+
45
+ #[test]
46
+ fn types_compiles() {
47
+ // Only class/trait/enum declarations, no function bodies to lower — should still
48
+ // produce a valid (if unexciting) module.
49
+ assert_compiles("types.plum");
50
+ }
51
+
52
+ #[test]
53
+ fn methods_compiles_and_runs_correctly() {
54
+ let bytes = assert_compiles("methods.plum");
55
+
56
+ let engine = wasmtime::Engine::default();
57
+ let module = wasmtime::Module::new(&engine, &bytes).expect("module should be loadable");
58
+ let mut store = wasmtime::Store::new(&engine, ());
59
+ let instance = wasmtime::Instance::new(&mut store, &module, &[]).expect("module should instantiate");
60
+ let main = instance
61
+ .get_typed_func::<(), i64>(&mut store, "main")
62
+ .expect("main should have signature () -> i64");
63
+ let result = main.call(&mut store, ()).expect("main should not trap");
64
+ // makeCat() -> Cat(age: 3); a = getAge() = 3; b = birthday() = 4;
65
+ // w.innerAge() = inner.age = 3 => 3 + 4 + 3 = 10
66
+ assert_eq!(result, 10);
67
+ }
68
+
69
+ /// match.plum and strings.plum intentionally exercise syntax beyond what codegen
70
+ /// currently lowers (non-Bool enum-tag/constructor match patterns, string
71
+ /// interpolation) — they must fail loudly with a clear message, not silently
72
+ /// produce wrong wasm.
73
+ #[test]
74
+ fn match_example_reports_clear_unsupported_pattern_errors() {
75
+ let source = parse_file("match.plum");
76
+ let err = compile_source(&source).expect_err("non-Bool enum-tag patterns are not yet supported");
77
+ assert!(err.contains("enum variant pattern"), "got: {}", err);
78
+ }
79
+
80
+ #[test]
81
+ fn strings_example_reports_clear_interpolation_error() {
82
+ let source = parse_file("strings.plum");
83
+ let err = compile_source(&source).expect_err("string interpolation is not yet supported");
84
+ assert!(err.contains("interpolation"), "got: {}", err);
85
+ }