plum

#treesitter#compiler#wasm

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

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


a1704af — Peter John 2026-11-02T19:50:25+05:30
T8: docs and examples
README.md CHANGED
@@ -528,6 +528,43 @@ enum Step(n: Int) =
528
528
  | ReadMax(20)
529
529
  ```
530
530
 
531
+ ### Traits
532
+
533
+ A `trait` bundles fields and methods. A method without a body is required; one with a body is a default that a record may replace. A `record` embeds traits by listing them first in its field list, gains their fields (set in its constructor like its own) and default methods, and converts to each trait wherever one is expected:
534
+
535
+ ```plum
536
+ trait Entity(pos: Vec2, z: Int) =
537
+ fun update(self, dt: Float) # required: every embedding record defines it
538
+ fun draw(self)
539
+ fun moveBy(self, d: Vec2) = # default
540
+ self.pos = self.pos + d
541
+ fun drawTwice(self) =
542
+ self.draw() # runs the record's own draw
543
+ self.draw()
544
+
545
+ record House(Entity, Clickable, kind: Int) =
546
+ fun update(self, dt: Float) = ...
547
+ fun draw(self) = ...
548
+
549
+ es := List[Entity]()
550
+ es.push(House(pos: p, z: 0, hits: 0, kind: 1)) # House converts to Entity
551
+ for e := range es
552
+ e.update(dt) # runs House.update
553
+ e.pos = e.pos + drift # fields read and write through the trait
554
+ ```
555
+
556
+ Writes through a trait are seen by the record value (`e.pos = p` changes the `House`); a nested write such as `e.pos.x = 1` is rejected, assign the whole field. `match` narrows a trait value back to a record and always needs a `_` case; a record pattern binds all of the record's fields, trait fields first:
557
+
558
+ ```plum
559
+ match e
560
+ House(_, _, _, kind) => kind
561
+ _ => -1
562
+ ```
563
+
564
+ Everything is resolved statically from the whole program; there are no vtables. Compile errors: a missing required method, two traits (or a trait and the record) declaring the same field, two traits with the same default method that the record doesn't define itself, embedding something that isn't a trait, a trait in a `struct`, a method call the trait doesn't declare, and a `match` on a trait without `_`.
565
+
566
+ Not in v1: generic traits, traits embedding traits, `fun*` in traits, struct embedding, and `==` on trait values. See `plum-examples/traits.plum` and `plum-examples/trait_scene.plum`.
567
+
531
568
  ### Methods, `self` and field access
532
569
 
533
570
  A `fun` declared indented directly inside an `enum`, `struct` or `record` body whose first parameter is `self` is a method, called on a value (`c.getAge()`). One without `self` is a static function, called on the type (`Cat.named("Tom")`), and can't use `self`. Calling a static on a value, or a method on the type, is an error:
plum-examples/trait_scene.plum ADDED
@@ -0,0 +1,50 @@
1
+ # A tiny pure scene: a `Scene` holds `List[Entity]`; the engine side knows only
2
+ # the trait, the records fill it in. No game host needed.
3
+ module trait_scene
4
+ import std/Str
5
+ import std/Int
6
+ import std/List
7
+
8
+ trait Entity(x: Int, y: Int) =
9
+ fun update(self, dt: Int)
10
+ fun draw(self) -> Int
11
+ fun hit(self, px: Int, py: Int) -> Bool = px == self.x && py == self.y
12
+
13
+ record House(Entity, floors: Int) =
14
+ fun update(self, dt: Int) =
15
+ return
16
+ fun draw(self) -> Int = self.floors * 10
17
+
18
+ record Walker(Entity, speed: Int) =
19
+ fun update(self, dt: Int) =
20
+ self.x = self.x + self.speed * dt
21
+ fun draw(self) -> Int = self.x
22
+
23
+ record Scene(entities: List[Entity]) =
24
+ fun update(self, dt: Int) =
25
+ for e := range self.entities
26
+ e.update(dt)
27
+ fun drawAll(self) -> Int =
28
+ total := 0
29
+ for e := range self.entities
30
+ total = total + e.draw()
31
+ total
32
+
33
+ fun makeScene() -> Scene =
34
+ es: List[Entity] := List[Entity]()
35
+ es.push(House(x: 0, y: 0, floors: 2))
36
+ es.push(Walker(x: 3, y: 1, speed: 2))
37
+ Scene(entities: es)
38
+
39
+ test "scene_updates_and_draws"
40
+ s := makeScene()
41
+ assert s.drawAll() == 23
42
+ s.update(5)
43
+ assert s.drawAll() == 33
44
+
45
+ test "scene_hit_default"
46
+ s := makeScene()
47
+ assert s.entities.at(1).hit(3, 1)
48
+ s.update(1)
49
+ assert s.entities.at(1).hit(5, 1)
50
+ assert !s.entities.at(0).hit(1, 1)
plum-examples/traits.plum ADDED
@@ -0,0 +1,80 @@
1
+ # Traits: `trait Entity(pos: Vec2, z: Int) = ...` bundles fields and methods. A
2
+ # `record` embeds traits by listing them first in its field list, gains their
3
+ # fields and default methods, and converts to each trait where one is expected.
4
+ # A method without a body is required; one with a body is a default.
5
+ module traits
6
+ import std/Str
7
+ import std/Int
8
+ import std/Float
9
+ import std/List
10
+
11
+ struct Vec2(x: Int, y: Int) =
12
+ fun add(self, o: Vec2) -> Vec2 = Vec2(x: self.x + o.x, y: self.y + o.y)
13
+
14
+ trait Entity(pos: Vec2, z: Int) =
15
+ fun update(self, dt: Int) -> Int
16
+ fun draw(self) -> Int
17
+ fun moveBy(self, d: Vec2) =
18
+ self.pos = self.pos + d
19
+ fun drawTwice(self) -> Int = self.draw() * 2
20
+
21
+ trait Clickable(hits: Int) =
22
+ fun onClick(self) -> Int
23
+
24
+ record House(Entity, Clickable, kind: Int) =
25
+ fun update(self, dt: Int) -> Int = self.kind * dt
26
+ fun draw(self) -> Int = 100 + self.pos.x
27
+ fun onClick(self) -> Int = 7
28
+
29
+ record Tree(Entity, age: Int) =
30
+ fun update(self, dt: Int) -> Int = self.age + dt
31
+ fun draw(self) -> Int = 200 + self.pos.x
32
+ fun drawTwice(self) -> Int = 1
33
+
34
+ fun house() -> House = House(pos: Vec2(x: 1, y: 2), z: 0, hits: 0, kind: 3)
35
+
36
+ fun tree() -> Tree = Tree(pos: Vec2(x: 5, y: 5), z: 1, age: 10)
37
+
38
+ fun clickIt(c: Clickable) -> Int = c.onClick() + c.hits
39
+
40
+ fun describe(e: Entity) -> Int =
41
+ match e
42
+ House(_, _, _, kind) => kind
43
+ _ => -1
44
+
45
+ test "fields_through_trait"
46
+ h := house()
47
+ e: Entity := h
48
+ assert e.z == 0
49
+ e.pos = Vec2(x: 9, y: 9)
50
+ assert h.pos.x == 9
51
+ e.z = 4
52
+ assert h.z == 4
53
+
54
+ test "list_of_entities_dispatch"
55
+ es: List[Entity] := List[Entity]()
56
+ es.push(house())
57
+ es.push(tree())
58
+ assert es.at(0).update(2) == 6
59
+ assert es.at(1).update(2) == 12
60
+ assert es.at(0).draw() == 101
61
+ assert es.at(1).draw() == 205
62
+
63
+ test "default_changes_pos"
64
+ h := house()
65
+ e: Entity := h
66
+ e.moveBy(Vec2(x: 10, y: 20))
67
+ assert h.pos.x == 11
68
+ assert e.pos.y == 22
69
+
70
+ test "default_calls_override"
71
+ assert house().drawTwice() == 202
72
+ e: Entity := tree()
73
+ assert e.drawTwice() == 1
74
+
75
+ test "match_with_wildcard"
76
+ assert describe(house()) == 3
77
+ assert describe(tree()) == -1
78
+
79
+ test "record_in_two_traits"
80
+ assert clickIt(house()) == 7