///|
fn encoder_ascii_is_vowel(code : UInt16) -> Bool {
  code == 65 ||
  code == 69 ||
  code == 73 ||
  code == 79 ||
  code == 85 ||
  code == 89
}

///|
fn ascii_starts_with_at(input : String, index : Int, pattern : String) -> Bool {
  if index < 0 || index + pattern.length() > input.length() {
    return false
  }
  for i = 0; i < pattern.length(); i = i + 1 {
    if input[index + i] != pattern[i] {
      return false
    }
  }
  true
}

///|
fn ascii_ends_with(input : String, suffix : String) -> Bool {
  ascii_starts_with_at(input, input.length() - suffix.length(), suffix)
}

///|
fn ascii_slice(input : String, start : Int, end : Int) -> String {
  let from = if start < 0 {
    0
  } else if start > input.length() {
    input.length()
  } else {
    start
  }
  let until = if end < from {
    from
  } else if end > input.length() {
    input.length()
  } else {
    end
  }
  let mut output = ""
  for i = from; i < until; i = i + 1 {
    output = output + input[i].unsafe_to_char().to_string()
  }
  output
}

///|
fn append_bounded(output : String, fragment : String, maximum : Int) -> String {
  if maximum <= 0 {
    return ""
  }
  let mut bounded = ascii_slice(output, 0, maximum)
  for i = 0; i < fragment.length() && bounded.length() < maximum; i = i + 1 {
    bounded = bounded + fragment[i].unsafe_to_char().to_string()
  }
  bounded
}

///|
priv struct RuleCursor {
  input : String
  index : Int
  output : String
  maximum : Int
}

///|
fn RuleCursor::new(input : String, maximum : Int) -> RuleCursor {
  { input, index: 0, output: "", maximum }
}

///|
fn RuleCursor::remaining(self : RuleCursor) -> Int {
  self.input.length() - self.index
}

///|
fn RuleCursor::peek(self : RuleCursor, offset : Int) -> UInt16 {
  let position = self.index + offset
  if position < 0 || position >= self.input.length() {
    0
  } else {
    self.input[position]
  }
}

///|
fn RuleCursor::advance(self : RuleCursor, amount : Int) -> RuleCursor {
  let requested = if amount < 0 { self.index } else { self.index + amount }
  let next_index = if requested > self.input.length() {
    self.input.length()
  } else {
    requested
  }
  {
    input: self.input,
    index: next_index,
    output: self.output,
    maximum: self.maximum,
  }
}

///|
fn RuleCursor::append(self : RuleCursor, fragment : String) -> RuleCursor {
  {
    input: self.input,
    index: self.index,
    output: append_bounded(self.output, fragment, self.maximum),
    maximum: self.maximum,
  }
}