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
af1776b 1
import std/Result
af1776b 2
import std/Option
af1776b 3
import std/Bool
af1776b 4
import std/Number
af1776b 5
import std/Str
af1776b 6
import std/List
af1776b 7
af1776b 8
# `expr?` unwraps a `Result`'s `Ok` or an `Option`'s `Some`, or exits the
af1776b 9
# enclosing function early with the `Err`/`None` value as-is otherwise — same
af1776b 10
# idea as Rust's `?`. The enclosing function's own return type must accept
af1776b 11
# whatever gets returned early; that isn't checked until codegen (see
af1776b 12
# `plum-checker`'s `inferExpr` on `Expr::Try`), so a mismatch there still
af1776b 13
# surfaces as a clear compile error, just a less precise one.
ec5336f 14
#
ec5336f 15
# `left ?: right` (elvis) is the plain-expression counterpart: same Ok/Some
ec5336f 16
# vs Err/None narrowing, but no early return — `right` is a fallback value,
ec5336f 17
# fully checked (unlike `?`) since there's no enclosing-return-type
ec5336f 18
# uncertainty to be permissive about.
af1776b 19
af1776b 20
fun parsePositive(s: Str) -> Result[Int, Str] =
af1776b 21
  n := parseInt(s)?
af1776b 22
  if n < 0
af1776b 23
    return Err("negative")
af1776b 24
  return Ok(n)
af1776b 25
af1776b 26
fun sumTwo(a: Str, b: Str) -> Result[Int, Str] =
af1776b 27
  x := parsePositive(a)?
af1776b 28
  y := parsePositive(b)?
af1776b 29
  return Ok(x + y)
af1776b 30
af1776b 31
fun firstPositive(list: List[Int]) -> Option[Int] =
af1776b 32
  match list.get(0)
af1776b 33
    Some(v) =>
af1776b 34
      if v > 0
af1776b 35
        return Some(v)
af1776b 36
      return None
af1776b 37
    None =>
af1776b 38
      return None
af1776b 39
af1776b 40
fun doubledFirstPositive(list: List[Int]) -> Option[Int] =
af1776b 41
  a := firstPositive(list)?
af1776b 42
  return Some(a * 2)
af1776b 43
ec5336f 44
# `left ?: right` — same Ok/Some-vs-Err/None narrowing as `?`, but a plain
ec5336f 45
# expression (no early return): `right` is the fallback value, unified against
ec5336f 46
# the success field's type by the checker.
ec5336f 47
ec5336f 48
fun sumOrZero(a: Str, b: Str) -> Int =
ec5336f 49
  x := parsePositive(a) ?: 0
ec5336f 50
  y := parsePositive(b) ?: 0
ec5336f 51
  x + y
ec5336f 52
ec5336f 53
fun firstOrDefault(list: List[Int], default: Int) -> Int =
ec5336f 54
  list.get(0) ?: default
ec5336f 55
af1776b 56
test "try operator unwraps Ok and propagates the value through two calls"
af1776b 57
  r := sumTwo("2", "3")
af1776b 58
  assert r.isOk()
af1776b 59
  assert r.unwrap() == 5
af1776b 60
af1776b 61
test "try operator exits early with Err, skipping the rest of the function"
af1776b 62
  r := sumTwo("2", "-3")
af1776b 63
  assert r.isErr()
af1776b 64
  assert r.unwrapErr() == "negative"
af1776b 65
af1776b 66
test "try operator unwraps Some and propagates the value"
af1776b 67
  l := List(5)
af1776b 68
  r := doubledFirstPositive(l)
af1776b 69
  assert r.isSome()
af1776b 70
  assert r.unwrap() == 10
af1776b 71
af1776b 72
test "try operator exits early with None, skipping the rest of the function"
af1776b 73
  l := List[Int]()
af1776b 74
  r := doubledFirstPositive(l)
af1776b 75
  assert r.isNone()
ec5336f 76
ec5336f 77
test "elvis operator unwraps Ok and falls back to 0 on Err"
ec5336f 78
  assert sumOrZero("2", "3") == 5
ec5336f 79
  assert sumOrZero("2", "-3") == 2
ec5336f 80
ec5336f 81
test "elvis operator unwraps Some and falls back to the given default on None"
ec5336f 82
  assert firstOrDefault(List(5), 99) == 5
ec5336f 83
  assert firstOrDefault(List[Int](), 99) == 99