///|
fn ascii_upper(s : String) -> String {
  let b = StringBuilder::new()
  let mut i = 0
  while i < s.length() {
    b.write_char(s[i].unsafe_to_char().to_ascii_uppercase())
    i += 1
  }
  b.to_string()
}

///|
fn trim_ascii(s : String) -> String {
  let mut start = 0
  let mut end = s.length()
  while start < end && is_space_code(s[start]) {
    start += 1
  }
  while end > start && is_space_code(s[end - 1]) {
    end -= 1
  }
  s[start:end].to_owned()
}

///|
fn is_space_code(code : UInt16) -> Bool {
  code == 32 || code == 9 || code == 10 || code == 13
}

///|
fn split_by_char(s : String, sep : UInt16) -> Array[String] {
  let out : Array[String] = []
  let mut start = 0
  let mut i = 0
  while i < s.length() {
    if s[i] == sep {
      out.push(s[start:i].to_owned())
      start = i + 1
    }
    i += 1
  }
  out.push(s[start:s.length()].to_owned())
  out
}

///|
/// Split a parameter head/value without treating separators inside quotes as
/// delimiters. Property values themselves intentionally use split_by_char.
fn split_quoted(s : String, sep : UInt16) -> Array[String] {
  let out : Array[String] = []
  let mut start = 0
  let mut quoted = false
  let mut escaped = false
  let mut i = 0
  while i < s.length() {
    let code = s[i]
    if escaped {
      escaped = false
    } else if code == 92 {
      escaped = true
    } else if code == 34 {
      quoted = !quoted
    } else if code == sep && !quoted {
      out.push(s[start:i].to_owned())
      start = i + 1
    }
    i += 1
  }
  out.push(s[start:s.length()].to_owned())
  out
}

///|
fn index_of_char(s : String, sep : UInt16) -> Int {
  let mut i = 0
  while i < s.length() {
    if s[i] == sep {
      return i
    }
    i += 1
  }
  -1
}

///|
fn valid_token(s : String) -> Bool {
  if s.length() == 0 {
    return false
  }
  let mut i = 0
  while i < s.length() {
    let c = s[i]
    let upper = c >= 65 && c <= 90
    let lower = c >= 97 && c <= 122
    let digit = c >= 48 && c <= 57
    if !(upper || lower || digit || c == 45) {
      return false
    }
    i += 1
  }
  true
}

///|
fn split_physical_lines(input : String) -> Array[String] {
  let lines : Array[String] = []
  let mut start = 0
  let mut i = 0
  while i < input.length() {
    if input[i] == 10 {
      let mut end = i
      if end > start && input[end - 1] == 13 {
        end -= 1
      }
      lines.push(input[start:end].to_owned())
      start = i + 1
    }
    i += 1
  }
  if start < input.length() {
    let mut end = input.length()
    if end > start && input[end - 1] == 13 {
      end -= 1
    }
    lines.push(input[start:end].to_owned())
  }
  lines
}

///|
fn unfold_lines(input : String) -> Array[String] raise CalError {
  let physical = split_physical_lines(input)
  let logical : Array[String] = []
  let mut physical_line = 1
  for line in physical {
    if line.length() > 0 && (line[0] == 32 || line[0] == 9) {
      if logical.length() == 0 {
        raise BadPropertyName(physical_line, line)
      } else {
        let last = logical.length() - 1
        logical[last] = logical[last] + line[1:line.length()].to_owned()
      }
    } else if line.length() > 0 {
      logical.push(line)
    }
    physical_line += 1
  }
  logical
}

///|
fn escape_text(s : String) -> String {
  let b = StringBuilder::new()
  let mut i = 0
  while i < s.length() {
    let c = s[i]
    if c == 92 {
      b.write_string("\\\\")
    } else if c == 44 {
      b.write_string("\\,")
    } else if c == 59 {
      b.write_string("\\;")
    } else if c == 10 {
      b.write_string("\\n")
    } else {
      b.write_char(c.unsafe_to_char())
    }
    i += 1
  }
  b.to_string()
}

///|
fn unescape_text(s : String) -> String {
  let b = StringBuilder::new()
  let mut i = 0
  while i < s.length() {
    if s[i] == 92 && i + 1 < s.length() {
      let n = s[i + 1]
      if n == 110 || n == 78 {
        b.write_char('\n')
      } else {
        b.write_char(n.unsafe_to_char())
      }
      i += 2
    } else {
      b.write_char(s[i].unsafe_to_char())
      i += 1
    }
  }
  b.to_string()
}