///|
/// Evaluate Explode
fn eval_explode(input : Json) -> Iter[Json] raise InterpreterError {
  guard input is String(s) else {
    raise TypeMismatch("string", @ast_internal.json_type_name(input))
  }
  let results = s
    .iter()
    .map(fn(char) { Json::number(char.to_int().to_double()) })
    .to_array()
  Iter::singleton(Json::array(results))
}

///|
/// Evaluate Implode
fn eval_implode(input : Json) -> Iter[Json] raise InterpreterError {
  guard input is Array(arr) else {
    raise TypeMismatch("array", @ast_internal.json_type_name(input))
  }
  let chars = arr.filter_map(fn(elem) {
    match elem {
      Number(n, ..) => Int::to_char(n.to_int())
      _ => None
    }
  })
  let result = String::from_array(chars)
  Iter::singleton(Json::string(result))
}

///|
/// Evaluate Scan
fn eval_scan(
  pattern : String,
  input : Json,
) -> Iter[Json] raise InterpreterError {
  guard input is String(s) else {
    raise TypeMismatch("string", @ast_internal.json_type_name(input))
  }
  let results : Array[Json] = []
  scan_matches(s, pattern, 0, s.length(), pattern.length(), results)
  results.iter()
}

///|
fn scan_matches(
  s : String,
  pattern : String,
  idx : Int,
  slen : Int,
  plen : Int,
  results : Array[Json],
) -> Unit {
  if plen == 0 {
    return
  }
  let mut pos = idx
  let last = slen - plen
  while pos <= last {
    if is_string_boundary(s, pos) &&
      is_string_boundary(s, pos + plen) &&
      s[pos:pos + plen].to_owned() == pattern {
      results.push(Json::string(pattern))
      pos += plen
    } else {
      pos = next_string_index(s, pos)
    }
  }
}

///|
fn is_string_boundary(s : String, index : Int) -> Bool {
  index == 0 ||
  index == s.length() ||
  !s.unsafe_get(index).is_trailing_surrogate()
}

///|
fn next_string_index(s : String, index : Int) -> Int {
  if index < s.length() && s.unsafe_get(index).is_leading_surrogate() {
    index + 2
  } else {
    index + 1
  }
}