plum

#treesitter#compiler#wasm

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

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


examples/arrays.plum
# Arrays: typed native arrays and arrays of structs.
import std/Int
import std/Float
import std/Array
import std/Grid
import std/Str
import std/Slice

# ==== typed arrays ====

# Typed arrays: unboxed native `Array[Int]` and `Array[Float]` lowering to
# WebAssembly primitive arrays, along with flat 2D `Grid[T]`.

# Tile-grid style data: Array[Int] / Array[Float] / Array[Bool] are native
# unboxed wasm arrays (i64 / f64 / i32 elements), not boxed anyref arrays.
fun sumInts(n: Int) -> Int =
  a := Array.filled(n, 3)
  total := 0
  for i := range n
    total = total + a.get(i)
  return total

fun sumFloats(n: Int) -> Float =
  a := Array.filled(n, 0.5)
  total := 0.0
  for i := range n
    total = total + a.get(i)
  return total

fun countTrue(n: Int) -> Int =
  a := Array.filled(n, false)
  a.set(1, true)
  a.set(3, true)
  c := 0
  for i := range n
    if a.get(i)
      c = c + 1
  return c

# ==== struct arrays ====

# Arrays of structs are stored field by field (struct-of-arrays): `Array[Vec2]`
# is one object holding an `f64` array for `x` and one for `y`, so `get`/`set`
# allocate nothing per element. `Slice`, `Grid` and friends get this for free.

struct Vec2(x: Float, y: Float)

struct Rect(pos: Vec2, size: Vec2) =

  fun area(self) -> Float =
    self.size.x * self.size.y

struct Tile(id: Int, solid: Bool, weight: Float)

struct World(points: Slice[Vec2], name: Str)

struct One(n: Int)

struct Flags(a: Bool, b: Bool, c: Int)

struct L3(r: Rect, k: Int)

struct Holder(arr: Array[Vec2])

struct Named(label: Str, v: Vec2)

fun bump(h: Holder) =
  h.arr.set(0, Vec2(x: 77.0, y: 88.0))

fun first[T](a: Array[T]) -> T =
  a.get(0)

fun mk() -> Array[Vec2] =
  Array[Vec2]().push(Vec2(x: 1.0, y: 2.0))

struct Px(r: Byte, g: Byte, b: Byte, a: Byte)

fun mkPx(v: Int) -> Px =
  Px(r: Byte(v), g: Byte(v + 1), b: Byte(v + 2), a: Byte(255))

# ==== typed arrays tests ====

test "array_int_native: filled arrays hold their values"
  assert sumInts(10) == 30
  assert sumFloats(4) == 2.0
  assert countTrue(5) == 2

# ==== struct arrays tests ====

test "List[Vec2] push/get/set round-trips across growth"
  l := Slice[Vec2]()
  for i := range 100
    l.push(Vec2(x: Float(i), y: Float(i) * 2.0))
  assert l.length() == 100
  v := l.get(37)!
  assert v.x == 37.0
  assert v.y == 74.0
  _ := l.set(37, Vec2(x: v.x + 1.0, y: v.y))!
  assert l.get(37)!.x == 38.0
  assert l.get(37)!.y == 74.0
  assert l.get(36)!.x == 36.0
  assert l.get(99)!.y == 198.0

test "nested struct (Rect of Vec2s) in an Array"
  a := Array[Rect]()
  a = a.push(Rect(pos: Vec2(x: 1.0, y: 2.0), size: Vec2(x: 3.0, y: 4.0)))
  a = a.push(Rect(pos: Vec2(x: 5.0, y: 6.0), size: Vec2(x: 7.0, y: 8.0)))
  assert a.length() == 2
  r := a.get(1)
  assert r.pos.x == 5.0
  assert r.pos.y == 6.0
  assert r.size.x == 7.0
  assert r.size.y == 8.0
  a.set(0, Rect(pos: r.size, size: r.pos))
  assert a.get(0).pos.y == 8.0
  assert a.get(0).size.x == 5.0
  assert a.get(0).area() == 30.0

test "struct with Int, Bool and Float leaves"
  a := Array[Tile]()
  a = a.push(Tile(id: 7, solid: true, weight: 1.5))
  a = a.push(Tile(id: 9, solid: false, weight: 2.5))
  assert a.get(0).id == 7
  assert a.get(0).solid
  assert a.get(0).weight == 1.5
  assert a.get(1).id == 9
  assert !a.get(1).solid
  assert a.get(1).weight == 2.5
  b := a.resize(4)
  assert b.length() == 4
  assert b.get(1).id == 9
  assert b.get(3).id == 0
  assert !b.get(3).solid

test "copy semantics: a value read from the array is a copy until set"
  l := Slice[Vec2]()
  l.push(Vec2(x: 1.0, y: 1.0))
  v := l.get(0)!
  v = Vec2(x: 99.0, y: 99.0)
  assert l.get(0)!.x == 1.0
  w := l.get(0)!
  _ := l.set(0, Vec2(x: 5.0, y: 5.0))!
  assert w.x == 1.0
  assert l.get(0)!.x == 5.0
  assert v.x == 99.0

test "for x := range over Array[Vec2]"
  a := Array[Vec2]().push(Vec2(x: 1.0, y: 10.0)).push(Vec2(x: 2.0, y: 20.0)).push(Vec2(x: 3.0, y: 30.0))
  sx := 0.0
  sy := 0.0
  for v := range a
    sx = sx + v.x
    sy = sy + v.y
  assert sx == 6.0
  assert sy == 60.0

test "Array.filled with a struct"
  a := Array.filled(5, Vec2(x: 2.0, y: 3.0))
  assert a.length() == 5
  assert a.get(4).y == 3.0

test "Grid[Tile] get/set"
  g := Grid.filled(3, 2, Tile(id: 1, solid: false, weight: 0.5))
  g.set(2, 1, Tile(id: 42, solid: true, weight: 9.0))
  assert g.get(2, 1).id == 42
  assert g.get(2, 1).solid
  assert g.get(0, 0).id == 1
  assert g.get(1, 1).weight == 0.5

test "a List[Vec2] inside a record field"
  w := World(points: Slice[Vec2](), name: "w")
  w.points.push(Vec2(x: 1.0, y: 2.0))
  w.points.push(Vec2(x: 3.0, y: 4.0))
  assert w.points.length() == 2
  _ := w.points.set(1, Vec2(x: 30.0, y: 40.0))!
  assert w.points.get(1)!.x == 30.0
  assert w.points.get(0)!.y == 2.0

test "probe push growth, shrink, single leaf"
  a := Array[One]()
  for i := range 50
    a = a.push(One(n: i))
  assert a.length() == 50
  assert a.get(49).n == 49
  b := a.resize(10)
  assert b.length() == 10
  assert b.get(9).n == 9
  c := b.resize(0)
  assert c.length() == 0

test "probe Byte/Bool leaves"
  a := Array[Flags]()
  a = a.push(Flags(a: false, b: true, c: 3))
  assert a.get(0).b
  assert a.get(0).c == 3

test "probe three-level nested"
  a := Array[L3]()
  a = a.push(L3(r: Rect(pos: Vec2(x: 1.0, y: 2.0), size: Vec2(x: 3.0, y: 4.0)), k: 9))
  assert a.get(0).r.size.y == 4.0
  assert a.get(0).k == 9
  assert a.get(0).r.area() == 12.0

test "probe array field reference semantics"
  h := Holder(arr: Array[Vec2]().push(Vec2(x: 1.0, y: 1.0)))
  bump(h)
  assert h.arr.get(0).x == 77.0
  alias := h.arr
  alias.set(0, Vec2(x: 5.0, y: 5.0))
  assert h.arr.get(0).x == 5.0

test "probe generic first, equality"
  a := Array[Vec2]().push(Vec2(x: 1.0, y: 2.0)).push(Vec2(x: 1.0, y: 2.0))
  assert first(a).y == 2.0
  eq := a.get(0) == a.get(1)
  assert eq

test "probe option and list"
  n: Option[Array[Vec2]] := Some(Array[Vec2]().push(Vec2(x: 3.0, y: 3.0)))
  assert n!.get(0).x == 3.0
  l := Slice[Array[Vec2]]()
  l.push(Array[Vec2]().push(Vec2(x: 4.0, y: 4.0)))
  assert l.get(0)!.get(0).x == 4.0
  r := mk()
  assert r.get(0).y == 2.0

test "probe break/continue"
  a := Array[Vec2]().push(Vec2(x: 1.0, y: 0.0)).push(Vec2(x: 2.0, y: 0.0)).push(Vec2(x: 3.0, y: 0.0)).push(Vec2(x: 4.0, y: 0.0))
  s := 0.0
  for v := range a
    if v.x == 2.0
      continue
    if v.x == 4.0
      break
    s = s + v.x
  assert s == 4.0

test "probe Str leaf"
  a := Array[Named]()
  a = a.push(Named(label: "hi", v: Vec2(x: 1.0, y: 2.0)))
  assert a.get(0).label == "hi"

test "Byte struct fields: copy, compare, store"
  p := mkPx(10)
  q := p
  assert p == q
  assert p.r == Byte(10)
  assert p.a == Byte(255)
  assert p != mkPx(11)
  a := Array[Px]().push(p).push(mkPx(20))
  assert a.get(1).g == Byte(21)
  assert a.get(0) == q
  l := Slice[Px]()
  for i := range 50
    l.push(mkPx(i))
  assert l.get(37)!.b == Byte(39)
  assert l.get(49)! == mkPx(49)

test "assert == on structs without toStr"
  assert Vec2(x: 1.0, y: 2.0) == Vec2(x: 1.0, y: 2.0)