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
import std/Option
import std/Bool
import std/Number
import std/Str

enum Color =
  | Red
  | Green
  | Blue

fun describeNumber(n: Int) -> Str =
  match n
    0 => "zero"
    1 => "one"
    _ => "many"

fun describeBool(b: Bool) -> Int =
  match b
    True => 1
    False => 0

fun bindExample(n: Int) -> Int =
  match n
    x => x

fun describeColor(c: Color) -> Str =
  match c
    Red => "red"
    Green => "green"
    Blue => "blue"

# A dedicated, non-generic "maybe an Int" — as opposed to the real, generic
# `Option[T]` (`import std/option` above, used elsewhere in this file):
# constructing a bare payload-free variant (`Absent` here, `None` for a real
# generic enum) outside of a `match` pattern can't be disambiguated between
# multiple concrete instantiations of ITS enum from that expression alone
# (see the README's Generics section) — and since this file's forced-in
# stdlib prelude (`plum-core::loader::loadAndMerge`) uses `Option` at several
# OTHER concrete types internally, a bare `None` here genuinely IS
# ambiguous. `IntOpt` sidesteps that entirely by only ever having ONE
# possible instantiation to begin with.
enum IntOpt =
  | Present(Int)
  | Absent

fun describeOption(opt: IntOpt) -> Int =
  match opt
    Present(v) => v
    Absent => 0

fun main() -> Int =
  describeOption(Present(5))

# ---- pattern matching regression tests ----

enum Nested =
  | Wrap(IntOpt)
  | Empty

fun unwrapNested(n: Nested) -> Int =
  match n
    Wrap(Present(v)) => v
    Wrap(Absent) => -1
    Empty => 0

enum GenericOption[T] =
  | GSome(T)
  | GNone

enum GenericBox[T] =
  | GFull(T)
  | GEmpty

fun unwrapGenericBox(b: GenericBox) -> Int =
  match b
    GFull(GSome(v)) => v
    GFull(GNone) => -1
    GEmpty => 0

enum RecOption =
  | RSome(RecOption)
  | RNone

fun unwrapTwice(o: RecOption) -> Int =
  match o
    RSome(RSome(RNone)) => 1
    RSome(RNone) => 2
    RNone => 3
    _ => 0

fun classifyForWildcardTest(a: Int) -> Int =
  match a
    1 => 100
    2 => 200
    _ => 0

fun colorCode(c: Color) -> Int =
  match c
    Red => 1
    Green => 2
    Blue => 3

fun isSome(o: Option) -> Int =
  match o
    Some(_) => 1
    None => 0

# Mirrors libs/std/bool.plum's `and`/`or`: `match a, b` against two Bool
# subjects, each case naming a tag pattern per position.
fun and(a: Bool, b: Bool) -> Bool =
  match a, b
    True, True => True
    True, False => False
    False, True => False
    False, False => False

fun andOrCheck() -> Int =
  x := and(True, True)
  y := and(True, False)
  match x, y
    True, False => 1
    _, _ => 0

fun classifyTwoInts(a: Int, b: Int) -> Int =
  match a, b
    1, 1 => 100
    1, 2 => 200
    _, _ => 0

fun combine(a: Int, b: Int) -> Int =
  match a, b
    0, y => y
    x, 0 => x
    x, y => x + y

fun bothOptions(a: Option, b: Option) -> Int =
  match a, b
    Some(x), Some(y) => x + y
    _, _ => 0

enum Point =
  | Point(x: Int, y: Int)

  fun sum(self) -> Int =
    match self
      Point(x, y) => x + y

  fun classify(self) -> Str =
    match self
      Point(0, 0) => "origin"
      Point(x, 0) => "on x axis"
      Point(_, _) => "elsewhere"

enum Shape =
  | Circle(Point)
  | Square(Point)

  fun measure(self) -> Int =
    match self
      Circle(Point(x, y)) => x + y
      Square(Point(x, y)) => x * y

# Named payload fields (`Ring(radius: Int)`) alongside the
# unnamed-positional-payload form (`Circle(Point)` above) — lets a
# multi-field variant like `Rect` name each field instead of leaving them as
# anonymous positional types. Pattern matching is unaffected either way
# (still positional, by declaration order — `Rect(w, h)`, not `Rect(w:, h:)`).
enum Figure =
  | Ring(radius: Int)
  | Rect(w: Int, h: Int)

  fun area(self) -> Int =
    match self
      Ring(radius) => radius * radius
      Rect(w, h) => w * h

test "match example computes correctly"
  assert main() == 5

test "nested constructor pattern matches and binds runs correctly"
  assert unwrapNested(Wrap(Present(5))) == 5

test "nested constructor pattern mismatch falls through to next case runs correctly"
  assert unwrapNested(Wrap(Absent)) == -1

test "nested constructor pattern against a specialized generic enum runs correctly"
  assert unwrapGenericBox(GFull(GSome(7))) == 7

test "doubly nested constructor pattern runs correctly"
  assert unwrapTwice(RSome(RSome(RNone))) == 1

test "match int and wildcard run correctly"
  assert classifyForWildcardTest(2) == 200

test "match bool variant pattern runs correctly"
  assert describeBool(False) == 0

test "non bool bare tag pattern runs correctly"
  assert colorCode(Green) == 2

test "constructor pattern wildcard field runs correctly"
  assert isSome(Some(99)) == 1

test "constructor pattern does not misfire on payload free sibling"
  assert describeOption(Absent) == 0

test "multi subject match with enum tags runs correctly"
  assert andOrCheck() == 1

test "multi subject match falls through to next case when only first position matches"
  # The first case's position-0 pattern (`1`) matches, but position-1 (`1`)
  # doesn't (b is 2) — codegen must fall through to the *next case* (trying
  # its own position 0 again), not just "move on" within the first case.
  assert classifyTwoInts(1, 2) == 200

test "multi subject match with binding and wildcard runs correctly"
  assert combine(3, 4) == 7

test "multi subject match with generic enum variant runs correctly"
  assert bothOptions(Some(3), Some(4)) == 7

test "match destructures a plain class the same way it destructures an enum variant"
  assert Point(x: 3, y: 4).sum() == 7

test "match on a plain class supports literal/wildcard sub-patterns and case fallthrough"
  assert Point(x: 0, y: 0).classify() == "origin"
  assert Point(x: 5, y: 0).classify() == "on x axis"
  assert Point(x: 5, y: 5).classify() == "elsewhere"

test "a plain class nested inside an enum variant pattern destructures correctly"
  assert Circle(Point(x: 3, y: 4)).measure() == 7
  assert Square(Point(x: 3, y: 4)).measure() == 12

test "named-payload enum variant constructs via named args and destructures by position"
  assert Ring(radius: 5).area() == 25
  assert Rect(w: 3, h: 4).area() == 12

test "named-payload enum variant still supports positional construction too"
  assert Ring(5).area() == 25
  assert Rect(3, 4).area() == 12