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

# Regression coverage for `plum-std/Number.plum` — the methods it now hosts
# for BOTH `Int` and `Float` (via `match self`), after `Int.plum`/`Float.plum`
# were folded into it and deleted. Every call below reaches its method
# through the primitive-to-wrapping-enum dispatch fallback (`Int`/`Float`
# have no method tables of their own any more).

fun toInt(n: Number) -> Int =
  match n
    Int(i) => i
    Float(f) => Int(f)

test "abs works for both Int and Float, wrapping back into the same variant"
  assert {-5}.abs().kind() == "Int"
  assert {-2.5}.abs().kind() == "Float"

test "sign returns -1/0/1 (or the matching Float) for both Int and Float"
  assert toInt({5}.sign()) == 1
  assert toInt({-5}.sign()) == -1
  assert toInt({0}.sign()) == 0

test "hash is identity for Int and truncating for Float"
  assert {5}.hash() == 5
  assert {5.9}.hash() == 5

test "trunc/floor/ceil/round agree with Int passthrough and real Float math"
  assert {5}.trunc() == 5.0f
  assert {2.7}.trunc() == 2.0f
  assert {-2.7}.trunc() == -2.0f
  assert {2.7}.floor() == 2.0f
  assert {-2.7}.floor() == -3.0f
  assert {2.3}.ceil() == 3.0f
  assert {-2.3}.ceil() == -2.0f
  assert {2.5}.round() == 3.0f
  assert {-2.5}.round() == -3.0f

test "sqrt works for Int (via Float conversion) and Float directly"
  assert {4}.sqrt() == 2.0f
  assert {2.25}.sqrt() == 1.5f

test "pow computes integer powers exactly for both Int and Float self"
  assert {2}.pow(10.0f) == 1024.0f
  assert {2.0}.pow(10.0f) == 1024.0f

test "log2/log10 agree between Int and the equivalent Float"
  # `log`/`log2`/`log10` are built on the Taylor-series `ln` approximation
  # (see `plum-std/Number.plum`'s `ln`), not exact libm — compare within a
  # small epsilon rather than requiring bit-exact equality.
  a := {8}.log2()
  b := {8.0}.log2()
  assert {a - b}.abs().toFloatValue() < 0.0001f
  c := {100}.log10()
  d := {100.0}.log10()
  assert {c - d}.abs().toFloatValue() < 0.0001f

test "isFinite/isInfinite/isNaN are always well-defined for Int, real for Float"
  assert {5}.isFinite()
  assert !{5}.isInfinite()
  assert !{5}.isNaN()
  assert {1.0f / 0.0f}.isInfinite()
  assert {0.0f / 0.0f}.isNaN()
  assert {1.5}.isFinite()

test "min/max compare across Int and Float without losing the original variant"
  assert {3}.min(5).kind() == "Int"
  assert {3}.max(2.5).kind() == "Int"
  assert {2.5}.min(3).kind() == "Float"

test "toStr renders Int and Float correctly, including negatives and fractions"
  assert {42}.toStr() == "42"
  assert {-7}.toStr() == "-7"
  assert {0}.toStr() == "0"
  assert {3.5}.toStr() == "3.5"
  assert {-3.5}.toStr() == "-3.5"

test "parseInt/parseFloat round-trip a rendered number"
  assert parseInt("123").unwrapOr(-1) == 123
  assert parseInt("-45").unwrapOr(1) == -45
  assert parseFloat("3.25").unwrapOr(-1.0f) == 3.25f

test "trig/hyperbolic/exp/ln free functions and Number methods still work post-migration"
  assert sin(0.0f) == 0.0f
  assert cos(0.0f) == 1.0f
  assert {0}.sinh() == 0.0f
  assert {0}.cosh() == 1.0f

test "string interpolation of a Float value works, via Number.toStr"
  x := 3.5f
  assert "value is {x}" == "value is 3.5"
  y := {8}.log2()
  assert "computed is {y}" == "computed is 3.0"

test "assert of two directly-chained Float method calls renders a real failure message"
  # Previously crashed the whole compile with "interpolating a Float value is
  # not yet supported" instead of a normal pass/fail report — `looksLikeFloat`
  # only caught an OBVIOUS float literal on either side of the comparison,
  # missing any Float-typed expression that wasn't written as one (like these
  # chained method calls). Fixed at the root: `"{expr}"` interpolation of a
  # Float now genuinely works (via `Number.toStr`), so the "Expected/Actual"
  # failure-message machinery no longer needs to special-case Float at all.
  assert {8}.log2() == {8.0}.log2()