///|
/// Write a shrubbery on one line, with `«»`, `;` and `,` doing all the
/// grouping.
///
/// This is the reading that does not care where it ends up: no line break and
/// no column carries meaning, so re-indenting the result, or wrapping it, or
/// pasting it into a file with a different indentation cannot change what it
/// says. That is why it is the default for writing a shrubbery down to be read
/// back later.
fn write_flat(
  node : @ast.Node,
  buf : StringBuilder,
  before_block~ : Bool,
) -> Unit {
  match node.it {
    Op(name) =>
      // An operator made only of colons, written just before a block, would
      // run into the block's own `:`.
      if before_block && all_colons(name) {
        buf.write_string(name)
        buf.write_char(' ')
      } else {
        buf.write_string(name)
      }
    Id(_) | Kw(_) | Lit(_) | Parsed(_) => buf.write_string(atom_text(node.it))
    Alts(blocks) => {
      let mut first = true
      for b in blocks {
        if !first {
          buf.write_char(' ')
        }
        first = false
        buf.write_string("|« ")
        let groups = b.children()
        for i in 0.. 0 {
            buf.write_string("; ")
          }
          write_flat(groups[i], buf, before_block=false)
        }
        buf.write_string(" »")
      }
    }
    Multi(groups) =>
      for i in 0.. 0 {
          buf.write_string("; ")
        }
        write_flat(groups[i], buf, before_block=false)
      }
    _ => write_flat_compound(node, buf)
  }
}

///|
fn write_flat_compound(node : @ast.Node, buf : StringBuilder) -> Unit {
  let children = node.children()
  let (open, close, sep) = match node.it {
    Group(_) => ("", "", " ")
    Block(_) =>
      (
        if children.length() == 0 {
          ":«"
        } else {
          ":« "
        },
        if children.length() == 0 {
          "»"
        } else {
          " »"
        },
        "; ",
      )
    Parens(_) => ("(", ")", ", ")
    Brackets(_) => ("[", "]", ", ")
    Braces(_) => ("{", "}", ", ")
    Quotes(_) => ("'«", "»'", "; ")
    _ => ("", "", " ")
  }
  buf.write_string(open)
  for i in 0.. 0 && !(child.it is Block(_)) {
      buf.write_string(sep)
    }
    write_flat(child, buf, before_block=next_is_block)
  }
  buf.write_string(close)
}

///|
fn all_colons(name : String) -> Bool {
  if name.length() == 0 {
    return false
  }
  for c in name {
    if c != ':' {
      return false
    }
  }
  true
}