///|
fn Machine::read_bytes(
  self : Machine,
  address : Int,
  count : Int,
) -> Array[Byte] raise ForthError {
  self.memory_range(address, count)
  Array::makei(count, i => self.read_byte(address + i))
}

///|
fn Machine::read_byte(self : Machine, address : Int) -> Byte raise ForthError {
  if address >= 0 && address < self.memory.length() {
    return self.memory[address]
  }
  if address >= 65536 && address < 66560 {
    return self.scratch[address - 65536]
  }
  if address >= 1048576 && address - 1048576 < self.strings.length() {
    return self.strings[address - 1048576]
  }
  for frame in self.sources {
    if address >= frame.address && address - frame.address < frame.data.length() {
      return frame.data[address - frame.address]
    }
  }
  raise Invalid("invalid data space range")
}

///|
fn Machine::write_byte(
  self : Machine,
  address : Int,
  value : Byte,
) -> Unit raise ForthError {
  if address >= 0 && address < self.memory.length() {
    self.memory[address] = value
  } else if address >= 65536 && address < 66560 {
    self.scratch[address - 65536] = value
  } else {
    raise Invalid("invalid or read-only data space range")
  }
}

///|
fn Machine::write_text(self : Machine, text : String) -> Unit raise ForthError {
  self.write_bytes(@utf8.encode(text).to_array())
}

///|
fn Machine::write_bytes(
  self : Machine,
  bytes : Array[Byte],
) -> Unit raise ForthError {
  if self.terminal.length() + bytes.length() > 2000000 {
    raise Invalid("output byte limit")
  }
  for byte in bytes {
    self.terminal.push(byte)
  }
}

///|
pub fn Machine::output_bytes(self : Machine) -> Bytes {
  Bytes::from_array(self.terminal[:])
}

///|
pub fn Machine::output_text(self : Machine) -> String {
  @utf8.decode_lossy(self.output_bytes()[:])
}

///|
pub fn Machine::clear_output(self : Machine) -> Unit {
  self.terminal.clear()
  self.output.clear()
}