///|
fn LinkSource::write(self : LinkSource, writer : UpdateWriter) -> Unit {
  let single = self.is_single()
  let mut flags = if single { 0 } else { 1 }
  if self.start.assoc == After {
    flags = flags | 2
  }
  if self.end.assoc == After {
    flags = flags | 4
  }
  if !self.start.is_relative() {
    flags = flags | 8
  }
  if !self.end.is_relative() {
    flags = flags | 16
  }
  if self.start.is_root() || self.end.is_root() {
    flags = flags | 32
  }
  writer.raw.byte(flags)
  fn scope(index : StickyIndex) -> Unit {
    match index.scope {
      Relative(id) | Nested(id) => writer.raw.id(id)
      Root(name) => writer.string(name)
    }
  }
  scope(self.start)
  if !single || !self.end.is_relative() {
    scope(self.end)
  }
}

///|
fn LinkSource::read(reader : UpdateReader) -> LinkSource raise DecodeError {
  let flags = reader.raw.byte()
  guard flags < 64 else { raise InvalidData("invalid weak link flags") }
  fn scope(unbounded : Bool) -> IndexScope raise DecodeError {
    if unbounded {
      if (flags & 32) != 0 {
        Root(reader.string())
      } else {
        Nested(reader.raw.id())
      }
    } else {
      Relative(reader.raw.id())
    }
  }
  let start = scope((flags & 8) != 0)
  let end = if (flags & 16) != 0 {
    scope(true)
  } else if (flags & 1) == 0 {
    start
  } else {
    scope(false)
  }
  LinkSource(
    StickyIndex(
      scope=start,
      assoc=if (flags & 2) != 0 { After } else { Before },
    ),
    StickyIndex(scope=end, assoc=if (flags & 4) != 0 { After } else { Before }),
  )
}