///|
/// Move every element's prefix onto the element before it, and lift a group's
/// outermost prefix and suffix onto the group itself.
///
/// The concatenation is unchanged — this pass moves text between adjacent
/// nodes, never adds or drops any — so source reproduction is exact with or
/// without it. What it fixes is WHERE the text sits, which is what a consumer
/// reading one node's metadata sees. Without it, the comment written after a
/// term is found on the term after that.
///
/// The reference does the same and states the one deliberate omission: a
/// `parens`, `block` or `alts` keeps its leading prefix and trailing suffix
/// INSIDE, because moving them out would put them on the wrong side of the
/// delimiter.
///
/// The reference needs a helper here to find where a node's properties live --
/// on the operator rather than on the `op` wrapper it writes around it. This
/// representation puts them on the node itself in every case, so there is
/// nothing to look up.
fn normalize_group_raw(node : @ast.Node) -> Unit {
  match node.it {
    // A document's own suffix is already the end-of-file trail, so nothing is
    // lifted onto it here.
    Multi(children) => {
      let _ = shift_prefix_to_suffix(children, extract_suffix=false)
    }
    Group(children) => {
      let carried = shift_prefix_to_suffix(children, extract_suffix=true)
      match carried {
        Some(suffix) => node.meta.suffix = suffix.combine(node.meta.suffix)
        None => ()
      }
      if children.length() > 0 {
        let first = children[0]
        node.meta.prefix = node.meta.prefix.combine(first.meta.prefix)
        first.meta.prefix = Empty
      }
    }
    Parens(children)
    | Brackets(children)
    | Braces(children)
    | Quotes(children)
    | Block(children)
    | Alts(children) => {
      let _ = shift_prefix_to_suffix(children, extract_suffix=false)
    }
    _ => ()
  }
}

///|
/// Returns the suffix lifted off the last element, when asked for it.
fn shift_prefix_to_suffix(
  elems : Array[@ast.Node],
  extract_suffix~ : Bool,
) -> @raw.Raw? {
  for e in elems {
    normalize_group_raw(e)
  }
  if elems.length() == 0 {
    return None
  }
  for i in 1..