plum

#treesitter#compiler#wasm

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

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


fa4cd74 — Peter John 2026-11-03T18:28:51+05:30
forms: regression test for for-range over a type with fun* items
plum-core/tests/forms_list_range_test.rs ADDED
@@ -0,0 +1,53 @@
1
+ #![allow(non_snake_case)]
2
+ //! `for x := range xs` over a type declaring `fun* items(self)` lowers like `xs.items()`
3
+ //! (no per-element `at(i)`).
4
+ use plum_core::ast::*;
5
+ use plum_core::forms::{self, TyInfo};
6
+ use plum_core::AstParser;
7
+
8
+ const SRC: &str = "\
9
+ enum List[T] =
10
+ | Nil
11
+ | Cons(head: T, tail: List[T])
12
+
13
+ fun* items(self) *T =
14
+ cur := self
15
+ while true
16
+ if Cons(h, t) := cur
17
+ cur = t
18
+ yield h
19
+ else
20
+ break
21
+
22
+ fun main() =
23
+ for x := range xs
24
+ x
25
+ ";
26
+
27
+ fn lowered(withItems: bool) -> String {
28
+ let mut parser = tree_sitter::Parser::new();
29
+ parser.set_language(&tree_sitter_plum::LANGUAGE.into()).unwrap();
30
+ let tree = parser.parse(SRC, None).unwrap();
31
+ assert!(!tree.root_node().has_error(), "{}", tree.root_node().to_sexp());
32
+ let source = AstParser::new(SRC).parseSource(tree.root_node());
33
+ forms::begin(&source);
34
+ let main = source.items.iter().find_map(|i| match i {
35
+ Item::Fn(f) if f.name == "main" => Some(f.clone()),
36
+ _ => None,
37
+ }).unwrap();
38
+ let FnBody::Block(b) = &main.body else { panic!() };
39
+ let Stmt::ForLoop(fl) = &b.stmts[0] else { panic!("for") };
40
+ let typeOf = |_: &Expr| Some(TyInfo {
41
+ name: "List".into(),
42
+ iterable: true,
43
+ methods: if withItems { ["items".to_string()].into() } else { Default::default() },
44
+ });
45
+ format!("{:?}", forms::lowerLoop(fl, &typeOf))
46
+ }
47
+
48
+ #[test]
49
+ fn list_range_uses_items() {
50
+ let s = lowered(true);
51
+ assert!(s.contains("Cons"), "items body not inlined: {s}");
52
+ assert!(!s.contains("\"at\""), "{s}");
53
+ }
plum-game/web-test/fixtures/stress_pure_scene_inplace.plum DELETED
@@ -1,176 +0,0 @@
1
- # The stress scene of stress_scene.plum on the PURE scene graph (std/game/PureScene):
2
- # 5000 sprites in 50 slowly rotating groups, 500 rects and 200 circles (sprites of a
3
- # 1x1 white texture), 50 texts with a changing number, drawn through a camera. Upper-bound
4
- # probe: each frame updates the actors in place (no clone, no spread). Group and
5
- # sprite spin comes from `forever(rotateBy(...))` actions; drifting and counting
6
- # actors do theirs in `act`.
7
- module stress_pure_scene
8
- import std/Str
9
- import std/Int
10
- import std/Float
11
- import std/List
12
- import std/game/Vec2
13
- import std/game/Color
14
- import std/game/Affine
15
- import std/game/Game
16
- import std/game/Actions
17
- import std/game/PureScene
18
- import std/game/SceneTree
19
- import std/game/SceneInput
20
-
21
- primitive Counts =
22
- Sprites = 5000
23
- Rects = 500
24
- Circles = 200
25
- Texts = 50
26
- Groups = 50
27
- WorldW = 1600
28
- WorldH = 900
29
-
30
- record World(sheet: Int, stage: Stage)
31
-
32
- fun hue(i: Int) -> Color =
33
- Color(r: Float(i % 7) / 7.0, g: Float(i % 5) / 5.0, b: 0.4 + Float(i % 3) / 5.0, a: 0.8)
34
-
35
- fun quad(m: Affine, tint: Color, w: Float, h: Float, tex: Int, srcX: Float, srcY: Float, srcW: Float, srcH: Float) =
36
- sx := w * m.scaleX()
37
- sy := h * m.scaleY()
38
- c := m.apply(Vec2(x: w / 2.0, y: h / 2.0))
39
- drawTextureEx(tex, srcX, srcY, srcW, srcH, c.x - sx / 2.0, c.y - sy / 2.0, sx, sy, m.rotation(), false, tint)
40
-
41
- fun caption(m: Affine, tint: Color, s: Str, size: Float) =
42
- p := m.apply(Vec2(x: 0.0, y: 0.0))
43
- drawText(s, p.x, p.y, size * m.scaleY(), tint)
44
-
45
- # A square of the white texture that moves by (dx, dy) per frame and wraps.
46
- record Drifter(
47
- Actor(id, x, y, width, height, originX, originY, scaleX, scaleY, rotation, color, visible, touchable, actions),
48
- tex: Int, dx: Float, dy: Float) =
49
- override fun clone(self) -> Actor = Drifter(..self)
50
-
51
- override fun act(self, dt: Float) -> Actor =
52
- self.x = self.x + self.dx
53
- self.y = self.y + self.dy
54
- if self.x > Float(Counts.WorldW)
55
- self.x = self.x - Float(Counts.WorldW)
56
- if self.y > Float(Counts.WorldH)
57
- self.y = self.y - Float(Counts.WorldH)
58
- me: Actor := self
59
- me
60
-
61
- override fun draw(self, m: Affine, tint: Color) =
62
- quad(m, tint, self.width, self.height, self.tex, 0.0, 0.0, 0.0, 0.0)
63
-
64
- # Shows "n <count>" and counts up each frame.
65
- record CountLabel(
66
- Actor(id, x, y, width, height, originX, originY, scaleX, scaleY, rotation, color, visible, touchable, actions),
67
- n: Int, text: Str) =
68
- override fun clone(self) -> Actor = CountLabel(..self)
69
-
70
- override fun act(self, dt: Float) -> Actor =
71
- self.n = self.n + 1
72
- self.text = "n {self.n}"
73
- me: Actor := self
74
- me
75
-
76
- override fun draw(self, m: Affine, tint: Color) =
77
- caption(m, tint, self.text, 14.0)
78
-
79
- fun drifter(id: Int, tex: Int, c: Color, x: Float, y: Float, w: Float, h: Float, dx: Float, dy: Float) -> Actor =
80
- d := Drifter(
81
- id: id, x: x, y: y, width: w, height: h, originX: 0.0, originY: 0.0,
82
- scaleX: 1.0, scaleY: 1.0, rotation: 0.0, color: c, visible: true,
83
- touchable: Disabled, actions: List[Action](), tex: tex, dx: dx, dy: dy)
84
- me: Actor := d
85
- me
86
-
87
- fun countLabel(id: Int, n: Int, x: Float, y: Float) -> Actor =
88
- l := CountLabel(
89
- id: id, x: x, y: y, width: 60.0, height: 14.0, originX: 0.0, originY: 0.0,
90
- scaleX: 1.0, scaleY: 1.0, rotation: 0.0, color: Color.White, visible: true,
91
- touchable: Disabled, actions: List[Action](), n: n, text: "n {n}")
92
- me: Actor := l
93
- me
94
-
95
- fun spinning(rate: Float) -> List[Action] =
96
- acts := List[Action]()
97
- acts.push(Actions.forever(Actions.rotateBy(rate, 1.0)))
98
- acts
99
-
100
- fun dancer(id: Int, sheet: Int, j: Int, i: Int) -> Actor =
101
- s := PureScene.sprite(id, sheet, Float({j * j * 13 + j * 37} % 150), Float({j * j * 29 + j * 53} % 170), 16.0, 16.0)
102
- s.originX = 8.0
103
- s.originY = 8.0
104
- s.rotation = Float(i)
105
- s.srcX = Float(i % 4 * 16)
106
- s.srcW = 16.0
107
- s.srcH = 16.0
108
- s.actions = spinning(1.2)
109
- me: Actor := s
110
- me
111
-
112
- fun buildWorld(sheet: Int, white: Int) -> World =
113
- root := List[Actor]()
114
- per := Counts.Sprites / Counts.Groups
115
- for gi := range Counts.Groups
116
- kids := List[Actor]()
117
- for j := range per
118
- i := gi * per + j
119
- kids.push(dancer(1000 + i, sheet, j, i))
120
- g := PureScene.group(100 + gi, Float(gi % 10 * 160), Float(gi / 10 * 180), 150.0, 170.0, kids)
121
- g.originX = 75.0
122
- g.originY = 85.0
123
- g.actions = spinning(0.24)
124
- me: Actor := g
125
- root.push(me)
126
- for i := range Counts.Rects
127
- root.push(drifter(10000 + i, white, hue(i), Float(i * 61 % Counts.WorldW), Float(i * 29 % Counts.WorldH), 12.0, 8.0, 2.0, 0.0))
128
- for i := range Counts.Circles
129
- root.push(drifter(20000 + i, white, hue(i + 3), Float(i * 83 % Counts.WorldW), Float(i * 47 % Counts.WorldH), 12.0, 12.0, 3.0, 1.0))
130
- for i := range Counts.Texts
131
- root.push(countLabel(30000 + i, i, Float(i * 30 % Counts.WorldW), Float(i * 17 % Counts.WorldH)))
132
- top: Actor := PureScene.group(1, 0.0, 0.0, Float(Counts.WorldW), Float(Counts.WorldH), root)
133
- cam := Affine(a: 0.8, b: 0.0, c: 0.0, d: 0.8, e: 0.0, f: 0.0)
134
- World(sheet: sheet, stage: Stage(root: top, camera: cam, captured: null))
135
-
136
- fun whiteTexture() -> Int =
137
- t := textureCreate(1, 1)
138
- renderTarget(t)
139
- clear(Color.White)
140
- renderTarget(-1)
141
- t
142
-
143
- export fun init() -> World =
144
- buildWorld(textureLoad("sprites.png"), whiteTexture())
145
-
146
- # Updates the actor and its subtree by assigning fields on the existing objects: no
147
- # clone, no spread, no rebuilt child lists.
148
- fun updateInPlace(a: Actor, dt: Float) =
149
- _ := a.act(dt)
150
- r := Actions.stepAll(Pose(x: a.x, y: a.y, rotation: a.rotation, scaleX: a.scaleX, scaleY: a.scaleY), a.actions, dt)
151
- a.x = r.pose.x
152
- a.y = r.pose.y
153
- a.rotation = r.pose.rotation
154
- a.scaleX = r.pose.scaleX
155
- a.scaleY = r.pose.scaleY
156
- a.actions = r.kept
157
- if ks := a.kids()
158
- for i := range ks.length()
159
- updateInPlace(ks.at(i), dt)
160
-
161
- export fun update(w: World, dt: Float) =
162
- updateInPlace(w.stage.root, dt)
163
-
164
- export fun draw(w: World) =
165
- clear(Color.rgb(0.05, 0.06, 0.1))
166
- w.stage.draw()
167
-
168
- test "ten frames of update run"
169
- w := buildWorld(0, 0)
170
- for i := range 10
171
- update(w, 0.016)
172
- if k := w.stage.root.kids()
173
- assert k.length() == 50 + 500 + 200 + 50
174
- assert {k.at(50).x - 20.0}.abs() < 0.000001
175
- else
176
- assert false
plum-std/{List.plum → Slice.plum} RENAMED
File without changes