plum
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
|