///|
/// Core builtins (print/len/range/sum/max/min/any/all/enumerate/zip/map/filter/reversed/sorted).

///|
fn builtin_internal_iter(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = match ensure_no_keywords("__mpython.internal_iter", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() != 1 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "__mpython.internal_iter() takes exactly one argument".to_string(),
      ),
    )
  }
  Ok(positional[0])
}

///|
fn builtin_internal_next(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = match ensure_no_keywords("__mpython.internal_next", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() != 1 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "__mpython.internal_next() takes exactly one argument".to_string(),
      ),
    )
  }
  iterator_next(positional[0], None, globals, builtins, io)
}

///|
fn builtin_print(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let mut sep = " ".to_string()
  let mut end = "\n".to_string()
  let mut file_value = Value::None
  let mut flush = false
  for pair in keywords {
    let key = pair.0
    let value = pair.1
    match key {
      "sep" =>
        match value {
          Value::Str(text) => sep = text
          _ =>
            return Err(
              make_runtime_error(
                RuntimeErrorKind::Type,
                "print() sep must be str".to_string(),
              ),
            )
        }
      "end" =>
        match value {
          Value::Str(text) => end = text
          _ =>
            return Err(
              make_runtime_error(
                RuntimeErrorKind::Type,
                "print() end must be str".to_string(),
              ),
            )
        }
      "file" => file_value = value
      "flush" =>
        match value {
          Value::Bool(v) => flush = v
          _ =>
            return Err(
              make_runtime_error(
                RuntimeErrorKind::Type,
                "print() flush must be bool".to_string(),
              ),
            )
        }
      _ =>
        return Err(
          make_runtime_error(
            RuntimeErrorKind::Type,
            "print() got an unexpected keyword argument '" + key + "'",
          ),
        )
    }
  }
  let buf = StringBuilder()
  for i = 0; i < positional.length(); i = i + 1 {
    if i > 0 {
      buf.write_string(sep)
    }
    buf.write_string(value_to_string(positional[i]))
  }
  buf.write_string(end)
  let text = buf.to_string()
  if file_value is Value::None {
    match module_cache_get("sys") {
      Some(sys_module) =>
        match get_attr_from_value(sys_module, "stdout", globals, builtins, io) {
          Ok(v) => file_value = v
          Err(err) =>
            match err.kind {
              RuntimeErrorKind::Attribute => ()
              _ => return Err(err)
            }
        }
      None => ()
    }
  }
  match file_value {
    Value::None => io.write_stdout(text)
    _ => {
      let write_method = match
        get_attr_from_value(file_value, "write", globals, builtins, io) {
        Ok(v) => v
        Err(err) => return Err(err)
      }
      let _ = match
        call_callable_with_env(
          write_method,
          [Value::Str(text)],
          [],
          globals,
          builtins,
          io,
        ) {
        Ok(v) => v
        Err(err) => return Err(err)
      }
      if flush {
        match get_attr_from_value(file_value, "flush", globals, builtins, io) {
          Ok(flush_method) => {
            let _ = match
              call_callable_with_env(
                flush_method,
                [],
                [],
                globals,
                builtins,
                io,
              ) {
              Ok(v) => v
              Err(err) => return Err(err)
            }
          }
          Err(err) =>
            match err.kind {
              RuntimeErrorKind::Attribute => ()
              _ => return Err(err)
            }
        }
      }
    }
  }
  Ok(Value::None)
}

///|
fn builtin_len(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = match ensure_no_keywords("len", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() != 1 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "len() takes exactly one argument".to_string(),
      ),
    )
  }
  let target = positional[0]
  match target {
    Value::Str(text) => Ok(Value::Int(@bigint.BigInt::from_int(text.length())))
    Value::Bytes(values)
    | Value::ByteArray(values)
    | Value::MemoryView(values) =>
      Ok(Value::Int(@bigint.BigInt::from_int(values.length())))
    Value::List(values) =>
      Ok(Value::Int(@bigint.BigInt::from_int(values.length())))
    Value::Tuple(values) =>
      Ok(Value::Int(@bigint.BigInt::from_int(values.length())))
    Value::Dict(pairs) =>
      Ok(Value::Int(@bigint.BigInt::from_int(pairs.length())))
    Value::Set(values) =>
      Ok(Value::Int(@bigint.BigInt::from_int(values.length())))
    Value::Instance(inst) => {
      let _ = locals
      match get_named_value(inst.dict, list_storage_name) {
        Some(Value::List(values)) =>
          return Ok(Value::Int(@bigint.BigInt::from_int(values.length())))
        _ => ()
      }
      match get_named_value(inst.dict, tuple_storage_name) {
        Some(Value::Tuple(values)) =>
          return Ok(Value::Int(@bigint.BigInt::from_int(values.length())))
        _ => ()
      }
      match get_named_value(inst.dict, dict_storage_name) {
        Some(Value::Dict(pairs)) =>
          return Ok(Value::Int(@bigint.BigInt::from_int(pairs.length())))
        _ => ()
      }
      match get_named_value(inst.dict, set_storage_name) {
        Some(Value::Set(values)) =>
          return Ok(Value::Int(@bigint.BigInt::from_int(values.length())))
        _ => ()
      }
      let len_method = match
        get_attr_from_value(
          Value::Instance(inst),
          "__len__",
          globals,
          builtins,
          io,
        ) {
        Ok(v) => v
        Err(err) =>
          match err.kind {
            RuntimeErrorKind::Attribute =>
              return Err(
                make_runtime_error(
                  RuntimeErrorKind::Type,
                  "object has no len()".to_string(),
                ),
              )
            _ => return Err(err)
          }
      }
      let value = match
        call_callable_with_env(len_method, [], [], globals, builtins, io) {
        Ok(v) => v
        Err(err) => return Err(err)
      }
      match value {
        Value::Int(v) =>
          if v < 0N {
            Err(
              make_runtime_error(
                RuntimeErrorKind::Runtime,
                "ValueError: __len__() should return >= 0".to_string(),
              ),
            )
          } else {
            Ok(Value::Int(v))
          }
        Value::Bool(b) => Ok(Value::Int(if b { 1N } else { 0N }))
        _ =>
          Err(
            make_runtime_error(
              RuntimeErrorKind::Type,
              "__len__() should return an int".to_string(),
            ),
          )
      }
    }
    _ =>
      Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "object has no len()".to_string(),
        ),
      )
  }
}

///|
fn builtin_range(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = match ensure_no_keywords("range", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  fn to_int(value : Value) -> Result[@bigint.BigInt, RuntimeError] {
    match value {
      Value::Int(v) => Ok(v)
      Value::Bool(v) => Ok(if v { 1N } else { 0N })
      _ =>
        Err(
          make_runtime_error(
            RuntimeErrorKind::Type,
            "range() integer argument expected".to_string(),
          ),
        )
    }
  }

  if positional.length() == 0 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "range expected at least 1 argument, got 0".to_string(),
      ),
    )
  }
  if positional.length() > 3 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "range expected at most 3 arguments, got " +
        positional.length().to_string(),
      ),
    )
  }
  let mut start = 0N
  let mut stop = 0N
  let mut step = 1N
  if positional.length() == 1 {
    stop = match to_int(positional[0]) {
      Ok(v) => v
      Err(err) => return Err(err)
    }
  } else if positional.length() == 2 {
    start = match to_int(positional[0]) {
      Ok(v) => v
      Err(err) => return Err(err)
    }
    stop = match to_int(positional[1]) {
      Ok(v) => v
      Err(err) => return Err(err)
    }
  } else {
    start = match to_int(positional[0]) {
      Ok(v) => v
      Err(err) => return Err(err)
    }
    stop = match to_int(positional[1]) {
      Ok(v) => v
      Err(err) => return Err(err)
    }
    step = match to_int(positional[2]) {
      Ok(v) => v
      Err(err) => return Err(err)
    }
  }
  if step == 0N {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Runtime,
        "ValueError: range() arg 3 must not be zero".to_string(),
      ),
    )
  }
  Ok(make_range_instance(start, stop, step, builtins))
}

///|
fn builtin_sum(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = match ensure_no_keywords("sum", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() == 0 || positional.length() > 2 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "sum() takes 1 or 2 arguments".to_string(),
      ),
    )
  }
  let items = match iterable_values(positional[0]) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  let mut is_float = false
  let mut total = 0.0
  if positional.length() == 2 {
    let (start_is_float, start_num) = match number_value(positional[1]) {
      Ok(v) => v
      Err(err) => return Err(err)
    }
    is_float = start_is_float
    total = start_num
  }
  for item in items {
    let (item_is_float, item_num) = match number_value(item) {
      Ok(v) => v
      Err(err) => return Err(err)
    }
    if item_is_float {
      is_float = true
    }
    total = total + item_num
  }
  if is_float {
    Ok(Value::Float(total))
  } else {
    Ok(Value::Int(@bigint.BigInt::from_int64(total.to_int64())))
  }
}

///|
fn builtin_max(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = match ensure_no_keywords("max", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() == 0 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "max() takes at least 1 argument".to_string(),
      ),
    )
  }
  let items = if positional.length() == 1 {
    match iterable_values(positional[0]) {
      Ok(v) => v
      Err(err) => return Err(err)
    }
  } else {
    positional
  }
  if items.length() == 0 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "max() arg is an empty sequence".to_string(),
      ),
    )
  }
  let mut best = items[0]
  match best {
    Value::Str(best_text) => {
      let mut current = best_text
      for i = 1; i < items.length(); i = i + 1 {
        let item = items[i]
        match item {
          Value::Str(text) =>
            if cmp_string_for_sorted(text, current) > 0 {
              best = item
              current = text
            }
          _ =>
            return Err(
              make_runtime_error(
                RuntimeErrorKind::Type,
                "max() expected strings".to_string(),
              ),
            )
        }
      }
    }
    Value::Bytes(best_bytes) => {
      fn cmp_bytes_for_sorted(a : Array[Int32], b : Array[Int32]) -> Int32 {
        let n = if a.length() < b.length() { a.length() } else { b.length() }
        for i = 0; i < n; i = i + 1 {
          let ai = a[i]
          let bi = b[i]
          if ai < bi {
            return -1
          }
          if ai > bi {
            return 1
          }
        }
        if a.length() < b.length() {
          -1
        } else if a.length() > b.length() {
          1
        } else {
          0
        }
      }

      let mut current = best_bytes
      for i = 1; i < items.length(); i = i + 1 {
        let item = items[i]
        match item {
          Value::Bytes(bytes) =>
            if cmp_bytes_for_sorted(bytes, current) > 0 {
              best = item
              current = bytes
            }
          _ =>
            return Err(
              make_runtime_error(
                RuntimeErrorKind::Type,
                "max() expected bytes".to_string(),
              ),
            )
        }
      }
    }
    Value::List(_) | Value::Tuple(_) =>
      for i = 1; i < items.length(); i = i + 1 {
        let item = items[i]
        let cmp = match cmp_value_for_sorted_dynamic(item, best) {
          Ok(v) => v
          Err(err) => return Err(err)
        }
        if cmp > 0 {
          best = item
        }
      }
    _ => {
      let (_, first_num) = match number_value(best) {
        Ok(v) => v
        Err(err) => return Err(err)
      }
      let mut best_num = first_num
      for i = 1; i < items.length(); i = i + 1 {
        let item = items[i]
        let (_, item_num) = match number_value(item) {
          Ok(v) => v
          Err(err) => return Err(err)
        }
        if item_num > best_num {
          best = item
          best_num = item_num
        }
      }
    }
  }
  Ok(best)
}

///|
fn builtin_min(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = match ensure_no_keywords("min", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() == 0 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "min() takes at least 1 argument".to_string(),
      ),
    )
  }
  let items = if positional.length() == 1 {
    match iterable_values(positional[0]) {
      Ok(v) => v
      Err(err) => return Err(err)
    }
  } else {
    positional
  }
  if items.length() == 0 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "min() arg is an empty sequence".to_string(),
      ),
    )
  }
  let mut best = items[0]
  match best {
    Value::Str(best_text) => {
      let mut current = best_text
      for i = 1; i < items.length(); i = i + 1 {
        let item = items[i]
        match item {
          Value::Str(text) =>
            if cmp_string_for_sorted(text, current) < 0 {
              best = item
              current = text
            }
          _ =>
            return Err(
              make_runtime_error(
                RuntimeErrorKind::Type,
                "min() expected strings".to_string(),
              ),
            )
        }
      }
    }
    Value::Bytes(best_bytes) => {
      fn cmp_bytes_for_sorted(a : Array[Int32], b : Array[Int32]) -> Int32 {
        let n = if a.length() < b.length() { a.length() } else { b.length() }
        for i = 0; i < n; i = i + 1 {
          let ai = a[i]
          let bi = b[i]
          if ai < bi {
            return -1
          }
          if ai > bi {
            return 1
          }
        }
        if a.length() < b.length() {
          -1
        } else if a.length() > b.length() {
          1
        } else {
          0
        }
      }

      let mut current = best_bytes
      for i = 1; i < items.length(); i = i + 1 {
        let item = items[i]
        match item {
          Value::Bytes(bytes) =>
            if cmp_bytes_for_sorted(bytes, current) < 0 {
              best = item
              current = bytes
            }
          _ =>
            return Err(
              make_runtime_error(
                RuntimeErrorKind::Type,
                "min() expected bytes".to_string(),
              ),
            )
        }
      }
    }
    Value::List(_) | Value::Tuple(_) =>
      for i = 1; i < items.length(); i = i + 1 {
        let item = items[i]
        let cmp = match cmp_value_for_sorted_dynamic(item, best) {
          Ok(v) => v
          Err(err) => return Err(err)
        }
        if cmp < 0 {
          best = item
        }
      }
    _ => {
      let (_, first_num) = match number_value(best) {
        Ok(v) => v
        Err(err) => return Err(err)
      }
      let mut best_num = first_num
      for i = 1; i < items.length(); i = i + 1 {
        let item = items[i]
        let (_, item_num) = match number_value(item) {
          Ok(v) => v
          Err(err) => return Err(err)
        }
        if item_num < best_num {
          best = item
          best_num = item_num
        }
      }
    }
  }
  Ok(best)
}

///|
fn builtin_any(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = match ensure_no_keywords("any", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() != 1 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "any() takes exactly one argument".to_string(),
      ),
    )
  }
  let items = match iterable_values(positional[0]) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  for item in items {
    if bool_from_value(item) {
      return Ok(Value::Bool(true))
    }
  }
  Ok(Value::Bool(false))
}

///|
fn builtin_all(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = match ensure_no_keywords("all", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() != 1 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "all() takes exactly one argument".to_string(),
      ),
    )
  }
  let items = match iterable_values(positional[0]) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  for item in items {
    if !bool_from_value(item) {
      return Ok(Value::Bool(false))
    }
  }
  Ok(Value::Bool(true))
}

///|
fn builtin_enumerate(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let mut start_kw : Value? = None
  for pair in keywords {
    if pair.0 == "start" {
      start_kw = Some(pair.1)
    } else {
      return Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "enumerate() got an unexpected keyword argument".to_string(),
        ),
      )
    }
  }
  if positional.length() == 0 || positional.length() > 2 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "enumerate() takes 1 or 2 arguments".to_string(),
      ),
    )
  }
  let iterator = match
    iter_value_to_iterator(positional[0], globals, builtins, io) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() == 2 {
    match start_kw {
      Some(_) =>
        return Err(
          make_runtime_error(
            RuntimeErrorKind::Type,
            "enumerate() got multiple values for argument 'start'".to_string(),
          ),
        )
      None => ()
    }
  }
  let mut start = 0N
  let start_value = match start_kw {
    Some(v) => Some(v)
    None => if positional.length() == 2 { Some(positional[1]) } else { None }
  }
  match start_value {
    Some(v) =>
      match v {
        Value::Int(v) => start = v
        Value::Bool(v) => start = if v { 1N } else { 0N }
        _ =>
          return Err(
            make_runtime_error(
              RuntimeErrorKind::Type,
              "enumerate() integer argument expected".to_string(),
            ),
          )
      }
    None => ()
  }
  Ok(make_enumerate_iterator(iterator, start))
}

///|
fn builtin_zip(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let mut strict = false
  for pair in keywords {
    if pair.0 == "strict" {
      match pair.1 {
        Value::Bool(v) => strict = v
        _ =>
          return Err(
            make_runtime_error(
              RuntimeErrorKind::Type,
              "zip() strict must be bool".to_string(),
            ),
          )
      }
    } else {
      return Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "zip() got unexpected keyword arguments".to_string(),
        ),
      )
    }
  }
  let iterators : Array[Value] = []
  for value in positional {
    let it = match iter_value_to_iterator(value, globals, builtins, io) {
      Ok(v) => v
      Err(err) => return Err(err)
    }
    iterators.push(it)
  }
  Ok(make_zip_iterator(iterators, strict))
}

///|
fn builtin_map(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = match ensure_no_keywords("map", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() != 2 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "map() takes exactly two arguments".to_string(),
      ),
    )
  }
  let func = positional[0]
  let items = match iterable_values(positional[1]) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  let results : Array[Value] = []
  for item in items {
    let mapped = match
      call_callable_with_env(func, [item], [], globals, builtins, io) {
      Ok(v) => v
      Err(err) => return Err(err)
    }
    results.push(mapped)
  }
  Ok(make_iterator(results))
}

///|
fn builtin_filter(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = match ensure_no_keywords("filter", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() != 2 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "filter() takes exactly two arguments".to_string(),
      ),
    )
  }
  let predicate = positional[0]
  let items = match iterable_values(positional[1]) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  let results : Array[Value] = []
  for item in items {
    let keep = if predicate is Value::None {
      bool_from_value(item)
    } else {
      let test_value = match
        call_callable_with_env(predicate, [item], [], globals, builtins, io) {
        Ok(v) => v
        Err(err) => return Err(err)
      }
      bool_from_value(test_value)
    }
    if keep {
      results.push(item)
    }
  }
  Ok(make_iterator(results))
}

///|
fn builtin_reversed(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = match ensure_no_keywords("reversed", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() != 1 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "reversed() takes exactly one argument".to_string(),
      ),
    )
  }
  // Prefer sequence fast-paths so `len(reversed(seq))` works (tests rely on it).
  match positional[0] {
    Value::List(values) => Ok(Value::List(reverse_values(values)))
    Value::Tuple(values) => Ok(Value::List(reverse_values(values)))
    Value::Str(text) => {
      let chars = text.to_array()
      let out : Array[Value] = []
      let mut i = chars.length()
      while i > 0 {
        i -= 1
        out.push(Value::Str(chars[i].to_string()))
      }
      Ok(Value::List(out))
    }
    other => {
      // Fallback: consume any iterable and return a reversed list.
      let iterator_value = match
        iter_value_to_iterator(other, globals, builtins, io) {
        Ok(v) => v
        Err(err) => return Err(err)
      }
      let items : Array[Value] = []
      while true {
        match iterator_next(iterator_value, None, globals, builtins, io) {
          Ok(item) => items.push(item)
          Err(err) =>
            if err.exc_type == "StopIteration" {
              break
            } else {
              return Err(err)
            }
        }
      }
      Ok(Value::List(reverse_values(items)))
    }
  }
}

///|
fn builtin_sorted(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  if positional.length() == 0 || positional.length() > 2 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "sorted() takes 1 or 2 arguments".to_string(),
      ),
    )
  }
  let mut reverse = false
  let mut key_value : Value? = None
  for pair in keywords {
    if pair.0 == "key" {
      key_value = Some(pair.1)
    } else if pair.0 == "reverse" {
      match pair.1 {
        Value::Bool(v) => reverse = v
        _ =>
          return Err(
            make_runtime_error(
              RuntimeErrorKind::Type,
              "sorted() reverse must be bool".to_string(),
            ),
          )
      }
    } else {
      return Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "sorted() got unexpected keyword arguments".to_string(),
        ),
      )
    }
  }
  if positional.length() == 2 {
    if key_value is Some(_) {
      return Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "sorted() got multiple values for argument 'key'".to_string(),
        ),
      )
    }
    key_value = Some(positional[1])
  }
  let items = match
    collect_items_from_iterable(positional[0], globals, builtins, io) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if items.length() == 0 {
    return Ok(Value::List([]))
  }
  fn lt_for_sort(
    a : Value,
    b : Value,
    globals : Array[(String, Value)],
    builtins : Array[(String, Value)],
    io : MockIO,
  ) -> Result[Bool, RuntimeError] {
    fn tuple_items_for_sort(v : Value) -> Array[Value]? {
      match v {
        Value::Tuple(items) => Some(items)
        Value::Instance(inst) =>
          match get_named_value(inst.dict, tuple_storage_name) {
            Some(Value::Tuple(items)) => Some(items)
            _ => None
          }
        _ => None
      }
    }

    match (a, b) {
      (Value::Str(x), Value::Str(y)) => Ok(cmp_string_for_sorted(x, y) < 0)
      (
        Value::Bytes(x)
        | Value::ByteArray(x)
        | Value::MemoryView(x),
        Value::Bytes(y)
        | Value::ByteArray(y)
        | Value::MemoryView(y),
      ) => Ok(compare_bytes_like(x, y) < 0)
      _ =>
        match (tuple_items_for_sort(a), tuple_items_for_sort(b)) {
          (Some(xs), Some(ys)) =>
            match compare_sequence_values(xs, ys, globals, builtins, io) {
              Ok(cmp) => Ok(cmp < 0)
              Err(err) => Err(err)
            }
          _ =>
            match (a, b) {
              (Value::List(xs), Value::List(ys)) =>
                match compare_sequence_values(xs, ys, globals, builtins, io) {
                  Ok(cmp) => Ok(cmp < 0)
                  Err(err) => Err(err)
                }
              (Value::Int(_) | Value::Float(_) | Value::Bool(_), _)
              | (_, Value::Int(_) | Value::Float(_) | Value::Bool(_)) => {
                let (_, left_num) = match number_value(a) {
                  Ok(v) => v
                  Err(_) =>
                    return Err(
                      make_runtime_error(
                        RuntimeErrorKind::Type,
                        "sorted() cannot compare values".to_string(),
                      ),
                    )
                }
                let (_, right_num) = match number_value(b) {
                  Ok(v) => v
                  Err(_) =>
                    return Err(
                      make_runtime_error(
                        RuntimeErrorKind::Type,
                        "sorted() cannot compare values".to_string(),
                      ),
                    )
                }
                Ok(left_num < right_num)
              }
              _ =>
                ordering_bool(
                  a, b, "__lt__", "__gt__", "<", globals, builtins, io,
                )
            }
        }
    }
  }

  let mut out : Array[Value] = []
  match key_value {
    Some(key_fn) => {
      let key_pairs : Array[(Value, Value)] = []
      for item in items {
        let key = if key_fn is Value::None {
          item
        } else {
          match
            call_callable_with_env(key_fn, [item], [], globals, builtins, io) {
            Ok(value) => value
            Err(err) => return Err(err)
          }
        }
        key_pairs.push((key, item))
      }
      let mut kind_opt : SortKind? = None
      let mut can_use_fast = true
      for pair in key_pairs {
        match sort_kind_for_sorted(pair.0) {
          Some(kind) =>
            match kind_opt {
              None => kind_opt = Some(kind)
              Some(prev) => if prev != kind { can_use_fast = false }
            }
          None => can_use_fast = false
        }
        // Only use the fast comparator for simple scalar keys. For tuples/lists,
        // fall back to rich comparisons so wrappers like pprint._safe_key work.
        match kind_opt {
          Some(SortKind::Tuple) | Some(SortKind::List) => can_use_fast = false
          _ => ()
        }
      }
      let sorted_pairs = if can_use_fast {
        let kind = match kind_opt {
          Some(kind) => kind
          None => return Ok(Value::List([]))
        }
        match stable_sort_pairs_by_key(key_pairs, kind) {
          Ok(v) => v
          Err(err) => return Err(err)
        }
      } else {
        // Stable insertion sort using __lt__ fallback.
        let out_pairs : Array[(Value, Value)] = []
        for pair in key_pairs {
          out_pairs.push(pair)
        }
        let mut i = 1
        while i < out_pairs.length() {
          let pair = out_pairs[i]
          let mut j = i
          while j > 0 {
            let less = match
              lt_for_sort(pair.0, out_pairs[j - 1].0, globals, builtins, io) {
              Ok(v) => v
              Err(err) => return Err(err)
            }
            if !less {
              break
            }
            out_pairs[j] = out_pairs[j - 1]
            j -= 1
          }
          out_pairs[j] = pair
          i += 1
        }
        out_pairs
      }
      for pair in sorted_pairs {
        out.push(pair.1)
      }
    }
    None => {
      let mut kind_opt : SortKind? = None
      let mut can_use_fast = true
      for item in items {
        match sort_kind_for_sorted(item) {
          Some(kind) =>
            match kind_opt {
              None => kind_opt = Some(kind)
              Some(prev) => if prev != kind { can_use_fast = false }
            }
          None => can_use_fast = false
        }
        match kind_opt {
          Some(SortKind::Tuple) | Some(SortKind::List) => can_use_fast = false
          _ => ()
        }
      }
      out = if can_use_fast {
        let kind = match kind_opt {
          Some(kind) => kind
          None => return Ok(Value::List([]))
        }
        match stable_sort_for_sorted(items, kind) {
          Ok(v) => v
          Err(err) => return Err(err)
        }
      } else {
        // Stable insertion sort using __lt__ fallback.
        let out : Array[Value] = []
        for item in items {
          out.push(item)
        }
        let mut i = 1
        while i < out.length() {
          let item = out[i]
          let mut j = i
          while j > 0 {
            let less = match
              lt_for_sort(item, out[j - 1], globals, builtins, io) {
              Ok(v) => v
              Err(err) => return Err(err)
            }
            if !less {
              break
            }
            out[j] = out[j - 1]
            j -= 1
          }
          out[j] = item
          i += 1
        }
        out
      }
    }
  }
  if reverse {
    out = reverse_values(out)
  }
  Ok(Value::List(out))
}

///|
fn builtin_sys_exit(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = match ensure_no_keywords("sys.exit", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() > 1 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "exit() takes at most 1 argument".to_string(),
      ),
    )
  }
  let args : Array[Value] = []
  if positional.length() == 1 {
    args.push(positional[0])
  }
  let klass = match get_named_value(builtins, "SystemExit") {
    Some(Value::Class(klass)) => klass
    _ =>
      return Err(
        make_runtime_error(
          RuntimeErrorKind::Runtime,
          "RuntimeError: SystemExit is not available".to_string(),
        ),
      )
  }
  let inst_value = match
    call_callable_with_env(Value::Class(klass), args, [], globals, builtins, io) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  match inst_value {
    Value::Instance(inst) => Err(runtime_error_from_exception_instance(inst))
    _ =>
      Err(
        make_runtime_error(
          RuntimeErrorKind::Runtime,
          "RuntimeError: invalid SystemExit".to_string(),
        ),
      )
  }
}

///|
fn builtin_sys_audit(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  touch_env(locals, globals, builtins, io)
  let _ = match ensure_no_keywords("sys.audit", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() < 1 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "sys.audit() missing required argument".to_string(),
      ),
    )
  }
  match positional[0] {
    Value::Str(_) => Ok(Value::None)
    _ =>
      Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "sys.audit() event must be str".to_string(),
        ),
      )
  }
}