plum

#treesitter#compiler#wasm

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

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


9cc75c2 — Peter John 2026-11-01T22:42:05+05:30
docs: merge duplicated README passages
Files changed (1) hide show
  1. README.md +25 -40
README.md CHANGED
@@ -217,7 +217,7 @@ From tightest to loosest binding:
217
217
  | | `!` | boolean not |
218
218
  | | `&&` | |
219
219
  | | `\|\|` | |
220
- | | `?:` | optional default (right-associative: `a ?: b ?: c`) |
220
+ | | `?:` | optional default (right-associative, see [Optionals](#optionals)) |
221
221
  | | `if c then a else b` | inline if (the else branch extends as far right as possible) |
222
222
  | lowest | `catch` | `x catch default` (the default extends as far right as possible) |
223
223
 
@@ -393,34 +393,9 @@ A function without a return type returns `Unit`; the last expression of a body i
393
393
 
394
394
  ## Types: struct, record and enum
395
395
 
396
- `enum` is a union: a value that is one of several member types. `struct` and `record` are product types with named fields. A `struct` is a value type and a `record` is a reference type.
396
+ `enum` is a union: a value that is one of several member types. `struct` and `record` are product types with named fields. A `struct` is a value type and a `record` is a reference type (see [`struct` and `record`](#struct-and-record)).
397
397
 
398
- ```plum
399
- struct Vec2(x: Float, y: Float) =
400
- ZERO = Vec2(x: 0.0, y: 0.0)
401
-
402
- fun add(self, o: Vec2) -> Vec2 =
403
- Vec2(x: self.x + o.x, y: self.y + o.y)
404
-
405
- record User(name: Str, age: Int)
406
-
407
- record Pair[K, V](key: K, val: V)
408
- ```
409
-
410
- The header ends with `=` only when an indented body follows: `struct Vec2(x: Float, y: Float)` with no body has no `=`.
411
-
412
- 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.
413
-
414
- ```plum
415
- a := Vec2(x: 1.0, y: 2.0)
416
- b := a
417
- b.x = 9.0 # a.x is still 1.0: a struct is copied
418
- u := User(name: "ann", age: 3)
419
- u2 := u
420
- u2.age = 4 # u.age is now 4: a record is shared
421
- ```
422
-
423
- 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)).
398
+ The other declaration forms are `error Name = A | B` ([Error handling](#error-handling)) and `primitive Name =` ([Constants, statics and `primitive`](#constants-statics-and-primitive)).
424
399
 
425
400
  ### Enums
426
401
 
@@ -447,7 +422,7 @@ enum Json =
447
422
  | Map[Str, Json]
448
423
  ```
449
424
 
450
- `Red`, `Green`, `Blue` and `JsonNull` are tags: values with no payload. Every other member is an existing type, and the enum adds no new constructor for it.
425
+ Every member other than a tag is an existing type, and the enum adds no new constructor for it.
451
426
 
452
427
  A member converts to its enum wherever the enum is expected: an argument, a `return`, a tail expression, `push`, a `T?` or a `!T` slot. Nothing is written at the call site:
453
428
 
@@ -561,7 +536,7 @@ match book
561
536
  FantasyBook(title, _, _) => "Fantasy book \"{title}\" has no mythical creatures"
562
537
  ```
563
538
 
564
- Patterns can be literals, a bare identifier (binds), a bare capitalized name (a member or tag of an enum, or an error name when matching an error value, also written `error.Name`; compared), a constructor pattern (`Circle(r)`, binding its fields, possibly nested), a bare member name (`Float`, which narrows the subject to that type), or `_` (wildcard). `match a, b, ...` supports multiple subjects, and a case can carry a `when <expr>` guard. A `match` over an enum must be exhaustive; to test just one case, use [pattern `if`](#pattern-if) (`if Circle(r) := shape`). See [`plum-examples/match.plum`](plum-examples/match.plum), [`plum-examples/dop_visitor.plum`](plum-examples/dop_visitor.plum) and [`plum-examples/oop_visitor.plum`](plum-examples/oop_visitor.plum).
539
+ Patterns can be literals, a bare identifier (binds), a bare capitalized name (a member or tag of an enum, or an error name when matching an error value, also written `error.Name`; compared), a constructor pattern (`Circle(r)`, binding its fields, possibly nested), a bare member name (`Float`, which narrows the subject to that type, see [Enums](#enums)), or `_` (wildcard). `match a, b, ...` supports multiple subjects, and a case can carry a `when <expr>` guard. A `match` over an enum must be exhaustive. See [`plum-examples/match.plum`](plum-examples/match.plum), [`plum-examples/dop_visitor.plum`](plum-examples/dop_visitor.plum) and [`plum-examples/oop_visitor.plum`](plum-examples/oop_visitor.plum).
565
540
 
566
541
  Mixed kinds of value live in one enum with a `match self` method per operation:
567
542
 
@@ -596,6 +571,17 @@ struct Rect(pos: Vec2, size: Vec2) # a field may be another struct
596
571
  record User(name: Str, age: Int)
597
572
  ```
598
573
 
574
+ The header ends with `=` only when an indented body follows: `struct Vec2(x: Float, y: Float)` with no body has no `=`.
575
+
576
+ ```plum
577
+ a := Vec2(x: 1.0, y: 2.0)
578
+ b := a
579
+ b.x = 9.0 # a.x is still 1.0: a struct is copied
580
+ u := User(name: "ann", age: 3)
581
+ u2 := u
582
+ u2.age = 4 # u.age is now 4: a record is a heap object, shared on assignment
583
+ ```
584
+
599
585
  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).
600
586
 
601
587
  ## Generics
@@ -607,11 +593,11 @@ fun wrap(value: T) -> Bool =
607
593
  true
608
594
  ```
609
595
 
610
- A free function's generic parameters are its single-uppercase-letter type names (`T`, `U`); a type declares its own with `record Box[T]`. Generic types, methods, and free functions are duck-typed and compiled once per distinct instantiation. See [How generics compile](#how-generics-compile).
596
+ A free function's generic parameters are its single-uppercase-letter type names (`T`, `U`); a type declares its own with `record Box[T]`. Generic types, methods, and free functions are duck-typed: see [How generics compile](#how-generics-compile).
611
597
 
612
598
  ### How generics compile
613
599
 
614
- Generics are duck-typed and compiled per type, as in Zig. A generic body is type-checked once per concrete instantiation, and each instantiation gets its own compiled copy with unboxed values: `List[Int]` and `List[Str]` are different code, and a `List[Float]` stores raw `f64`s.
600
+ Generics are duck-typed and compiled per type, as in Zig. A generic body is type-checked once per concrete instantiation, and each instantiation gets its own compiled copy with unboxed values (an array of its own element type, no boxing at generic edges): `List[Int]` and `List[Str]` are different code, and a `List[Float]` stores raw `f64`s.
615
601
 
616
602
  - **No bounds.** `[T]` carries no constraint. `fun show(x: T) -> Str = x.toStr()` works for every type that has a `toStr` method and fails at the use site otherwise.
617
603
  - **Errors point at the use site.** A missing method is reported against the concrete type, with the chain of instantiations that led there:
@@ -623,7 +609,6 @@ in outer[Plain] (called at main.plum:9:3):
623
609
  ```
624
610
 
625
611
  - **Unreachable generics are not emitted.** A generic that no root (a non-generic fn or a test) reaches is neither checked per type nor compiled.
626
- - **Typed arrays are native.** Each instantiation stores its elements in an array of its own element type, with no boxing at generic edges.
627
612
  - **Bare generic types need type arguments.** `p: Parcel` is an error when `Parcel` is generic; write `p: Parcel[Int]` (or `Parcel[T]` inside a generic).
628
613
  - **Explicit behavior values.** When a generic needs behavior that is not a method on `T`, pass it explicitly, like a Zig comptime parameter or a Haskell dictionary:
629
614
 
@@ -733,7 +718,7 @@ for k := range scores.keys() # a fun* method of Map (also values(), entries
733
718
  printLn(k)
734
719
  ```
735
720
 
736
- `for i := range n` counts 0 to n-1, and the `Int` methods `a.until(b)` (a to b-1), `a.to(b)` (inclusive) and `a.downTo(b)` give other ranges (`for i := range 1.to(10)`).
721
+ Beyond `range n` (0 to n-1), the `Int` methods `a.until(b)` (a to b-1), `a.to(b)` (inclusive) and `a.downTo(b)` give other ranges (`for i := range 1.to(10)`).
737
722
 
738
723
  ### The iteration protocol
739
724
 
@@ -833,7 +818,7 @@ See [`plum-examples/seq.plum`](plum-examples/seq.plum) (one test per adapter and
833
818
 
834
819
  ## Optionals
835
820
 
836
- 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.
821
+ 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, `(T..., i32)` (the `i32` is 0 when present, and the code of `null` when absent). A doubled `T??` is not allowed.
837
822
 
838
823
  ```plum
839
824
  record Person(name: Str, age: Int)
@@ -851,7 +836,7 @@ fun find(n: Int) -> Person? =
851
836
  | `while x := opt` + block | loops while the expression is non-null, re-evaluated each iteration: `while job := queue.next()` |
852
837
  | `opt!` | the value, or crashes with `<callee> returned null at file:line:col` |
853
838
  | `opt == null`, `opt != null` | presence test (either order) |
854
- | `opt?.field`, `opt?.method()` | maps over a present value; null passes through. A field that is itself `U?` flattens |
839
+ | `opt?.field`, `opt?.method()` | maps over a present value; null passes through (see [Safe navigation](#safe-navigation-)) |
855
840
 
856
841
  `if` also takes patterns beyond a plain name (`if Circle(r) := shape`), and optional bindings work in `else if` chains: see [Pattern `if`](#pattern-if).
857
842
 
@@ -922,7 +907,7 @@ fun parse(s: Str) -> ParseError!Int =
922
907
  return n
923
908
  ```
924
909
 
925
- `error Name = A | B` is a top-level item; set names are unique, like type names. A set name is also a type: `e: ParseError` is a bare error value of that set, and `match e` over it is exhaustive. Pinned functions are checked against their body (`return error.Other` is an error, and so is propagating a call whose set is not a subset). A type's own generics are ordinary: `ParseError!Box[Int]` and `ParseError!Box [Int]` are the same type. Recursive functions, `extern fun`s and public std APIs pin their set (`fn 'f' is recursive: pin its error set (-> error{..}!T)`):
910
+ `error Name = A | B` is a top-level item; set names are unique, like type names. A set name is also a type: `e: ParseError` is a bare error value of that set, and `match e` over it is exhaustive. Pinned functions are checked against their body (`return error.Other` is an error, and so is propagating a call whose set is not a subset). A type's own generics are ordinary: `ParseError!Box[Int]` and `ParseError!Box [Int]` are the same type. Recursive functions and public std APIs pin their set (`fn 'f' is recursive: pin its error set (-> error{..}!T)`):
926
911
 
927
912
  ```plum
928
913
  primitive Int =
@@ -997,11 +982,11 @@ An error union can have an optional as its success value: `!T?` (`error{Bad}!Int
997
982
 
998
983
  ### Runtime representation
999
984
 
1000
- Each error name gets a global `i32` code, interned in sorted order from 1 (0 means success). An optional `T?` of a value type is `(T..., i32)` too (the `i32` is 0 when present, and the code of `null` when absent), and of a reference type a nullable reference. A `!T` result is a multi-value return `(T..., i32)`, so neither success nor failure allocates; stored in a heap slot (a field, a `List` element, a generic container) it is boxed into a `{value, code}` struct.
985
+ Each error name gets a global `i32` code, interned in sorted order from 1 (0 means success). An optional `T?` uses the same layout (see [Optionals](#optionals)). A `!T` result is a multi-value return `(T..., i32)`, so neither success nor failure allocates; stored in a heap slot (a field, a `List` element, a generic container) it is boxed into a `{value, code}` struct.
1001
986
 
1002
987
  ## Testing
1003
988
 
1004
- Zig-style `test` blocks are a native language feature, compiled and run only by `plum test`. Test blocks go at the end of the file; `plum test` takes files and directories (searched recursively):
989
+ Zig-style `test` blocks are a native language feature, compiled and run only by `plum test`. `plum test` takes files and directories (searched recursively):
1005
990
 
1006
991
  ```plum
1007
992
  fun add(a: Int, b: Int) -> Int =
@@ -1037,7 +1022,7 @@ hello, world!
1037
1022
  ## Standard library highlights
1038
1023
 
1039
1024
  - **`Bool`** — `true`/`false`, `if`, `&&`/`||`/`!` and `==` are built in (no import); `toStr` (also used by `"{b}"` interpolation) and `Bool.parse("true")` need `import std/Bool`
1040
- - **`Int`** / **`Float`** (`import std/Int`, `import std/Float`) — built-in numbers with their API in `primitive Int` / `primitive Float`: `abs`, `sign`, `min`, `max`, `sqrt`, `pow`, `floor`, `round`, `toStr`, ..., the constants `Int.MAX`, `Float.PI`, ..., and `Int.parse` / `Float.parse`
1025
+ - **`Int`** / **`Float`** (`import std/Int`, `import std/Float`) — built-in numbers with their API in `primitive Int` / `primitive Float`: `abs`, `sign`, `min`, `max`, `sqrt`, `pow`, `floor`, `round`, `toStr`, ... (constants and `parse`: see [Constants](#constants-statics-and-primitive))
1041
1026
  - **`Math`** (`import std/Math`) — a `primitive` namespace of floating-point functions: `Math.sqrt`, `Math.sin`, `Math.exp`, `Math.ln`, ...
1042
1027
  - **`Str`** (`import std/Str`) — a byte array with a codepoint-aware layer for Unicode correctness: `runeLength`, `runeAt`, `codePointAt`, `codePointToStr`
1043
1028
  - **`Byte`** / **`ByteSlice`** (`import std/Byte`, `import std/ByteSlice`) — a scalar unsigned 8-bit value and a fixed-length mutable byte slice