plum

#treesitter#compiler#wasm

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

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


808db73 — Peter John 2026-11-02T19:49:16+05:30
T6: codegen and run tests
plum-wasm-codegen/tests/traits_e2e_test.rs ADDED
@@ -0,0 +1,205 @@
1
+ #![allow(non_snake_case)]
2
+ use plum_wasm_codegen::compileSource;
3
+
4
+ fn gcEngine() -> wasmtime::Engine {
5
+ let mut config = wasmtime::Config::new();
6
+ config.wasm_gc(true);
7
+ config.wasm_function_references(true);
8
+ wasmtime::Engine::new(&config).expect("engine with GC config should construct")
9
+ }
10
+
11
+ fn repoRoot() -> std::path::PathBuf {
12
+ std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("..")
13
+ }
14
+
15
+ fn load(name: &str, src: &str) -> Result<plum_core::ast::Source, String> {
16
+ let dir = std::env::temp_dir().join(format!("plum_traits_{}_{}", name, std::process::id()));
17
+ std::fs::create_dir_all(&dir).unwrap();
18
+ let path = dir.join("main.plum");
19
+ std::fs::write(&path, src).unwrap();
20
+ let r = plum_core::loadAndMerge(&path, &repoRoot());
21
+ let _ = std::fs::remove_dir_all(&dir);
22
+ r.map_err(|e| format!("{e}"))
23
+ }
24
+
25
+ /// Compiles `src` through the real pipeline and runs its `main() -> Int`.
26
+ fn runInt(name: &str, src: &str) -> i64 {
27
+ let source = load(name, src).expect("load");
28
+ let bytes = compileSource(&source).unwrap_or_else(|e| panic!("compile failed: {e}"));
29
+ wasmparser::validate(&bytes).expect("valid wasm");
30
+ let engine = gcEngine();
31
+ let module = wasmtime::Module::new(&engine, &bytes).unwrap();
32
+ let mut store = wasmtime::Store::new(&engine, ());
33
+ let mut linker = wasmtime::Linker::new(&engine);
34
+ linker
35
+ .define_unknown_imports_as_default_values(&mut store, &module)
36
+ .unwrap();
37
+ let instance = linker.instantiate(&mut store, &module).unwrap();
38
+ let main = instance
39
+ .get_typed_func::<(), i64>(&mut store, "main")
40
+ .unwrap();
41
+ main.call(&mut store, ()).unwrap()
42
+ }
43
+
44
+
45
+ const PRE: &str = "import std/Str\nimport std/Int\nimport std/Float\nimport std/List\n\n";
46
+
47
+ fn prog(body: &str) -> String {
48
+ format!("{PRE}{body}")
49
+ }
50
+
51
+ #[test]
52
+ fn opening_example_runs() {
53
+ let src = prog("\
54
+ struct Vec2(x: Int, y: Int) =
55
+ fun add(self, o: Vec2) -> Vec2 = Vec2(x: self.x + o.x, y: self.y + o.y)
56
+
57
+ trait Entity(pos: Vec2, z: Int) =
58
+ fun update(self, dt: Int) -> Int
59
+ fun draw(self) -> Int
60
+ fun moveBy(self, d: Vec2) =
61
+ self.pos = self.pos + d
62
+
63
+ trait Clickable(hits: Int) =
64
+ fun onClick(self) -> Int
65
+
66
+ record House(Entity, Clickable, kind: Int) =
67
+ fun update(self, dt: Int) -> Int = self.kind * dt
68
+ fun draw(self) -> Int = 100 + self.pos.x
69
+ fun onClick(self) -> Int = 7
70
+
71
+ record Tree(Entity, age: Int) =
72
+ fun update(self, dt: Int) -> Int = self.age + dt
73
+ fun draw(self) -> Int = 200 + self.pos.x
74
+
75
+ fun main() -> Int =
76
+ h := House(pos: Vec2(x: 1, y: 2), z: 0, hits: 0, kind: 3)
77
+ es: List[Entity] := List[Entity]()
78
+ es.push(h)
79
+ es.push(Tree(pos: Vec2(x: 5, y: 5), z: 1, age: 10))
80
+ total := 0
81
+ for e := range es
82
+ total = total * 1000 + e.update(2) * 100 + e.draw() % 100
83
+ es.at(0).moveBy(Vec2(x: 10, y: 20))
84
+ total + h.pos.x * 100000000 + es.at(0).pos.y * 1000000000 + h.draw()
85
+ ");
86
+ // update/draw: House 6*100+1=601, Tree 12*100+5=1205 -> 601*1000+1205 = 602205
87
+ // h.pos.x = 11 -> 1100000000; entity pos.y = 22 -> 22000000000; h.draw = 111
88
+ assert_eq!(runInt("opening", &src), 602205 + 1100000000 + 22000000000 + 111);
89
+ }
90
+
91
+ #[test]
92
+ fn default_calls_override() {
93
+ let src = prog("\
94
+ trait Shape(n: Int) =
95
+ fun draw(self) -> Int
96
+ fun twice(self) -> Int = self.draw() * 2
97
+
98
+ record Sq(Shape, s: Int) =
99
+ fun draw(self) -> Int = self.s
100
+
101
+ record Ci(Shape, r: Int) =
102
+ fun draw(self) -> Int = self.r + 1
103
+
104
+ fun main() -> Int =
105
+ xs: List[Shape] := List[Shape]()
106
+ xs.push(Sq(n: 0, s: 5))
107
+ xs.push(Ci(n: 0, r: 20))
108
+ xs.at(0).twice() * 1000 + xs.at(1).twice()
109
+ ");
110
+ assert_eq!(runInt("override", &src), 10000 + 42);
111
+ }
112
+
113
+ #[test]
114
+ fn three_level_actor_tree() {
115
+ let src = prog("\
116
+ trait Actor(children: List[Actor], v: Int) =
117
+ fun own(self) -> Int
118
+ fun total(self) -> Int =
119
+ sum := self.own()
120
+ for c := range self.children
121
+ sum = sum + c.total()
122
+ sum
123
+
124
+ record Root(Actor) =
125
+ fun own(self) -> Int = 1
126
+
127
+ record Mid(Actor, w: Int) =
128
+ fun own(self) -> Int = self.w
129
+
130
+ record Leaf(Actor) =
131
+ fun own(self) -> Int = self.v
132
+
133
+ fun main() -> Int =
134
+ l1 := Leaf(children: List[Actor](), v: 100)
135
+ l2 := Leaf(children: List[Actor](), v: 200)
136
+ mk: List[Actor] := List[Actor]()
137
+ mk.push(l1)
138
+ mk.push(l2)
139
+ m := Mid(children: mk, v: 0, w: 10)
140
+ rk: List[Actor] := List[Actor]()
141
+ rk.push(m)
142
+ rk.push(Leaf(children: List[Actor](), v: 1000))
143
+ r := Root(children: rk, v: 0)
144
+ r.total()
145
+ ");
146
+ assert_eq!(runInt("actors", &src), 1311);
147
+ }
148
+
149
+ #[test]
150
+ fn trait_without_embedders_compiles() {
151
+ let src = prog("\
152
+ trait Lonely(a: Int) =
153
+ fun f(self) -> Int
154
+
155
+ fun main() -> Int = 5
156
+ ");
157
+ assert_eq!(runInt("lonely", &src), 5);
158
+ }
159
+
160
+ #[test]
161
+ fn record_in_trait_and_union_enum() {
162
+ let src = prog("\
163
+ trait Named(id: Int) =
164
+ fun tag(self) -> Int
165
+
166
+ record Dog(Named, legs: Int) =
167
+ fun tag(self) -> Int = self.legs
168
+
169
+ record Fish(fins: Int) =
170
+ fun tag(self) -> Int = self.fins
171
+
172
+ enum Pet = Dog | Fish
173
+
174
+ fun viaTrait(n: Named) -> Int = n.tag() + n.id
175
+ fun viaUnion(p: Pet) -> Int = p.tag()
176
+
177
+ fun main() -> Int =
178
+ d := Dog(id: 3, legs: 4)
179
+ viaTrait(d) * 100 + viaUnion(d) * 10 + viaUnion(Fish(fins: 2))
180
+ ");
181
+ assert_eq!(runInt("both", &src), 700 + 40 + 2);
182
+ }
183
+
184
+ #[test]
185
+ fn match_trait_narrows() {
186
+ let src = prog("\
187
+ trait Item(id: Int) =
188
+ fun name(self) -> Int
189
+
190
+ record Sword(Item, dmg: Int) =
191
+ fun name(self) -> Int = 1
192
+
193
+ record Shield(Item, def: Int) =
194
+ fun name(self) -> Int = 2
195
+
196
+ fun score(i: Item) -> Int =
197
+ match i
198
+ Sword(id, dmg) => id * 100 + dmg
199
+ _ => 5
200
+
201
+ fun main() -> Int =
202
+ score(Sword(id: 4, dmg: 9)) * 10 + score(Shield(id: 1, def: 2))
203
+ ");
204
+ assert_eq!(runInt("narrow", &src), 4090 + 5);
205
+ }