///|
fn counted_body(
  code : Array[String],
  start : Int,
) -> (Array[String], Bool, Int) raise ForthError {
  let body = []
  let mut nesting = 1
  let mut pc = start
  while pc < code.length() {
    let word = code[pc]
    pc += 1
    if word == "do" || word == "?do" {
      nesting += 1
    }
    if word == "loop" || word == "+loop" {
      nesting -= 1
      if nesting == 0 {
        return (body, word == "+loop", pc)
      }
    }
    body.push(word)
  }
  raise Invalid("unterminated counted loop")
}

///|
fn Machine::counted_loop(
  self : Machine,
  body : Array[String],
  start : Int,
  limit : Int,
  plus : Bool,
  depth : Int,
) -> Unit raise ForthError {
  if self.loop_indices.length() >= 64 {
    raise Invalid("loop nesting limit")
  }
  let base = self.loop_indices.length()
  self.loop_indices.push(start)
  errdefer {
    while self.loop_indices.length() > base {
      ignore(self.loop_indices.pop())
    }
    self.leaving = false
  }
  while true {
    self.fuel -= 1
    if self.fuel < 0 {
      raise Invalid("execution budget exhausted")
    }
    self.execute(body, depth)
    if self.exiting {
      break
    }
    if self.loop_indices.length() != base + 1 {
      raise Invalid("UNLOOP must be followed by EXIT before loop continuation")
    }
    if self.leaving {
      self.leaving = false
      break
    }
    let step = if plus { self.pop() } else { 1 }
    let at = self.loop_indices.length() - 1
    let current = self.loop_indices[at]
    let crossed = if step > 0 {
      let distance = (limit - current).reinterpret_as_uint().to_uint64()
      distance != 0UL && distance <= step.to_uint64()
    } else if step < 0 {
      let distance = (current - limit).reinterpret_as_uint().to_uint64()
      distance < (-step.to_int64()).reinterpret_as_uint64()
    } else {
      false
    }
    if crossed {
      break
    }
    self.loop_indices[at] = current + step
  }
  while self.loop_indices.length() > base {
    ignore(self.loop_indices.pop())
  }
}