///|
fn parse_rule(line0 : String) -> IgnoreRule? {
  let line1 = trim_bom(line0)
  let line = normalize_trailing_spaces(line1)

  if line.trim(char_set=" \t").is_empty() {
    return None
  }
  if has_odd_trailing_backslash(line) {
    return None
  }
  if line.has_prefix("#") {
    return None
  }

  let mut pattern = if line.has_prefix("\\#") {
    line[1:].to_owned()
  } else {
    line
  }

  let mut negative = false
  if pattern.has_prefix("\\!") {
    pattern = pattern[1:].to_owned()
  } else if pattern.has_prefix("!") {
    negative = true
    pattern = pattern[1:].to_owned()
  }

  if pattern.is_empty() {
    return None
  }

  let mut anchored = false
  if pattern.has_prefix("/") {
    anchored = true
    pattern = pattern[1:].to_owned()
  }

  let mut directory_only = false
  if ends_with_slash(pattern) {
    directory_only = true
    pattern = pattern[:pattern.length() - 1].to_owned()
  }

  let has_slash = pattern.find("/") is Some(_)
  let segments = path_to_segments(pattern)
  if segments.is_empty() {
    return None
  }

  Some({
    raw: line,
    pattern,
    negative,
    directory_only,
    anchored,
    has_slash,
    segments,
  })
}

///|
fn split_lines(text : String) -> Array[String] {
  let lines = []
  let buf = StringBuilder(size_hint=text.length())

  for i in 0.. String {
  if line.is_empty() {
    return line
  }
  if line.code_unit_at(0) == 0xFEFF {
    return line[1:].to_owned()
  }
  line
}

///|
fn normalize_trailing_spaces(line : String) -> String {
  if line.is_empty() {
    return line
  }

  let mut end_ = line.length()
  while end_ > 0 && line.code_unit_at(end_ - 1) == ' '.to_int().to_uint16() {
    end_ = end_ - 1
  }
  if end_ == line.length() {
    return line
  }
  if end_ == 0 {
    return ""
  }

  let mut slash_count = 0
  let mut idx = end_ - 1
  while true {
    if line.code_unit_at(idx) == '\\'.to_int().to_uint16() {
      slash_count = slash_count + 1
      if idx == 0 {
        break
      }
      idx = idx - 1
      continue
    }
    break
  }

  if slash_count % 2 == 1 {
    "\{line[:end_ - 1]} "
  } else {
    line[:end_].to_owned()
  }
}

///|
fn has_odd_trailing_backslash(line : String) -> Bool {
  if line.is_empty() {
    return false
  }
  if line.code_unit_at(line.length() - 1) != '\\'.to_int().to_uint16() {
    return false
  }

  let mut i = line.length() - 1
  let mut count = 0
  while true {
    if line.code_unit_at(i) == '\\'.to_int().to_uint16() {
      count = count + 1
      if i == 0 {
        break
      }
      i = i - 1
      continue
    }
    break
  }
  count % 2 == 1
}