plum

#treesitter#compiler#wasm

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

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


plum-examples/match.plum
b7071c9 1
import std/Option
73b5e55 2
import std/Bool
ca5fd6f 3
import std/Number
29313cb 4
import std/Str
73e4ff1 5
0e1801a 6
enum Color =
0e1801a 7
  | Red
0e1801a 8
  | Green
0e1801a 9
  | Blue
0e1801a 10
0fe3528 11
fun describeNumber(n: Int) -> Str =
0e1801a 12
  match n
3a2119e 13
    0 => "zero"
3a2119e 14
    1 => "one"
3a2119e 15
    _ => "many"
0e1801a 16
0fe3528 17
fun describeBool(b: Bool) -> Int =
0e1801a 18
  match b
3a2119e 19
    True => 1
3a2119e 20
    False => 0
0e1801a 21
0fe3528 22
fun bindExample(n: Int) -> Int =
0e1801a 23
  match n
3a2119e 24
    x => x
0e1801a 25
0fe3528 26
fun describeColor(c: Color) -> Str =
0e1801a 27
  match c
3a2119e 28
    Red => "red"
3a2119e 29
    Green => "green"
3a2119e 30
    Blue => "blue"
0e1801a 31
73e4ff1 32
# A dedicated, non-generic "maybe an Int" — as opposed to the real, generic
73e4ff1 33
# `Option[T]` (`import std/option` above, used elsewhere in this file):
73e4ff1 34
# constructing a bare payload-free variant (`Absent` here, `None` for a real
73e4ff1 35
# generic enum) outside of a `match` pattern can't be disambiguated between
73e4ff1 36
# multiple concrete instantiations of ITS enum from that expression alone
73e4ff1 37
# (see the README's Generics section) — and since this file's forced-in
73e4ff1 38
# stdlib prelude (`plum-core::loader::loadAndMerge`) uses `Option` at several
73e4ff1 39
# OTHER concrete types internally, a bare `None` here genuinely IS
73e4ff1 40
# ambiguous. `IntOpt` sidesteps that entirely by only ever having ONE
73e4ff1 41
# possible instantiation to begin with.
73e4ff1 42
enum IntOpt =
287b97c 43
  | Present(Int)
73e4ff1 44
  | Absent
73e4ff1 45
73e4ff1 46
fun describeOption(opt: IntOpt) -> Int =
0e1801a 47
  match opt
73e4ff1 48
    Present(v) => v
73e4ff1 49
    Absent => 0
fe36a46 50
0fe3528 51
fun main() -> Int =
73e4ff1 52
  describeOption(Present(5))
a271f34 53
a43f3af 54
# ---- pattern matching regression tests ----
a43f3af 55
a43f3af 56
enum Nested =
287b97c 57
  | Wrap(IntOpt)
a43f3af 58
  | Empty
a43f3af 59
a43f3af 60
fun unwrapNested(n: Nested) -> Int =
a43f3af 61
  match n
73e4ff1 62
    Wrap(Present(v)) => v
73e4ff1 63
    Wrap(Absent) => -1
a9a0147 64
    Empty => 0
a43f3af 65
287b97c 66
enum GenericOption[T] =
287b97c 67
  | GSome(T)
a43f3af 68
  | GNone
a43f3af 69
287b97c 70
enum GenericBox[T] =
287b97c 71
  | GFull(T)
a43f3af 72
  | GEmpty
a43f3af 73
a43f3af 74
fun unwrapGenericBox(b: GenericBox) -> Int =
a43f3af 75
  match b
a43f3af 76
    GFull(GSome(v)) => v
a43f3af 77
    GFull(GNone) => -1
a43f3af 78
    GEmpty => 0
a43f3af 79
a43f3af 80
enum RecOption =
287b97c 81
  | RSome(RecOption)
a43f3af 82
  | RNone
a43f3af 83
a43f3af 84
fun unwrapTwice(o: RecOption) -> Int =
a43f3af 85
  match o
a43f3af 86
    RSome(RSome(RNone)) => 1
a43f3af 87
    RSome(RNone) => 2
a43f3af 88
    RNone => 3
a43f3af 89
    _ => 0
a43f3af 90
a43f3af 91
fun classifyForWildcardTest(a: Int) -> Int =
a43f3af 92
  match a
a9a0147 93
    1 => 100
a9a0147 94
    2 => 200
a9a0147 95
    _ => 0
a43f3af 96
a43f3af 97
fun colorCode(c: Color) -> Int =
a43f3af 98
  match c
a9a0147 99
    Red => 1
a9a0147 100
    Green => 2
a9a0147 101
    Blue => 3
a43f3af 102
a43f3af 103
fun isSome(o: Option) -> Int =
a43f3af 104
  match o
a9a0147 105
    Some(_) => 1
a9a0147 106
    None => 0
a43f3af 107
a43f3af 108
# Mirrors libs/std/bool.plum's `and`/`or`: `match a, b` against two Bool
a43f3af 109
# subjects, each case naming a tag pattern per position.
a43f3af 110
fun and(a: Bool, b: Bool) -> Bool =
a43f3af 111
  match a, b
a43f3af 112
    True, True => True
a43f3af 113
    True, False => False
a43f3af 114
    False, True => False
a43f3af 115
    False, False => False
a43f3af 116
a43f3af 117
fun andOrCheck() -> Int =
a43f3af 118
  x := and(True, True)
a43f3af 119
  y := and(True, False)
a43f3af 120
  match x, y
a43f3af 121
    True, False => 1
a43f3af 122
    _, _ => 0
a43f3af 123
a43f3af 124
fun classifyTwoInts(a: Int, b: Int) -> Int =
a43f3af 125
  match a, b
a43f3af 126
    1, 1 => 100
a43f3af 127
    1, 2 => 200
a43f3af 128
    _, _ => 0
a43f3af 129
a43f3af 130
fun combine(a: Int, b: Int) -> Int =
a43f3af 131
  match a, b
a43f3af 132
    0, y => y
a43f3af 133
    x, 0 => x
a43f3af 134
    x, y => x + y
a43f3af 135
a43f3af 136
fun bothOptions(a: Option, b: Option) -> Int =
a43f3af 137
  match a, b
a43f3af 138
    Some(x), Some(y) => x + y
a43f3af 139
    _, _ => 0
a43f3af 140
5f2f962 141
enum Point =
5f2f962 142
  | Point(x: Int, y: Int)
a577529 143
3a2119e 144
  fun sum(self) -> Int =
3a2119e 145
    match self
3a2119e 146
      Point(x, y) => x + y
a577529 147
3a2119e 148
  fun classify(self) -> Str =
3a2119e 149
    match self
3a2119e 150
      Point(0, 0) => "origin"
3a2119e 151
      Point(x, 0) => "on x axis"
3a2119e 152
      Point(_, _) => "elsewhere"
a577529 153
a577529 154
enum Shape =
287b97c 155
  | Circle(Point)
287b97c 156
  | Square(Point)
a577529 157
3a2119e 158
  fun measure(self) -> Int =
3a2119e 159
    match self
3a2119e 160
      Circle(Point(x, y)) => x + y
3a2119e 161
      Square(Point(x, y)) => x * y
a577529 162
287b97c 163
# Named payload fields (`Ring(radius: Int)`) alongside the
287b97c 164
# unnamed-positional-payload form (`Circle(Point)` above) — lets a
3a2119e 165
# multi-field variant like `Rect` name each field instead of leaving them as
3a2119e 166
# anonymous positional types. Pattern matching is unaffected either way
3a2119e 167
# (still positional, by declaration order — `Rect(w, h)`, not `Rect(w:, h:)`).
3a2119e 168
enum Figure =
3a2119e 169
  | Ring(radius: Int)
3a2119e 170
  | Rect(w: Int, h: Int)
3a2119e 171
3a2119e 172
  fun area(self) -> Int =
3a2119e 173
    match self
3a2119e 174
      Ring(radius) => radius * radius
3a2119e 175
      Rect(w, h) => w * h
3a2119e 176
8de13fd 177
test "match example computes correctly"
8de13fd 178
  assert main() == 5
8de13fd 179
8de13fd 180
test "nested constructor pattern matches and binds runs correctly"
73e4ff1 181
  assert unwrapNested(Wrap(Present(5))) == 5
8de13fd 182
8de13fd 183
test "nested constructor pattern mismatch falls through to next case runs correctly"
73e4ff1 184
  assert unwrapNested(Wrap(Absent)) == -1
8de13fd 185
8de13fd 186
test "nested constructor pattern against a specialized generic enum runs correctly"
8de13fd 187
  assert unwrapGenericBox(GFull(GSome(7))) == 7
8de13fd 188
8de13fd 189
test "doubly nested constructor pattern runs correctly"
8de13fd 190
  assert unwrapTwice(RSome(RSome(RNone))) == 1
8de13fd 191
8de13fd 192
test "match int and wildcard run correctly"
8de13fd 193
  assert classifyForWildcardTest(2) == 200
8de13fd 194
8de13fd 195
test "match bool variant pattern runs correctly"
8de13fd 196
  assert describeBool(False) == 0
8de13fd 197
8de13fd 198
test "non bool bare tag pattern runs correctly"
8de13fd 199
  assert colorCode(Green) == 2
8de13fd 200
8de13fd 201
test "constructor pattern wildcard field runs correctly"
8de13fd 202
  assert isSome(Some(99)) == 1
8de13fd 203
8de13fd 204
test "constructor pattern does not misfire on payload free sibling"
73e4ff1 205
  assert describeOption(Absent) == 0
8de13fd 206
8de13fd 207
test "multi subject match with enum tags runs correctly"
8de13fd 208
  assert andOrCheck() == 1
8de13fd 209
8de13fd 210
test "multi subject match falls through to next case when only first position matches"
8de13fd 211
  # The first case's position-0 pattern (`1`) matches, but position-1 (`1`)
8de13fd 212
  # doesn't (b is 2) — codegen must fall through to the *next case* (trying
8de13fd 213
  # its own position 0 again), not just "move on" within the first case.
8de13fd 214
  assert classifyTwoInts(1, 2) == 200
8de13fd 215
8de13fd 216
test "multi subject match with binding and wildcard runs correctly"
8de13fd 217
  assert combine(3, 4) == 7
8de13fd 218
8de13fd 219
test "multi subject match with generic enum variant runs correctly"
8de13fd 220
  assert bothOptions(Some(3), Some(4)) == 7
8de13fd 221
8de13fd 222
test "match destructures a plain class the same way it destructures an enum variant"
8de13fd 223
  assert Point(x: 3, y: 4).sum() == 7
8de13fd 224
8de13fd 225
test "match on a plain class supports literal/wildcard sub-patterns and case fallthrough"
8de13fd 226
  assert Point(x: 0, y: 0).classify() == "origin"
8de13fd 227
  assert Point(x: 5, y: 0).classify() == "on x axis"
8de13fd 228
  assert Point(x: 5, y: 5).classify() == "elsewhere"
8de13fd 229
8de13fd 230
test "a plain class nested inside an enum variant pattern destructures correctly"
8de13fd 231
  assert Circle(Point(x: 3, y: 4)).measure() == 7
8de13fd 232
  assert Square(Point(x: 3, y: 4)).measure() == 12
8de13fd 233
3a2119e 234
test "named-payload enum variant constructs via named args and destructures by position"
a9a0147 235
  assert Ring(radius: 5).area() == 25
a9a0147 236
  assert Rect(w: 3, h: 4).area() == 12
3a2119e 237
3a2119e 238
test "named-payload enum variant still supports positional construction too"
a9a0147 239
  assert Ring(5).area() == 25
a9a0147 240
  assert Rect(3, 4).area() == 12