plum

#treesitter#compiler#wasm

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

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


plum-examples/error_propagation.plum
import std/Result
import std/Option
import std/Bool
import std/Number
import std/Str
import std/List

# `expr?` unwraps a `Result`'s `Ok` or an `Option`'s `Some`, or exits the
# enclosing function early with the `Err`/`None` value as-is otherwise — same
# idea as Rust's `?`. The enclosing function's own return type must accept
# whatever gets returned early; that isn't checked until codegen (see
# `plum-checker`'s `inferExpr` on `Expr::Try`), so a mismatch there still
# surfaces as a clear compile error, just a less precise one.
#
# `left ?: right` (elvis) is the plain-expression counterpart: same Ok/Some
# vs Err/None narrowing, but no early return — `right` is a fallback value,
# fully checked (unlike `?`) since there's no enclosing-return-type
# uncertainty to be permissive about.

fun parsePositive(s: Str) -> Result[Int, Str] =
  n := parseInt(s)?
  if n < 0
    return Err("negative")
  return Ok(n)

fun sumTwo(a: Str, b: Str) -> Result[Int, Str] =
  x := parsePositive(a)?
  y := parsePositive(b)?
  return Ok(x + y)

fun firstPositive(list: List[Int]) -> Option[Int] =
  match list.get(0)
    Some(v) =>
      if v > 0
        return Some(v)
      return None
    None =>
      return None

fun doubledFirstPositive(list: List[Int]) -> Option[Int] =
  a := firstPositive(list)?
  return Some(a * 2)

# `left ?: right` — same Ok/Some-vs-Err/None narrowing as `?`, but a plain
# expression (no early return): `right` is the fallback value, unified against
# the success field's type by the checker.

fun sumOrZero(a: Str, b: Str) -> Int =
  x := parsePositive(a) ?: 0
  y := parsePositive(b) ?: 0
  x + y

fun firstOrDefault(list: List[Int], default: Int) -> Int =
  list.get(0) ?: default

test "try operator unwraps Ok and propagates the value through two calls"
  r := sumTwo("2", "3")
  assert r.isOk()
  assert r.unwrap() == 5

test "try operator exits early with Err, skipping the rest of the function"
  r := sumTwo("2", "-3")
  assert r.isErr()
  assert r.unwrapErr() == "negative"

test "try operator unwraps Some and propagates the value"
  l := List(5)
  r := doubledFirstPositive(l)
  assert r.isSome()
  assert r.unwrap() == 10

test "try operator exits early with None, skipping the rest of the function"
  l := List[Int]()
  r := doubledFirstPositive(l)
  assert r.isNone()

test "elvis operator unwraps Ok and falls back to 0 on Err"
  assert sumOrZero("2", "3") == 5
  assert sumOrZero("2", "-3") == 2

test "elvis operator unwraps Some and falls back to the given default on None"
  assert firstOrDefault(List(5), 99) == 5
  assert firstOrDefault(List[Int](), 99) == 99