///|
/// Numeric builtins (int/float/complex + abs/round/pow/divmod/chr/ord/bin/oct/hex).

///|
fn int_parse_digit(ch : Char) -> Int32? {
  if ch.is_ascii_digit() {
    Some(ch.to_int() - '0'.to_int())
  } else if ch.is_ascii_lowercase() {
    Some(ch.to_int() - 'a'.to_int() + 10)
  } else if ch.is_ascii_uppercase() {
    Some(ch.to_int() - 'A'.to_int() + 10)
  } else {
    None
  }
}

///|
fn int_text_from_value(value : Value) -> Result[String, RuntimeError] {
  match value {
    Value::Str(text) => Ok(text)
    Value::Bytes(values)
    | Value::ByteArray(values)
    | Value::MemoryView(values) => {
      let chars : Array[Char] = []
      for byte in values {
        chars.push(byte.unsafe_to_char())
      }
      Ok(String::from_array(chars))
    }
    _ =>
      Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "int() argument must be a string or a number".to_string(),
        ),
      )
  }
}

///|
fn parse_bigint_from_string(
  text : String,
  base_in : Int32,
) -> Result[@bigint.BigInt, RuntimeError] {
  let trimmed = text.trim().to_owned()
  let chars = trimmed.to_array()
  if chars.length() == 0 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Runtime,
        "ValueError: invalid literal for int()".to_string(),
      ),
    )
  }
  let mut idx = 0
  let mut negative = false
  if chars[idx] == '-' {
    negative = true
    idx = idx + 1
  } else if chars[idx] == '+' {
    idx = idx + 1
  }
  let mut base = base_in
  if base == 0 {
    if idx + 1 < chars.length() && chars[idx] == '0' {
      match chars[idx + 1] {
        'x' | 'X' => {
          base = 16
          idx = idx + 2
        }
        'o' | 'O' => {
          base = 8
          idx = idx + 2
        }
        'b' | 'B' => {
          base = 2
          idx = idx + 2
        }
        _ => base = 10
      }
    } else {
      base = 10
    }
  } else {
    if base < 2 || base > 36 {
      return Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "int() base must be >= 2 and <= 36".to_string(),
        ),
      )
    }
    if idx + 1 < chars.length() && chars[idx] == '0' {
      if base == 16 && (chars[idx + 1] == 'x' || chars[idx + 1] == 'X') {
        idx = idx + 2
      } else if base == 8 && (chars[idx + 1] == 'o' || chars[idx + 1] == 'O') {
        idx = idx + 2
      } else if base == 2 && (chars[idx + 1] == 'b' || chars[idx + 1] == 'B') {
        idx = idx + 2
      }
    }
  }
  if base < 2 || base > 36 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "int() base must be >= 2 and <= 36".to_string(),
      ),
    )
  }
  let base_big = @bigint.BigInt::from_int(base)
  let mut acc = 0N
  let mut saw_digit = false
  while idx < chars.length() {
    let ch = chars[idx]
    idx = idx + 1
    if ch == '_' {
      continue
    }
    let digit = match int_parse_digit(ch) {
      Some(v) => v
      None =>
        return Err(
          make_runtime_error(
            RuntimeErrorKind::Runtime,
            "ValueError: invalid literal for int()".to_string(),
          ),
        )
    }
    if digit >= base {
      return Err(
        make_runtime_error(
          RuntimeErrorKind::Runtime,
          "ValueError: invalid literal for int()".to_string(),
        ),
      )
    }
    acc = acc * base_big + @bigint.BigInt::from_int(digit)
    saw_digit = true
  }
  if !saw_digit {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Runtime,
        "ValueError: invalid literal for int()".to_string(),
      ),
    )
  }
  if negative {
    acc = -acc
  }
  Ok(acc)
}

///|
fn builtin_int(
  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() > 2 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "int() takes at most 2 arguments".to_string(),
      ),
    )
  }
  let mut x_value : Value? = None
  let mut base_value : Value? = None
  for pair in keywords {
    if pair.0 == "x" {
      x_value = Some(pair.1)
    } else if pair.0 == "base" {
      base_value = Some(pair.1)
    } else {
      return Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "int() got unexpected keyword argument".to_string(),
        ),
      )
    }
  }
  if positional.length() >= 1 {
    if x_value is Some(_) {
      return Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "int() got multiple values for argument 'x'".to_string(),
        ),
      )
    }
    x_value = Some(positional[0])
  }
  if positional.length() >= 2 {
    if base_value is Some(_) {
      return Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "int() got multiple values for argument 'base'".to_string(),
        ),
      )
    }
    base_value = Some(positional[1])
  }
  match x_value {
    None => Ok(Value::Int(0N))
    Some(x) => {
      if base_value is Some(base_arg) {
        let base_int = match base_arg {
          Value::Int(v) =>
            match bigint_to_int_checked(v) {
              Ok(value) => value
              Err(err) => return Err(err)
            }
          Value::Bool(v) => if v { 1 } else { 0 }
          _ =>
            return Err(
              make_runtime_error(
                RuntimeErrorKind::Type,
                "int() base must be an integer".to_string(),
              ),
            )
        }
        let text = match int_text_from_value(positional[0]) {
          Ok(v) => v
          Err(_) =>
            return Err(
              make_runtime_error(
                RuntimeErrorKind::Type,
                "int() can't convert non-string with explicit base".to_string(),
              ),
            )
        }
        let parsed = match parse_bigint_from_string(text, base_int) {
          Ok(v) => v
          Err(err) => return Err(err)
        }
        return Ok(Value::Int(parsed))
      }
      match x {
        Value::Instance(_) => {
          let int_method = match
            get_attr_from_value(x, "__int__", globals, builtins, io) {
            Ok(v) => v
            Err(_) =>
              return Err(
                make_runtime_error(
                  RuntimeErrorKind::Type,
                  "int() argument must be a string or a number".to_string(),
                ),
              )
          }
          let value = match
            call_callable_with_env(int_method, [], [], globals, builtins, io) {
            Ok(v) => v
            Err(err) => return Err(err)
          }
          match value {
            Value::Int(v) => Ok(Value::Int(v))
            Value::Bool(v) => Ok(Value::Int(if v { 1N } else { 0N }))
            _ =>
              Err(
                make_runtime_error(
                  RuntimeErrorKind::Type,
                  "int() argument must be a string or a number".to_string(),
                ),
              )
          }
        }
        Value::Int(v) => Ok(Value::Int(v))
        Value::Bool(v) => Ok(Value::Int(if v { 1N } else { 0N }))
        Value::Float(v) =>
          Ok(Value::Int(@bigint.BigInt::from_int64(v.to_int64())))
        Value::Str(text) => {
          let parsed = match parse_bigint_from_string(text, 10) {
            Ok(v) => v
            Err(err) => return Err(err)
          }
          Ok(Value::Int(parsed))
        }
        Value::Bytes(_) | Value::ByteArray(_) | Value::MemoryView(_) => {
          let text = match int_text_from_value(x) {
            Ok(v) => v
            Err(err) => return Err(err)
          }
          let parsed = match parse_bigint_from_string(text, 10) {
            Ok(v) => v
            Err(err) => return Err(err)
          }
          Ok(Value::Int(parsed))
        }
        _ =>
          Err(
            make_runtime_error(
              RuntimeErrorKind::Type,
              "int() argument must be a string or a number".to_string(),
            ),
          )
      }
    }
  }
}

///|
fn builtin_int_new(
  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("int.__new__", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() == 0 || positional.length() > 2 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "int.__new__() takes 1 or 2 arguments".to_string(),
      ),
    )
  }
  let cls = match positional[0] {
    Value::Class(klass) => klass
    _ =>
      return Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "int.__new__() expects a type as the first argument".to_string(),
        ),
      )
  }
  if cls.name == "bool" {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "int.__new__(bool) is not safe, use bool.__new__()".to_string(),
      ),
    )
  }
  if !class_has_base_name(cls, "int") {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "int.__new__() expects an int subtype as the first argument".to_string(),
      ),
    )
  }
  let value = if positional.length() == 1 {
    0N
  } else {
    match positional[1] {
      Value::Int(v) => v
      Value::Bool(v) => if v { 1N } else { 0N }
      Value::Float(v) => @bigint.BigInt::from_int64(v.to_int64())
      Value::Str(text) => {
        let parsed = @string.parse_int(text) catch {
          _ =>
            return Err(
              make_runtime_error(
                RuntimeErrorKind::Runtime,
                "ValueError: invalid literal for int()".to_string(),
              ),
            )
        }
        @bigint.BigInt::from_int(parsed)
      }
      _ =>
        return Err(
          make_runtime_error(
            RuntimeErrorKind::Type,
            "int.__new__() argument must be a string or a number".to_string(),
          ),
        )
    }
  }
  if cls.name == "int" ||
    class_has_base_name(cls, "Enum") ||
    class_has_base_name(cls, "Flag") ||
    class_has_base_name(cls, "IntFlag") ||
    class_has_base_name(cls, "StrEnum") {
    return Ok(Value::Int(value))
  }
  Ok(
    Value::Instance(InstanceValue::{
      class: cls,
      dict: [(int_storage_name, Value::Int(value))],
    }),
  )
}

///|
fn int_value_for_builtin(
  name : String,
  value : Value,
) -> Result[@bigint.BigInt, RuntimeError] {
  match value {
    Value::Int(v) => Ok(v)
    Value::Bool(v) => Ok(if v { 1N } else { 0N })
    Value::Instance(inst) =>
      match get_named_value(inst.dict, int_storage_name) {
        Some(Value::Int(v)) => Ok(v)
        Some(Value::Bool(v)) => Ok(if v { 1N } else { 0N })
        _ =>
          Err(
            make_runtime_error(
              RuntimeErrorKind::Type,
              name + " expects an int".to_string(),
            ),
          )
      }
    _ =>
      Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          name + " expects an int".to_string(),
        ),
      )
  }
}

///|
fn builtin_int_bool(
  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("int.__bool__", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() != 1 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "int.__bool__() takes exactly one argument".to_string(),
      ),
    )
  }
  let n = match int_value_for_builtin("int.__bool__()", positional[0]) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  Ok(Value::Bool(n != 0N))
}

///|
fn builtin_int_repr(
  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("int.__repr__", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() != 1 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "int.__repr__() takes exactly one argument".to_string(),
      ),
    )
  }
  let n = match int_value_for_builtin("int.__repr__()", positional[0]) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  Ok(Value::Str(n.to_string()))
}

///|
fn builtin_int_index(
  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("int.__index__", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() != 1 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "int.__index__() takes exactly one argument".to_string(),
      ),
    )
  }
  let n = match int_value_for_builtin("int.__index__()", positional[0]) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  Ok(Value::Int(n))
}

///|
fn builtin_int_from_bytes(
  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 signed = false
  for item in keywords {
    if item.0 == "signed" {
      signed = bool_from_value(item.1)
    } else {
      return Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "int.from_bytes() got unexpected keyword argument".to_string(),
        ),
      )
    }
  }
  if positional.length() < 2 || positional.length() > 4 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "int.from_bytes() takes 1 or 2 arguments".to_string(),
      ),
    )
  }
  if positional.length() == 4 {
    signed = bool_from_value(positional[3])
  }
  let bytes = match positional[1] {
    Value::Bytes(values)
    | Value::ByteArray(values)
    | Value::MemoryView(values) => values
    other =>
      match
        bytes_from_iterable("int.from_bytes", other, globals, builtins, io) {
        Ok(value) => value
        Err(err) => return Err(err)
      }
  }
  let byteorder = if positional.length() >= 3 {
    match positional[2] {
      Value::Str(text) => text
      _ =>
        return Err(
          make_runtime_error(
            RuntimeErrorKind::Type,
            "int.from_bytes() byteorder must be str".to_string(),
          ),
        )
    }
  } else {
    "big".to_string()
  }
  let mut value = 0N
  if byteorder == "big" {
    for b in bytes {
      value = value * 256 + @bigint.BigInt::from_int(b)
    }
  } else if byteorder == "little" {
    let mut factor = 1N
    for b in bytes {
      value = value + @bigint.BigInt::from_int(b) * factor
      factor = factor * 256
    }
  } else {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "int.from_bytes() byteorder must be 'little' or 'big'".to_string(),
      ),
    )
  }
  if signed && bytes.length() > 0 {
    let sign_byte = if byteorder == "big" {
      bytes[0]
    } else {
      bytes[bytes.length() - 1]
    }
    if (sign_byte & 0x80) != 0 {
      let mut modulus = 1N
      let mut i = 0
      while i < bytes.length() {
        modulus = modulus * 256
        i += 1
      }
      value = value - modulus
    }
  }
  Ok(Value::Int(value))
}

///|
fn builtin_bool_from_bytes(
  positional : Array[Value],
  keywords : Array[(String, Value)],
  locals : Array[(String, Value)],
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value, RuntimeError] {
  let value = match
    builtin_int_from_bytes(positional, keywords, locals, globals, builtins, io) {
    Ok(Value::Int(v)) => v
    Ok(Value::Bool(v)) => if v { 1N } else { 0N }
    Ok(_) =>
      return Err(
        make_runtime_error(
          RuntimeErrorKind::Runtime,
          "bool.from_bytes() internal error".to_string(),
        ),
      )
    Err(err) => return Err(err)
  }
  Ok(Value::Bool(value != 0N))
}

///|
fn builtin_bool_new(
  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("bool.__new__", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() < 1 || positional.length() > 2 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "bool.__new__() takes 1 or 2 arguments".to_string(),
      ),
    )
  }
  match positional[0] {
    Value::Class(cls) =>
      if cls.name != "bool" {
        return Err(
          make_runtime_error(
            RuntimeErrorKind::Type,
            "bool.__new__() expects bool as the first argument".to_string(),
          ),
        )
      }
    _ =>
      return Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "bool.__new__() expects bool as the first argument".to_string(),
        ),
      )
  }
  if positional.length() == 1 {
    return Ok(Value::Bool(false))
  }
  let arg = positional[1]
  let truthy = match truthy_from_value_with_env(arg, globals, builtins, io) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  Ok(Value::Bool(truthy))
}

///|
fn builtin_int_to_bytes(
  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 signed = false
  for item in keywords {
    if item.0 == "signed" {
      signed = bool_from_value(item.1)
    } else {
      return Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "int.to_bytes() got unexpected keyword argument".to_string(),
        ),
      )
    }
  }
  if positional.length() < 2 || positional.length() > 4 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "int.to_bytes() takes 1 or 2 arguments".to_string(),
      ),
    )
  }
  if positional.length() == 4 {
    signed = bool_from_value(positional[3])
  }
  let value = match positional[0] {
    Value::Int(v) => v
    Value::Bool(v) => if v { 1N } else { 0N }
    _ =>
      return Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "int.to_bytes() expects an int".to_string(),
        ),
      )
  }
  let length_val = match positional[1] {
    Value::Int(v) => v
    Value::Bool(v) => if v { 1N } else { 0N }
    _ =>
      return Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "int.to_bytes() length must be int".to_string(),
        ),
      )
  }
  let length = match bigint_to_int_checked(length_val) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if length < 0 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Runtime,
        "ValueError: length argument must be non-negative".to_string(),
      ),
    )
  }
  let byteorder = if positional.length() >= 3 {
    match positional[2] {
      Value::Str(text) => text
      _ =>
        return Err(
          make_runtime_error(
            RuntimeErrorKind::Type,
            "int.to_bytes() byteorder must be str".to_string(),
          ),
        )
    }
  } else {
    "big".to_string()
  }
  if byteorder != "big" && byteorder != "little" {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "int.to_bytes() byteorder must be 'little' or 'big'".to_string(),
      ),
    )
  }
  let mut modulus = 1N
  let mut i = 0
  while i < length {
    modulus = modulus * 256
    i += 1
  }
  let mut unsigned_value = value
  if signed {
    let limit = modulus / 2
    if value < -limit || value >= limit {
      return Err(
        make_runtime_error(
          RuntimeErrorKind::Runtime,
          "OverflowError: int too big to convert".to_string(),
        ),
      )
    }
    if value < 0N {
      unsigned_value = value + modulus
    }
  } else {
    if value < 0N {
      return Err(
        make_runtime_error(
          RuntimeErrorKind::Runtime,
          "OverflowError: can't convert negative int to unsigned".to_string(),
        ),
      )
    }
    if value >= modulus {
      return Err(
        make_runtime_error(
          RuntimeErrorKind::Runtime,
          "OverflowError: int too big to convert".to_string(),
        ),
      )
    }
  }
  let out : Array[Int32] = []
  let mut v = unsigned_value
  i = 0
  while i < length {
    let byte_val = v % 256N
    let byte_int = match bigint_to_int_checked(byte_val) {
      Ok(value) => value
      Err(err) => return Err(err)
    }
    out.push(byte_int)
    v = v / 256N
    i += 1
  }
  if byteorder == "big" {
    let ordered : Array[Int32] = []
    let mut j = out.length() - 1
    while j >= 0 {
      ordered.push(out[j])
      j -= 1
    }
    return Ok(Value::Bytes(ordered))
  }
  Ok(Value::Bytes(out))
}

///|
fn builtin_int_bit_length(
  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("int.bit_length", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() != 1 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "int.bit_length() takes exactly one argument".to_string(),
      ),
    )
  }
  let mut value = match positional[0] {
    Value::Int(v) => v
    Value::Bool(v) => if v { 1N } else { 0N }
    _ =>
      return Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "int.bit_length() expects an int".to_string(),
        ),
      )
  }
  if value < 0N {
    value = -value
  }
  if value == 0N {
    return Ok(Value::Int(0N))
  }
  let text = bigint_to_base_string(value, 2, "0b")
  let bits = text.length() - 2
  Ok(Value::Int(@bigint.BigInt::from_int(bits)))
}

///|
fn builtin_int_conjugate(
  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("int.conjugate", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() != 1 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "int.conjugate() takes exactly one argument".to_string(),
      ),
    )
  }
  match positional[0] {
    Value::Int(v) => Ok(Value::Int(v))
    Value::Bool(v) => Ok(Value::Int(if v { 1N } else { 0N }))
    _ =>
      Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "int.conjugate() expects an int".to_string(),
        ),
      )
  }
}

///|
fn builtin_float_conjugate(
  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("float.conjugate", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() != 1 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "float.conjugate() takes exactly one argument".to_string(),
      ),
    )
  }
  match positional[0] {
    Value::Float(v) => Ok(Value::Float(v))
    _ =>
      Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "float.conjugate() expects a float".to_string(),
        ),
      )
  }
}

///|
fn builtin_float_as_integer_ratio(
  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("float.as_integer_ratio", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() != 1 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "float.as_integer_ratio() takes exactly one argument".to_string(),
      ),
    )
  }
  let x = match positional[0] {
    Value::Float(v) => v
    _ =>
      return Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "float.as_integer_ratio() expects a float".to_string(),
        ),
      )
  }
  if x.is_inf() {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Runtime,
        "OverflowError: cannot convert Infinity to integer ratio".to_string(),
      ),
    )
  }
  if x.is_nan() {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Runtime,
        "ValueError: cannot convert NaN to integer ratio".to_string(),
      ),
    )
  }
  if x == 0.0 {
    return Ok(Value::Tuple([Value::Int(0N), Value::Int(1N)]))
  }
  let neg = x < 0.0
  let mut mant = if neg { -x } else { x }
  let mut exp : Int64 = 0L
  while mant >= 1.0 {
    mant = mant / 2.0
    exp = exp + 1L
  }
  while mant < 0.5 {
    mant = mant * 2.0
    exp = exp - 1L
  }
  let scaled = mant * 9007199254740992.0 // 2**53
  let mut mant_i64 = @math.floor(scaled + 0.5).to_int64()
  // Normalize (mantissa is positive here).
  if mant_i64 < 0L {
    mant_i64 = -mant_i64
  }
  let mut num : @bigint.BigInt = @bigint.BigInt::from_int64(mant_i64)
  let mut den : @bigint.BigInt = 1N
  let exp2 = exp - 53L
  if exp2 >= 0L {
    num = num << exp2.to_int()
  } else {
    den = den << (-exp2).to_int()
  }
  while den > 1N && num % 2N == 0N {
    num = num / 2N
    den = den / 2N
  }
  if neg {
    num = -num
  }
  Ok(Value::Tuple([Value::Int(num), Value::Int(den)]))
}

///|
fn builtin_complex_conjugate(
  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("complex.conjugate", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() != 1 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "complex.conjugate() takes exactly one argument".to_string(),
      ),
    )
  }
  match positional[0] {
    Value::Complex(real, imag) => Ok(Value::Complex(real, -imag))
    _ =>
      Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "complex.conjugate() expects a complex".to_string(),
        ),
      )
  }
}

///|
fn builtin_float(
  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("float", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() == 0 {
    Ok(Value::Float(0.0))
  } else {
    match positional[0] {
      Value::Float(v) => Ok(Value::Float(v))
      Value::Int(v) => {
        let d = match bigint_to_double_checked(v) {
          Ok(v) => v
          Err(err) => return Err(err)
        }
        Ok(Value::Float(d))
      }
      Value::Bool(v) => Ok(Value::Float(if v { 1.0 } else { 0.0 }))
      Value::Instance(_) => {
        let converted = match
          real_to_double(positional[0], "__float__", globals, builtins, io) {
          Ok(v) => v
          Err(err) => return Err(err)
        }
        Ok(Value::Float(converted))
      }
      Value::Str(text) => {
        let parsed = @string.parse_double(text) catch {
          _ =>
            return Err(
              make_runtime_error(
                RuntimeErrorKind::Runtime,
                "ValueError: could not convert string to float".to_string(),
              ),
            )
        }
        Ok(Value::Float(parsed))
      }
      _ =>
        Err(
          make_runtime_error(
            RuntimeErrorKind::Type,
            "float() argument must be a string or a number".to_string(),
          ),
        )
    }
  }
}

///|
fn call_method0_on_instance(
  target : Value,
  method_name : String,
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Value?, RuntimeError] {
  let callable_value = match
    get_attr_from_value(target, method_name, globals, builtins, io) {
    Ok(v) => v
    Err(err) =>
      match err.kind {
        RuntimeErrorKind::Attribute => return Ok(None)
        _ => return Err(err)
      }
  }
  let v = match callable_value {
    Value::BoundMethod(bound) =>
      (eval_function_with_kwargs_ref.val)(
        bound.function,
        [bound.self],
        [],
        globals,
        builtins,
        io,
      )
    Value::Function(func) =>
      (eval_function_with_kwargs_ref.val)(
        func,
        [target],
        [],
        globals,
        builtins,
        io,
      )
    _ =>
      Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          method_name + " must be a function",
        ),
      )
  }
  match v {
    Ok(value) => Ok(Some(value))
    Err(err) => Err(err)
  }
}

///|
fn real_to_double(
  value : Value,
  primary_dunder : String,
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Double, RuntimeError] {
  match value {
    Value::Float(v) => Ok(v)
    Value::Int(v) => bigint_to_double_checked(v)
    Value::Bool(v) => Ok(if v { 1.0 } else { 0.0 })
    Value::Instance(_) => {
      let primary = match
        call_method0_on_instance(value, primary_dunder, globals, builtins, io) {
        Ok(v) => v
        Err(err) => return Err(err)
      }
      if primary is Some(ret) {
        match ret {
          Value::Float(v) => return Ok(v)
          Value::Int(v) => return bigint_to_double_checked(v)
          Value::Bool(v) => return Ok(if v { 1.0 } else { 0.0 })
          _ =>
            return Err(
              make_runtime_error(
                RuntimeErrorKind::Type,
                primary_dunder + " returned non-float",
              ),
            )
        }
      }
      let index = match
        call_method0_on_instance(value, "__index__", globals, builtins, io) {
        Ok(v) => v
        Err(err) => return Err(err)
      }
      if index is Some(ret) {
        match ret {
          Value::Int(v) => return bigint_to_double_checked(v)
          Value::Bool(v) => return Ok(if v { 1.0 } else { 0.0 })
          _ =>
            return Err(
              make_runtime_error(
                RuntimeErrorKind::Type,
                "__index__ returned non-int".to_string(),
              ),
            )
        }
      }
      Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "float() argument must be a string or a number".to_string(),
        ),
      )
    }
    _ =>
      Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "float() argument must be a string or a number".to_string(),
        ),
      )
  }
}

///|
fn complex_to_double(
  value : Value,
  globals : Array[(String, Value)],
  builtins : Array[(String, Value)],
  io : MockIO,
) -> Result[Double, RuntimeError] {
  match value {
    Value::Str(_) =>
      Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "complex() argument must be a number".to_string(),
        ),
      )
    Value::Complex(_, _) =>
      Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "complex() argument must be a real number".to_string(),
        ),
      )
    _ => real_to_double(value, "__float__", globals, builtins, io)
  }
}

///|
fn builtin_float_repr(
  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("float.__repr__", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() != 1 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "float.__repr__() takes exactly one argument".to_string(),
      ),
    )
  }
  match positional[0] {
    Value::Float(v) => Ok(Value::Str(v.to_string()))
    _ =>
      Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "float.__repr__() expects a float".to_string(),
        ),
      )
  }
}

///|
fn builtin_float_getformat(
  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("float.__getformat__", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() != 1 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "float.__getformat__() takes exactly one argument".to_string(),
      ),
    )
  }
  let kind = match positional[0] {
    Value::Str(text) => text
    _ =>
      return Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "float.__getformat__() argument must be str".to_string(),
        ),
      )
  }
  // CPython returns strings like "IEEE, little-endian". Most stdlib probes only
  // check that it starts with "IEEE".
  if kind == "double" || kind == "float" {
    Ok(Value::Str("IEEE, little-endian"))
  } else {
    Err(
      make_runtime_error(
        RuntimeErrorKind::Runtime,
        "ValueError: unknown float format".to_string(),
      ),
    )
  }
}

///|
fn builtin_complex(
  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("complex", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() == 0 {
    return Ok(Value::Complex(0.0, 0.0))
  }
  if positional.length() > 2 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "complex() takes at most two arguments".to_string(),
      ),
    )
  }
  if positional.length() == 1 {
    match positional[0] {
      Value::Complex(real, imag) => Ok(Value::Complex(real, imag))
      Value::Float(v) => Ok(Value::Complex(v, 0.0))
      Value::Int(v) =>
        match bigint_to_double_checked(v) {
          Ok(value) => Ok(Value::Complex(value, 0.0))
          Err(err) => Err(err)
        }
      Value::Bool(v) => Ok(Value::Complex(if v { 1.0 } else { 0.0 }, 0.0))
      Value::Instance(_) => {
        let complex_value = match
          call_method0_on_instance(
            positional[0],
            "__complex__",
            globals,
            builtins,
            io,
          ) {
          Ok(v) => v
          Err(err) => return Err(err)
        }
        if complex_value is Some(ret) {
          match ret {
            Value::Complex(r, i) => return Ok(Value::Complex(r, i))
            Value::Float(r) => return Ok(Value::Complex(r, 0.0))
            Value::Int(i) =>
              return Ok(
                Value::Complex(
                  match bigint_to_double_checked(i) {
                    Ok(d) => d
                    Err(err) => return Err(err)
                  },
                  0.0,
                ),
              )
            Value::Bool(b) =>
              return Ok(Value::Complex(if b { 1.0 } else { 0.0 }, 0.0))
            _ =>
              return Err(
                make_runtime_error(
                  RuntimeErrorKind::Type,
                  "__complex__ returned non-complex".to_string(),
                ),
              )
          }
        }
        let real = match
          complex_to_double(positional[0], globals, builtins, io) {
          Ok(v) => v
          Err(err) => return Err(err)
        }
        Ok(Value::Complex(real, 0.0))
      }
      _ =>
        Err(
          make_runtime_error(
            RuntimeErrorKind::Type,
            "complex() argument must be a number".to_string(),
          ),
        )
    }
  } else {
    let real = match positional[0] {
      Value::Complex(_, _) =>
        return Err(
          make_runtime_error(
            RuntimeErrorKind::Type,
            "complex() can't take second arg if first is complex".to_string(),
          ),
        )
      _ =>
        match complex_to_double(positional[0], globals, builtins, io) {
          Ok(value) => value
          Err(err) => return Err(err)
        }
    }
    let imag = match positional[1] {
      Value::Complex(_, _) =>
        return Err(
          make_runtime_error(
            RuntimeErrorKind::Type,
            "complex() second argument must be a real number".to_string(),
          ),
        )
      _ =>
        match complex_to_double(positional[1], globals, builtins, io) {
          Ok(value) => value
          Err(err) => return Err(err)
        }
    }
    Ok(Value::Complex(real, imag))
  }
}

///|
fn builtin_abs(
  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("abs", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() != 1 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "abs() takes exactly one argument".to_string(),
      ),
    )
  }
  match positional[0] {
    Value::Int(v) => Ok(Value::Int(if v < 0N { -v } else { v }))
    Value::Bool(v) => Ok(Value::Int(if v { 1N } else { 0N }))
    Value::Float(v) => Ok(Value::Float(if v < 0.0 { -v } else { v }))
    Value::Complex(real, imag) =>
      Ok(Value::Float(@math.pow(real * real + imag * imag, 0.5)))
    other =>
      match get_attr_from_value(other, "__abs__", globals, builtins, io) {
        Ok(callable) =>
          call_callable_with_env(callable, [], [], globals, builtins, io)
        Err(err) =>
          match err.kind {
            RuntimeErrorKind::Attribute =>
              Err(
                make_runtime_error(
                  RuntimeErrorKind::Type,
                  "bad operand type for abs()".to_string(),
                ),
              )
            _ => Err(err)
          }
      }
  }
}

///|
fn round_float_to_int(value : Double) -> Int64 {
  // CPython uses bankers rounding (ties to even) for floats.
  if value.is_nan() {
    return 0L
  }
  if value.is_inf() {
    return 0L
  }
  let neg = value < 0.0
  let x = if neg { -value } else { value }
  let f = @math.floor(x)
  let frac = x - f
  let mut out = if frac > 0.5 {
    f + 1.0
  } else if frac < 0.5 {
    f
  } else {
    // Exact 0.5: tie to even
    let fi = f.to_int64()
    if fi % 2L == 0L {
      f
    } else {
      f + 1.0
    }
  }
  if neg {
    out = -out
  }
  out.to_int64()
}

///|
fn builtin_round(
  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("round", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() == 0 || positional.length() > 2 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "round() takes 1 or 2 arguments".to_string(),
      ),
    )
  }
  let mut ndigits_opt : Int32? = None
  if positional.length() == 2 {
    ndigits_opt = match positional[1] {
      Value::Int(v) => Some(v.to_int())
      Value::Bool(v) => Some(if v { 1 } else { 0 })
      _ =>
        return Err(
          make_runtime_error(
            RuntimeErrorKind::Type,
            "round() ndigits must be an int".to_string(),
          ),
        )
    }
  }
  match (positional[0], ndigits_opt) {
    (Value::Int(v), None) => Ok(Value::Int(v))
    (Value::Bool(v), None) => Ok(Value::Int(if v { 1N } else { 0N }))
    (Value::Float(v), None) =>
      if v.is_nan() || v.is_inf() {
        Ok(Value::Float(v))
      } else {
        Ok(Value::Int(@bigint.BigInt::from_int64(round_float_to_int(v))))
      }
    (Value::Int(v), Some(ndigits)) =>
      if ndigits >= 0 {
        Ok(Value::Int(v))
      } else {
        // Round integer to negative ndigits (e.g. round(1234, -2) == 1200).
        let base = @bigint.BigInt::from_int(10)
        let exp = @bigint.BigInt::from_int(-ndigits)
        let factor = base.pow(exp)
        if factor == 0N {
          Ok(Value::Int(v))
        } else {
          let q = v / factor
          let r = v % factor
          let abs_r = if r < 0N { -r } else { r }
          let abs_factor = if factor < 0N { -factor } else { factor }
          let twice = abs_r * 2N
          let mut q_adj = q
          if twice > abs_factor {
            q_adj = if v < 0N { q - 1N } else { q + 1N }
          } else if twice == abs_factor {
            // tie to even on the quotient
            let even = q % 2N == 0N
            if !even {
              q_adj = if v < 0N { q - 1N } else { q + 1N }
            }
          }
          Ok(Value::Int(q_adj * factor))
        }
      }
    (Value::Bool(v), Some(ndigits)) => {
      // Treat bool as int for ndigits rounding.
      let args : Array[Value] = [
        Value::Int(if v { 1N } else { 0N }),
        Value::Int(@bigint.BigInt::from_int(ndigits)),
      ]
      builtin_round(args, [], locals, globals, builtins, io)
    }
    (Value::Float(v), Some(ndigits)) =>
      if v.is_nan() || v.is_inf() {
        Ok(Value::Float(v))
      } else if ndigits == 0 {
        Ok(Value::Float(round_float_to_int(v).to_double()))
      } else if ndigits > 0 {
        let factor = @math.pow(10.0, ndigits.to_double())
        let scaled = v * factor
        Ok(Value::Float(round_float_to_int(scaled).to_double() / factor))
      } else {
        let factor = @math.pow(10.0, (-ndigits).to_double())
        let scaled = v / factor
        Ok(Value::Float(round_float_to_int(scaled).to_double() * factor))
      }
    _ =>
      Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "round() argument must be a number".to_string(),
        ),
      )
  }
}

///|
fn builtin_pow(
  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("pow", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() < 2 || positional.length() > 3 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "pow() takes 2 or 3 arguments".to_string(),
      ),
    )
  }
  let modulus_opt : @bigint.BigInt? = if positional.length() == 3 {
    match positional[2] {
      Value::Int(v) => Some(v)
      Value::Bool(v) => Some(if v { 1N } else { 0N })
      _ =>
        return Err(
          make_runtime_error(
            RuntimeErrorKind::Type,
            "pow() 3rd argument must be int".to_string(),
          ),
        )
    }
  } else {
    None
  }
  match (positional[0], positional[1], modulus_opt) {
    (Value::Int(a), Value::Int(b), Some(m)) =>
      if b < 0N {
        Err(
          make_runtime_error(
            RuntimeErrorKind::Type,
            "pow() exponent must be >= 0 when modulus is provided".to_string(),
          ),
        )
      } else if m == 0N {
        Err(
          make_runtime_error(
            RuntimeErrorKind::ZeroDivision,
            "integer modulo by zero".to_string(),
          ),
        )
      } else {
        Ok(Value::Int(a.pow(b, modulus=m)))
      }
    (Value::Int(a), Value::Int(b), None) =>
      if b < 0N {
        let aa = match bigint_to_double_checked(a) {
          Ok(v) => v
          Err(err) => return Err(err)
        }
        let bb = match bigint_to_double_checked(b) {
          Ok(v) => v
          Err(err) => return Err(err)
        }
        Ok(Value::Float(@math.pow(aa, bb)))
      } else {
        Ok(Value::Int(a.pow(b)))
      }
    _ => {
      let (_, aa) = match number_value(positional[0]) {
        Ok(v) => v
        Err(err) => return Err(err)
      }
      let (_, bb) = match number_value(positional[1]) {
        Ok(v) => v
        Err(err) => return Err(err)
      }
      Ok(Value::Float(@math.pow(aa, bb)))
    }
  }
}

///|
fn floor_divmod_int(
  a : @bigint.BigInt,
  b : @bigint.BigInt,
) -> (@bigint.BigInt, @bigint.BigInt) {
  // Python-style floor division adjustment.
  let mut q = a / b
  let mut r = a % b
  if r != 0N && (r > 0N) != (b > 0N) {
    q = q - 1N
    r = r + b
  }
  (q, r)
}

///|
fn builtin_divmod(
  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("divmod", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() != 2 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "divmod() takes exactly two arguments".to_string(),
      ),
    )
  }
  if positional[0] is Value::Instance(_) {
    match
      get_attr_from_value(positional[0], "__divmod__", globals, builtins, io) {
      Ok(callable_value) =>
        return call_callable_with_env(
          callable_value,
          [positional[1]],
          [],
          globals,
          builtins,
          io,
        )
      Err(_) => ()
    }
  }
  if positional[1] is Value::Instance(_) {
    match
      get_attr_from_value(positional[1], "__rdivmod__", globals, builtins, io) {
      Ok(callable_value) =>
        return call_callable_with_env(
          callable_value,
          [positional[0]],
          [],
          globals,
          builtins,
          io,
        )
      Err(_) => ()
    }
  }
  match (positional[0], positional[1]) {
    (Value::Int(a), Value::Int(b)) =>
      if b == 0N {
        Err(
          make_runtime_error(
            RuntimeErrorKind::ZeroDivision,
            "integer division or modulo by zero".to_string(),
          ),
        )
      } else {
        let (q, r) = floor_divmod_int(a, b)
        Ok(Value::Tuple([Value::Int(q), Value::Int(r)]))
      }
    _ => {
      let (_, aa) = match number_value(positional[0]) {
        Ok(v) => v
        Err(err) => return Err(err)
      }
      let (_, bb) = match number_value(positional[1]) {
        Ok(v) => v
        Err(err) => return Err(err)
      }
      if bb == 0.0 {
        return Err(
          make_runtime_error(
            RuntimeErrorKind::ZeroDivision,
            "division by zero".to_string(),
          ),
        )
      }
      let q = @math.floor(aa / bb)
      let r = aa - q * bb
      Ok(Value::Tuple([Value::Float(q), Value::Float(r)]))
    }
  }
}

///|
fn builtin_chr(
  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("chr", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() != 1 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "chr() takes exactly one argument".to_string(),
      ),
    )
  }
  let codepoint = match positional[0] {
    Value::Int(v) => v
    Value::Bool(v) => if v { 1N } else { 0N }
    _ =>
      return Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "chr() argument must be int".to_string(),
        ),
      )
  }
  if codepoint < 0N || codepoint > @bigint.BigInt::from_int(0x10FFFF) {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Runtime,
        "ValueError: chr() arg not in range(0x110000)".to_string(),
      ),
    )
  }
  Ok(
    Value::Str(
      char_to_string(Int32::unsafe_to_char(codepoint.to_int64().to_int())),
    ),
  )
}

///|
fn builtin_ord(
  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("ord", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() != 1 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "ord() takes exactly one argument".to_string(),
      ),
    )
  }
  match positional[0] {
    Value::Str(text) => {
      let chars = text.to_array()
      if chars.length() != 1 {
        return Err(
          make_runtime_error(
            RuntimeErrorKind::Type,
            "ord() expected a character".to_string(),
          ),
        )
      }
      Ok(Value::Int(@bigint.BigInt::from_int(chars[0].to_int())))
    }
    Value::Bytes(values) | Value::ByteArray(values) => {
      if values.length() != 1 {
        return Err(
          make_runtime_error(
            RuntimeErrorKind::Type,
            "ord() expected a character".to_string(),
          ),
        )
      }
      Ok(Value::Int(@bigint.BigInt::from_int(values[0])))
    }
    _ =>
      Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "ord() expected a character".to_string(),
        ),
      )
  }
}

///|
fn builtin_bin(
  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("bin", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() != 1 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "bin() takes exactly one argument".to_string(),
      ),
    )
  }
  let value = match positional[0] {
    Value::Int(v) => v
    Value::Bool(v) => if v { 1N } else { 0N }
    _ =>
      return Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "bin() argument must be int".to_string(),
        ),
      )
  }
  Ok(Value::Str(bigint_to_base_string(value, 2, "0b")))
}

///|
fn builtin_oct(
  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("oct", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() != 1 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "oct() takes exactly one argument".to_string(),
      ),
    )
  }
  let value = match positional[0] {
    Value::Int(v) => v
    Value::Bool(v) => if v { 1N } else { 0N }
    _ =>
      return Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "oct() argument must be int".to_string(),
        ),
      )
  }
  Ok(Value::Str(bigint_to_base_string(value, 8, "0o")))
}

///|
fn builtin_hex(
  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("hex", keywords) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if positional.length() != 1 {
    return Err(
      make_runtime_error(
        RuntimeErrorKind::Type,
        "hex() takes exactly one argument".to_string(),
      ),
    )
  }
  let value = match positional[0] {
    Value::Int(v) => v
    Value::Bool(v) => if v { 1N } else { 0N }
    _ =>
      return Err(
        make_runtime_error(
          RuntimeErrorKind::Type,
          "hex() argument must be int".to_string(),
        ),
      )
  }
  Ok(Value::Str(bigint_to_base_string(value, 16, "0x")))
}