plum

#treesitter#compiler#wasm

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

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


be00b92 — Peter John 2026-11-01T17:31:34+05:30
T4: README
Files changed (1) hide show
  1. README.md +39 -28
README.md CHANGED
@@ -29,9 +29,7 @@ enum Shape = # a sum type; payload fields are name
29
29
  Circle(r) => r * r * 3.14
30
30
  Rect(w, h) => w * h
31
31
 
32
- value enum Vec2 = # small copy-semantics value type
32
+ struct Vec2(x: Float, y: Float) = # small copy-semantics value type
33
- | Vec2(x: Float, y: Float)
34
-
35
33
  ZERO = Vec2(x: 0.0, y: 0.0) # Vec2.ZERO
36
34
 
37
35
  fun add(self, o: Vec2) -> Vec2 = # `a + b` calls a.add(b)
@@ -77,7 +75,8 @@ test "glance"
77
75
  | a conditional expression | `if c then a else b` (there is no `?:` ternary) |
78
76
  | loop | `while c`, `for x := range src`, `for i := range n` |
79
77
  | a sum type / record | `enum Shape = \| Circle(r: Float) \| Rect(w: Float, h: Float)` |
80
- | a small value type | `value enum Vec2 = \| Vec2(x: Float, y: Float)` |
78
+ | a small value type | `struct Vec2(x: Float, y: Float)` |
79
+ | a record (reference type) | `record User(name: Str, age: Int)` |
81
80
  | a method / static / constant | `fun area(self)` / `fun origin()` / `NAME = expr` inside the type body |
82
81
  | generics | `fun id(x: T) -> T`, `enum Box[T]`, duck-typed, one copy per type |
83
82
  | operators on your types | define `add`, `sub`, `mul`, `div`, `neg`, `eq`, `lt`, `le`, `gt`, `ge` |
@@ -244,9 +243,7 @@ On `Int`, `Float`, `Byte` and `Str` operators are built in. On any other (declar
244
243
  Each operator needs its own method (no derivation from `lt`/`eq`). The right operand may have a different type: `v * 2.0` calls `mul(self, s: Float)`. Using an operator without the method is an error such as `Vec2 has no operator '+' (define add)`, with the instantiation chain when it happens inside a generic. `==`/`!=` on a type with no `eq` keep their built-in meaning (variant identity).
245
244
 
246
245
  ```plum
247
- enum Vec2 =
248
- | Vec2(x: Float, y: Float)
246
+ struct Vec2(x: Float, y: Float) =
249
-
250
247
  fun add(self, o: Vec2) -> Vec2 =
251
248
  Vec2(x: self.x + o.x, y: self.y + o.y)
252
249
 
@@ -340,7 +337,7 @@ match job
340
337
  Details:
341
338
 
342
339
  - The bindings exist only in that branch's block (and in the `, cond`s after the pattern); using them after the `if`, or in a later `else` branch, is an error.
343
- - A pattern that cannot fail on its type (`Vec2(x, y) := v` on a value enum) is allowed and always takes the branch.
340
+ - A pattern that cannot fail on its type (`Vec2(x, y) := v` on a struct) is allowed and always takes the branch.
344
341
  - `else if` chains can mix plain conditions, optional bindings (`if x := opt`, see [Optionals](#optionals)) and patterns. A plain lowercase name over a `T?` is the optional binding; any other pattern is a pattern test.
345
342
  - It works on error values too: inside a `catch e` block, `if TooBig := e` tests the error name.
346
343
 
@@ -367,16 +364,20 @@ fun sumAll(nums: ...Int) -> Int = # variadic param
367
364
 
368
365
  A function without a return type returns `Unit`; the last expression of a body is its value. A call can name its arguments only for constructors (`Cat(name: "Whiskers", age: 3)`); functions take positional arguments.
369
366
 
370
- ## Types: enums, records and value enums
367
+ ## Types: struct, record and enum
371
368
 
372
- `enum` is the one declaration form for data. A record type is just a single-variant enum whose one variant shares the enum's own name:
369
+ `enum` is a sum type: a value that is one of several variants. `struct` and `record` are product types: one variant named like the type, with named fields. A `struct` is a value type and a `record` is a reference type.
373
370
 
374
371
  ```plum
375
- enum Point =
372
+ struct Vec2(x: Float, y: Float) =
376
- | Point(x: Int, y: Int)
373
+ ZERO = Vec2(x: 0.0, y: 0.0)
377
374
 
378
- enum Named =
375
+ fun add(self, o: Vec2) -> Vec2 =
376
+ Vec2(x: self.x + o.x, y: self.y + o.y)
377
+
379
- | Named(name: Str)
378
+ record User(name: Str, age: Int)
379
+
380
+ record Pair[K, V](key: K, val: V)
380
381
 
381
382
  enum Color =
382
383
  | Red
@@ -396,7 +397,20 @@ enum Step(n: Int) = # a shared field on every variant ...
396
397
  | ReadMax(20)
397
398
  ```
398
399
 
399
- The other declaration forms are `value enum` (below), `error Name = A | B` ([Error handling](#error-handling)) and `primitive Name =` ([Constants, statics and `primitive`](#constants-statics-and-primitive)).
400
+ The header ends with `=` only when an indented body follows: `struct Vec2(x: Float, y: Float)` with no body has no `=`.
401
+
402
+ A `struct` is a value type: copied on assignment and when passed, with fields only `Int`, `Float`, `Bool` or other structs. A `record` is a reference type: a heap object shared on assignment, with any field types, and it may be generic.
403
+
404
+ ```plum
405
+ a := Vec2(x: 1.0, y: 2.0)
406
+ b := a
407
+ b.x = 9.0 # a.x is still 1.0: a struct is copied
408
+ u := User(name: "ann", age: 3)
409
+ u2 := u
410
+ u2.age = 4 # u.age is now 4: a record is shared
411
+ ```
412
+
413
+ The other declaration forms are `struct` and `record` (below), `error Name = A | B` ([Error handling](#error-handling)) and `primitive Name =` ([Constants, statics and `primitive`](#constants-statics-and-primitive)).
400
414
 
401
415
  ### Methods, `self` and field access
402
416
 
@@ -476,22 +490,21 @@ enum Shape =
476
490
 
477
491
  See [`plum-examples/shapes.plum`](plum-examples/shapes.plum) and [`plum-examples/types.plum`](plum-examples/types.plum).
478
492
 
479
- ### `value enum`
493
+ ### `struct` and `record`
480
494
 
481
- `value enum` declares a small, copy-semantics value type (a vector, a color, a rectangle) that is flattened into wasm locals instead of being heap-allocated:
495
+ `struct` declares a small, copy-semantics value type (a vector, a color, a rectangle) that is flattened into wasm locals instead of being heap-allocated. `record` declares a reference type. Both have one variant named like the type:
482
496
 
483
497
  ```plum
484
- value enum Vec2 =
485
- | Vec2(x: Float, y: Float)
498
+ struct Vec2(x: Float, y: Float) =
486
-
487
499
  fun add(self, o: Vec2) -> Vec2 =
488
500
  Vec2(x: self.x + o.x, y: self.y + o.y)
489
501
 
490
- value enum Rect =
491
- | Rect(pos: Vec2, size: Vec2) # a field may be another value enum
502
+ struct Rect(pos: Vec2, size: Vec2) # a field may be another struct
503
+
504
+ record User(name: Str, age: Int)
492
505
  ```
493
506
 
494
- It has exactly one constructor, named like the type; no generics; fields are only `Int`, `Float`, `Bool` or other value enums, and a value enum can't contain itself. Values are copied on assignment and when passed, and are boxed only inside generic containers (`List[Vec2]`, `Vec2?`). Methods and `match` work as for any enum. See [`plum-examples/values.plum`](plum-examples/values.plum).
507
+ A `struct` has no generics; its fields are only `Int`, `Float`, `Bool` or other structs, and a struct can't contain itself. Values are copied on assignment and when passed, and are boxed only inside generic containers (`List[Vec2]`, `Vec2?`). A `record` may have any field types and may be generic (`record Pair[K, V](key: K, val: V)`). Methods and `match` work as for any enum. See [`plum-examples/values.plum`](plum-examples/values.plum).
495
508
 
496
509
  ## Generics
497
510
 
@@ -545,7 +558,7 @@ sumWith(List(1.5, 2.5), floatNum) # 4.0
545
558
 
546
559
  ## Constants, statics and `primitive`
547
560
 
548
- There are no top-level constants and no free-standing statics. A constant lives inside a type's body (`enum`, `value enum`) or a `primitive`; `NAME = expr` declares an associated constant, and a `fun` without `self` is a static function. Both are reached through the type, `Type.NAME` and `Type.f(args)`, from anywhere, and by their bare name inside the same body. A constant is evaluated lazily, once (a heap value such as a `List` is cached and shared), and names are unique per type, so `Alpha.X` and `Beta.X` coexist.
561
+ There are no top-level constants and no free-standing statics. A constant lives inside a type's body (`enum`, `struct`, `record`) or a `primitive`; `NAME = expr` declares an associated constant, and a `fun` without `self` is a static function. Both are reached through the type, `Type.NAME` and `Type.f(args)`, from anywhere, and by their bare name inside the same body. A constant is evaluated lazily, once (a heap value such as a `List` is cached and shared), and names are unique per type, so `Alpha.X` and `Beta.X` coexist.
549
562
 
550
563
  ```plum
551
564
  enum Level =
@@ -557,9 +570,7 @@ enum Level =
557
570
  fun cap(self, n: Int) -> Int =
558
571
  if n > LIMIT then LIMIT else n
559
572
 
560
- value enum Vec2 =
561
- | Vec2(x: Float, y: Float)
573
+ struct Vec2(x: Float, y: Float) =
562
-
563
574
  ZERO = Vec2(x: 0.0, y: 0.0) # Vec2.ZERO
564
575
 
565
576
  fun unit() -> Vec2 = # a static: Vec2.unit()
@@ -732,7 +743,7 @@ See [`plum-examples/seq.plum`](plum-examples/seq.plum) (one test per adapter and
732
743
 
733
744
  ## Optionals
734
745
 
735
- An optional is `T?`: a `T`, or `null`. It is its own type, not an error: it carries no error name, and `catch` and `try` do not apply to it. A plain `T` in a `T?` slot is the present value, and `null` is the absent value wherever a `T?` is expected (a call or constructor argument, a `return`, a tail, a list element, a parameter default such as `n: Int? = null`). `null` with nothing to say what it is an optional of is an error (`x := null`: `cannot infer the type of null`). For a reference type (a class, enum, `Str`, `List`, ...) a `T?` is stored as a nullable reference, so a present value costs no allocation; for `Int`, `Float`, `Bool` and value enums it is the value plus a present flag. A doubled `T??` is not allowed.
746
+ An optional is `T?`: a `T`, or `null`. It is its own type, not an error: it carries no error name, and `catch` and `try` do not apply to it. A plain `T` in a `T?` slot is the present value, and `null` is the absent value wherever a `T?` is expected (a call or constructor argument, a `return`, a tail, a list element, a parameter default such as `n: Int? = null`). `null` with nothing to say what it is an optional of is an error (`x := null`: `cannot infer the type of null`). For a reference type (a class, enum, `Str`, `List`, ...) a `T?` is stored as a nullable reference, so a present value costs no allocation; for `Int`, `Float`, `Bool` and structs it is the value plus a present flag. A doubled `T??` is not allowed.
736
747
 
737
748
  ```plum
738
749
  enum Person =