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