///|
fn triple_at(source : String, start : Int, end : Int) -> Bool {
  start + 3 <= end &&
  source[start] == '"' &&
  source[start + 1] == '"' &&
  source[start + 2] == '"'
}

///|
/// Extended dialect only: literal triple-double-quoted values; no escapes.
/// Semantic newlines inside the value remain exactly CR/LF/CRLF as supplied.
fn decode_multiline(
  source : String,
  line : Line,
  start : Int,
  dialect : Dialect,
  diagnostics : Array[Diagnostic],
) -> (String, Span, Int) {
  let mut close = start + 3
  while close < source.length() && !triple_at(source, close, source.length()) {
    close += 1
  }
  if close == source.length() {
    diagnostics.push(
      diagnostic(
        "INI010",
        "unclosed triple-quoted value",
        line_span(line, start, close),
      ),
    )
    return (
      text(source, start + 3, close),
      line_span(line, start, close),
      close,
    )
  }
  let value_end = close + 3
  let mut end = value_end
  while end < source.length() && source[end] != '\r' && source[end] != '\n' {
    end += 1
  }
  let (tail, _) = trim_bounds(source, value_end, end)
  if tail < end &&
    !(dialect.inline_comments &&
    tail > value_end &&
    is_comment(source[tail], dialect)) {
    let prefix = text(source, line.start, tail)
    let suffix_lines = scan_lines(prefix)
    let last = suffix_lines[suffix_lines.length() - 1]
    diagnostics.push(
      diagnostic(
        "INI007",
        "unexpected text after multiline value",
        span(
          tail,
          end,
          line.number + last.number - 1,
          tail - line.start - last.start + 1,
        ),
      ),
    )
  }
  if end < source.length() {
    if source[end] == '\r' &&
      end + 1 < source.length() &&
      source[end + 1] == '\n' {
      end += 2
    } else {
      end += 1
    }
  }
  (text(source, start + 3, close), line_span(line, start, value_end), end)
}