///|
/// Parse a block: the body of a `:` or of one `|` alternative.
///
/// Answers with the ITEMS to append to the enclosing group -- normally the
/// block itself, possibly followed by the alternatives that ended it, and then
/// whatever `parse_group` found after the closer. That flattening is what makes
/// `a: b | c | d` one group with a block and an `alts` rather than a nest.
fn Parser::parse_block(
  self : Parser,
  t : @lexer.Token?,
  start : Int,
  count~ : Bool,
  line~ : Int?,
  closer~ : Closer,
  bar_closes? : Bool = false,
  bar_closes_line? : Int? = None,
  drop_empty? : Bool = false,
  delta~ : Int,
  raw~ : RawList,
  variant~ : @lexer.Variant,
  group_commenting? : @lexer.Token? = None,
  parent_column? : @column.Column? = None,
  block_mode? : BlockMode? = None,
  can_empty? : Bool = false,
  could_empty_if_start? : Bool = false,
  depth~ : Int,
) -> GroupResult raise @err.ShrubberyError {
  let (opener_t, oa) = self.next_of_opener(start, line, delta, RNil, count)
  match (t, opener_t) {
    (Some(tt), Some(ot)) => self.check_same_line(tt, ot, count)
    _ => ()
  }
  // Inside `«»` lines and columns stop mattering, which is the whole point of
  // writing one.
  let inside_count = count && opener_t is None
  let (commenting, a) = match group_commenting {
    Some(c) => (Some(c), oa)
    None =>
      self.next_of_commenting(
        oa.rest,
        oa.last_line,
        oa.delta,
        oa.raw,
        inside_count,
      )
  }
  let mode = match block_mode {
    Some(m) => m
    None =>
      match t {
        Some(tt) => Opened(tt, parent_column.unwrap_or(@column.zero))
        None => NoBlock
      }
  }
  let fail_empty = () => {
    match t {
      Some(tt) =>
        self.fail(tt, EmptyBlock(after=tt.text, could_empty_if_start~))
      None => ()
    }
  }
  if self.at_end(a.rest) {
    match opener_t {
      Some(ot) => self.fail(ot, ExpectedGuillemetClose)
      None => if !can_empty { fail_empty() }
    }
    let fallback = match t {
      Some(tt) => tt.span
      None => @basic.Span::at(@basic.start)
    }
    let (head, alts) = tag_as_block([], fallback)
    let items = [head]
    for x in alts {
      items.push(x)
    }
    attach_block_raw(head, t, raw, Empty)
    return {
      items,
      rest: a.rest,
      end_line: line,
      end_delta: a.delta,
      tail_commenting: commenting,
      tail_raw: a.raw,
    }
  }
  let next_t = self.tok(a.rest)
  let content_column = next_t.column()
  let inner = self.parse_groups(a.rest, {
    depth: depth + 1,
    count: inside_count,
    closer: match opener_t {
      Some(ot) => Delim("\u{BB}", ot)
      None => closer
    },
    paren_immed: NotImmed,
    column: Some(content_column),
    bar_column: None,
    check_column: inside_count,
    bar_closes: opener_t is None && bar_closes,
    bar_closes_line: if opener_t is None && inside_count {
      bar_closes_line
    } else {
      None
    },
    block_mode: mode,
    can_empty: false,
    comma_time: false,
    sequence_mode: AnyNumber,
    last_line: a.last_line,
    delta: a.delta,
    commenting,
    tail_commenting: None,
    raw: a.raw,
    variant,
  })
  if !can_empty &&
    inner.groups.length() == 0 &&
    t is Some(_) &&
    opener_t is None {
    fail_empty()
  }
  // Comments at the block's OWN indentation stay with it; everything else that
  // trails belongs to whatever encloses the block, so the split has to happen
  // BEFORE the outward tail is decided or the same comments are written twice.
  let (outward, block_suffix) = self.keep_same_indentation_comments(
    Some(content_column),
    inner.tail_raw,
  )
  let used_closer = opener_t is Some(_) || closer.is_delim()
  // After a `»` the group may take nothing more but alternatives, which is
  // what `End` mode enforces.
  let post = if used_closer {
    self.parse_group(inner.rest, {
      depth,
      count,
      line: inner.end_line,
      column: parent_column,
      bar_column: None,
      operator_column: None,
      bar_closes,
      bar_closes_line,
      block_mode: End,
      can_empty: false,
      delta: inner.end_delta,
      raw: RNil,
      at_mode: None,
      variant,
    })
  } else {
    (
      {
        items: [],
        rest: inner.rest,
        end_line: inner.end_line,
        end_delta: inner.end_delta,
        tail_commenting: inner.tail_commenting,
        tail_raw: outward,
      } : GroupResult)
  }
  let fallback = match t {
    Some(tt) => tt.span
    None => next_t.span
  }
  let (head, alts) = tag_as_block(inner.groups, fallback)
  let drop = inner.groups.length() == 0 && drop_empty
  let items = []
  if !drop {
    attach_block_raw(
      head,
      t,
      raw,
      (if used_closer { outward.to_raw() } else { Empty }).combine(
        block_suffix.to_raw(),
      ),
    )
    items.push(head)
    for x in alts {
      items.push(x)
    }
  }
  for x in post.items {
    items.push(x)
  }
  {
    items,
    rest: post.rest,
    end_line: post.end_line,
    end_delta: post.end_delta,
    tail_commenting: post.tail_commenting,
    tail_raw: if drop {
      raw.then(post.tail_raw)
    } else {
      post.tail_raw
    },
  }
}

///|
/// Split the raw that trails a block's content into the part that belongs to
/// the block and the part that belongs to whatever encloses it.
///
/// A comment sitting at the block's OWN indentation is part of the block -- it
/// reads as a note about its last group -- while one further left belongs
/// outside. The reference notes that reversing twice here is quadratic if
/// blocks are nested N deep with N comments after them; that is not a file
/// anyone writes.
fn Parser::keep_same_indentation_comments(
  self : Parser,
  column : @column.Column?,
  tail : RawList,
) -> (RawList, RawList) raise @err.ShrubberyError {
  let col = match column {
    Some(c) => c
    None => return (tail, RNil)
  }
  // Walk from the START of the trailing run, which is the far end of this
  // reversed list.
  let items = []
  let mut cur = tail
  while cur is RCons(item, rest) {
    items.push(item)
    cur = rest
  }
  items.rev_in_place()
  let mut split = 0
  let mut i = 0
  while i < items.length() {
    match items[i] {
      RTok(t) =>
        if t.kind is Whitespace {
          i = i + 1
        } else if t.kind is Comment && self.col_eq(t, col, t.column()) {
          i = i + 1
          // This comment, and the whitespace that led to it, stay.
          split = i
        } else {
          break
        }
      _ => break
    }
  }
  let mut kept : RawList = RNil
  for x in items[split:] {
    kept = RCons(x, kept)
  }
  let mut suffix : RawList = RNil
  for x in items[0:split] {
    suffix = RCons(x, suffix)
  }
  (kept, suffix)
}

///|
/// Give a block the text of the `:` or `|` that opened it, the prefix before
/// it, and whatever trailed its content.
fn attach_block_raw(
  block : @ast.Node,
  t : @lexer.Token?,
  pre : RawList,
  tail : @raw.Raw,
) -> Unit {
  match t {
    Some(tok) => {
      let head : @raw.Raw = Str(tok.raw_text())
      block.meta.raw = head.combine(block.meta.raw)
    }
    None => ()
  }
  attach_prefix(block, pre)
  block.meta.tail = block.meta.tail.combine(tail)
}