plum

#treesitter#compiler#wasm

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

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


plum-examples/numbers.plum
ca5fd6f 1
import std/Bool
ca5fd6f 2
import std/Str
ca5fd6f 3
import std/Number
ca5fd6f 4
ca5fd6f 5
# Regression coverage for `plum-std/Number.plum` — the methods it now hosts
ca5fd6f 6
# for BOTH `Int` and `Float` (via `match self`), after `Int.plum`/`Float.plum`
ca5fd6f 7
# were folded into it and deleted. Every call below reaches its method
ca5fd6f 8
# through the primitive-to-wrapping-enum dispatch fallback (`Int`/`Float`
ca5fd6f 9
# have no method tables of their own any more).
ca5fd6f 10
ca5fd6f 11
fun toInt(n: Number) -> Int =
ca5fd6f 12
  match n
ca5fd6f 13
    Int(i) => i
ca5fd6f 14
    Float(f) => Int(f)
ca5fd6f 15
ca5fd6f 16
test "abs works for both Int and Float, wrapping back into the same variant"
ca5fd6f 17
  assert {-5}.abs().kind() == "Int"
ca5fd6f 18
  assert {-2.5}.abs().kind() == "Float"
ca5fd6f 19
ca5fd6f 20
test "sign returns -1/0/1 (or the matching Float) for both Int and Float"
ca5fd6f 21
  assert toInt({5}.sign()) == 1
ca5fd6f 22
  assert toInt({-5}.sign()) == -1
ca5fd6f 23
  assert toInt({0}.sign()) == 0
ca5fd6f 24
ca5fd6f 25
test "hash is identity for Int and truncating for Float"
ca5fd6f 26
  assert {5}.hash() == 5
ca5fd6f 27
  assert {5.9}.hash() == 5
ca5fd6f 28
ca5fd6f 29
test "trunc/floor/ceil/round agree with Int passthrough and real Float math"
ca5fd6f 30
  assert {5}.trunc() == 5.0f
ca5fd6f 31
  assert {2.7}.trunc() == 2.0f
ca5fd6f 32
  assert {-2.7}.trunc() == -2.0f
ca5fd6f 33
  assert {2.7}.floor() == 2.0f
ca5fd6f 34
  assert {-2.7}.floor() == -3.0f
ca5fd6f 35
  assert {2.3}.ceil() == 3.0f
ca5fd6f 36
  assert {-2.3}.ceil() == -2.0f
ca5fd6f 37
  assert {2.5}.round() == 3.0f
ca5fd6f 38
  assert {-2.5}.round() == -3.0f
ca5fd6f 39
ca5fd6f 40
test "sqrt works for Int (via Float conversion) and Float directly"
ca5fd6f 41
  assert {4}.sqrt() == 2.0f
ca5fd6f 42
  assert {2.25}.sqrt() == 1.5f
ca5fd6f 43
ca5fd6f 44
test "pow computes integer powers exactly for both Int and Float self"
ca5fd6f 45
  assert {2}.pow(10.0f) == 1024.0f
ca5fd6f 46
  assert {2.0}.pow(10.0f) == 1024.0f
ca5fd6f 47
ca5fd6f 48
test "log2/log10 agree between Int and the equivalent Float"
ca5fd6f 49
  # `log`/`log2`/`log10` are built on the Taylor-series `ln` approximation
ca5fd6f 50
  # (see `plum-std/Number.plum`'s `ln`), not exact libm — compare within a
ca5fd6f 51
  # small epsilon rather than requiring bit-exact equality.
ca5fd6f 52
  a := {8}.log2()
ca5fd6f 53
  b := {8.0}.log2()
ca5fd6f 54
  assert {a - b}.abs().toFloatValue() < 0.0001f
ca5fd6f 55
  c := {100}.log10()
ca5fd6f 56
  d := {100.0}.log10()
ca5fd6f 57
  assert {c - d}.abs().toFloatValue() < 0.0001f
ca5fd6f 58
ca5fd6f 59
test "isFinite/isInfinite/isNaN are always well-defined for Int, real for Float"
ca5fd6f 60
  assert {5}.isFinite()
ca5fd6f 61
  assert !{5}.isInfinite()
ca5fd6f 62
  assert !{5}.isNaN()
ca5fd6f 63
  assert {1.0f / 0.0f}.isInfinite()
ca5fd6f 64
  assert {0.0f / 0.0f}.isNaN()
ca5fd6f 65
  assert {1.5}.isFinite()
ca5fd6f 66
ca5fd6f 67
test "min/max compare across Int and Float without losing the original variant"
ca5fd6f 68
  assert {3}.min(5).kind() == "Int"
ca5fd6f 69
  assert {3}.max(2.5).kind() == "Int"
ca5fd6f 70
  assert {2.5}.min(3).kind() == "Float"
ca5fd6f 71
ca5fd6f 72
test "toStr renders Int and Float correctly, including negatives and fractions"
ca5fd6f 73
  assert {42}.toStr() == "42"
ca5fd6f 74
  assert {-7}.toStr() == "-7"
ca5fd6f 75
  assert {0}.toStr() == "0"
ca5fd6f 76
  assert {3.5}.toStr() == "3.5"
ca5fd6f 77
  assert {-3.5}.toStr() == "-3.5"
ca5fd6f 78
ca5fd6f 79
test "parseInt/parseFloat round-trip a rendered number"
ca5fd6f 80
  assert parseInt("123").unwrapOr(-1) == 123
ca5fd6f 81
  assert parseInt("-45").unwrapOr(1) == -45
ca5fd6f 82
  assert parseFloat("3.25").unwrapOr(-1.0f) == 3.25f
ca5fd6f 83
ca5fd6f 84
test "trig/hyperbolic/exp/ln free functions and Number methods still work post-migration"
ca5fd6f 85
  assert sin(0.0f) == 0.0f
ca5fd6f 86
  assert cos(0.0f) == 1.0f
ca5fd6f 87
  assert {0}.sinh() == 0.0f
ca5fd6f 88
  assert {0}.cosh() == 1.0f
ca5fd6f 89
ca5fd6f 90
test "string interpolation of a Float value works, via Number.toStr"
ca5fd6f 91
  x := 3.5f
ca5fd6f 92
  assert "value is {x}" == "value is 3.5"
ca5fd6f 93
  y := {8}.log2()
ca5fd6f 94
  assert "computed is {y}" == "computed is 3.0"
ca5fd6f 95
ca5fd6f 96
test "assert of two directly-chained Float method calls renders a real failure message"
ca5fd6f 97
  # Previously crashed the whole compile with "interpolating a Float value is
ca5fd6f 98
  # not yet supported" instead of a normal pass/fail report — `looksLikeFloat`
ca5fd6f 99
  # only caught an OBVIOUS float literal on either side of the comparison,
ca5fd6f 100
  # missing any Float-typed expression that wasn't written as one (like these
ca5fd6f 101
  # chained method calls). Fixed at the root: `"{expr}"` interpolation of a
ca5fd6f 102
  # Float now genuinely works (via `Number.toStr`), so the "Expected/Actual"
ca5fd6f 103
  # failure-message machinery no longer needs to special-case Float at all.
ca5fd6f 104
  assert {8}.log2() == {8.0}.log2()