///|
let i128_max : BigInt = (1N << 127) - 1N

///|
let i128_min : BigInt = -(1N << 127)

///|
let u128_max : BigInt = (1N << 128) - 1N

///|
let i64_max_big : BigInt = BigInt::from_int64(0x7FFFFFFFFFFFFFFFL)

///|
let i64_min_big : BigInt = BigInt::from_int64(-0x7FFFFFFFFFFFFFFFL - 1L)

///|
/// `i128::MIN` as a positive `u128` (`2^127`).
let min_i128_as_pos_u128 : BigInt = 1N << 127

///|
let max_repeated_len = 100_000_000

///|
fn in_i128(v : BigInt) -> Bool {
  v >= i128_min && v <= i128_max
}

///|
fn in_i64(v : BigInt) -> Bool {
  v >= i64_min_big && v <= i64_max_big
}

///|
/// Converts an integer (assumed to be in the i128 range) into a value,
/// using a 64 bit representation if possible.
fn int_as_value(v : BigInt) -> Value {
  if in_i64(v) {
    I64(v.to_int64())
  } else if in_i128(v) {
    I128(v)
  } else if v >= 0N && v <= u128_max {
    U128(v)
  } else {
    Invalid(TemplateError::new(InvalidOperation, "integer out of range"))
  }
}

///|
/// Wraps a u128 into the i128 range (Rust's `as i128` cast).
fn wrap_u128_to_i128(v : BigInt) -> BigInt {
  if v > i128_max {
    v - (1N << 128)
  } else {
    v
  }
}

///|
/// Port of Rust's saturating `f64 as i64` cast.
fn f64_to_i64_sat(v : Double) -> Int64 {
  if v.is_nan() {
    0L
  } else if v >= 9223372036854775807.0 {
    0x7FFFFFFFFFFFFFFFL
  } else if v <= -9223372036854775808.0 {
    -0x7FFFFFFFFFFFFFFFL - 1L
  } else {
    v.to_int64()
  }
}

///|
/// Port of Rust's saturating `f64 as u64` cast.
fn f64_to_u64_sat(v : Double) -> UInt64 {
  if v.is_nan() || v <= 0.0 {
    0UL
  } else if v >= 18446744073709551615.0 {
    0xFFFFFFFFFFFFFFFFUL
  } else {
    v.to_uint64()
  }
}

///|
/// Exactly converts the integral part of a finite double into a big
/// integer (truncating towards zero).
fn f64_trunc_to_bigint(v : Double) -> BigInt {
  if v.is_nan() || v.is_inf() {
    return 0N
  }
  let bits = v.reinterpret_as_int64()
  let negative = bits < 0L
  let exp = ((bits >> 52) & 0x7FFL).to_int()
  let mantissa = bits & 0xFFFFFFFFFFFFFL
  if exp == 0 {
    // subnormals are always < 1
    return 0N
  }
  let m = BigInt::from_int64(mantissa | (1L << 52))
  let shift = exp - 1075
  let rv = if shift >= 0 {
    m << shift
  } else if shift > -64 {
    m >> -shift
  } else {
    0N
  }
  if negative {
    -rv
  } else {
    rv
  }
}

///|
fn clamp_big(v : BigInt, lo : BigInt, hi : BigInt) -> BigInt {
  if v < lo {
    lo
  } else if v > hi {
    hi
  } else {
    v
  }
}

///|
/// Converts a big integer to the nearest double.
fn bigint_to_f64(v : BigInt) -> Double {
  if in_i64(v) {
    return v.to_int64().to_double()
  }
  @string.parse_double(v.to_string()) catch {
    _ => if v > 0N { 1.0 / 0.0 } else { -1.0 / 0.0 }
  }
}

///|
/// Port of `i128::try_from(Value)`.
fn Value::to_i128(self : Value) -> BigInt? {
  match self {
    Bool(b) => Some(if b { 1N } else { 0N })
    I64(v) => Some(BigInt::from_int64(v))
    U64(v) => Some(BigInt::from_uint64(v))
    F64(v) => {
      let i = f64_to_i64_sat(v)
      if i.to_double() == v {
        Some(BigInt::from_int64(i))
      } else {
        None
      }
    }
    I128(v) => Some(v)
    U128(v) => if v <= i128_max { Some(v) } else { None }
    _ => None
  }
}

///|
/// Port of `u128::try_from(Value)`.
fn Value::to_u128(self : Value) -> BigInt? {
  match self {
    U128(v) => Some(v)
    _ =>
      match self.to_i128() {
        Some(v) if v >= 0N => Some(v)
        _ => None
      }
  }
}

///|
/// Returns the value as `Int64` if it is an integer (or an integral float)
/// that fits.
pub fn Value::as_i64(self : Value) -> Int64? {
  match self {
    I64(v) => Some(v)
    U64(v) =>
      if v <= 0x7FFFFFFFFFFFFFFFUL {
        Some(v.reinterpret_as_int64())
      } else {
        None
      }
    _ =>
      match self.to_i128() {
        Some(v) if in_i64(v) => Some(v.to_int64())
        _ => None
      }
  }
}

///|
/// Returns the value as a non-negative `Int` index if possible.
pub fn Value::as_usize(self : Value) -> Int? {
  match self.as_i64() {
    Some(v) if v >= 0L && v <= 0x7FFFFFFFL => Some(v.to_int())
    _ => None
  }
}

///|
/// Returns the value as `Double` if it is a number (or a bool).
pub fn Value::as_f64(self : Value) -> Double? {
  as_f64(self, true)
}

///|
fn as_f64(value : Value, lossy : Bool) -> Double? {
  match value {
    Bool(b) => Some(if b { 1.0 } else { 0.0 })
    U64(x) => {
      let rv = x.to_double()
      if lossy || f64_to_u64_sat(rv) == x {
        Some(rv)
      } else {
        None
      }
    }
    I64(x) => {
      let rv = x.to_double()
      if lossy || f64_to_i64_sat(rv) == x {
        Some(rv)
      } else {
        None
      }
    }
    U128(x) => {
      let rv = bigint_to_f64(x)
      // Rust's `rv as u128` saturates
      if lossy || clamp_big(f64_trunc_to_bigint(rv), 0N, u128_max) == x {
        Some(rv)
      } else {
        None
      }
    }
    I128(x) => {
      let rv = bigint_to_f64(x)
      if lossy || clamp_big(f64_trunc_to_bigint(rv), i128_min, i128_max) == x {
        Some(rv)
      } else {
        None
      }
    }
    F64(x) => Some(x)
    _ => None
  }
}

///|
priv enum CoerceResult {
  Int(Int64, Int64)
  Big(BigInt, BigInt)
  F64(Double, Double)
  Str(String, String)
}

///|
fn big_pair(a : BigInt, b : BigInt) -> CoerceResult {
  if in_i64(a) && in_i64(b) {
    Int(a.to_int64(), b.to_int64())
  } else {
    Big(a, b)
  }
}

///|
fn coerce(a : Value, b : Value, lossy : Bool) -> CoerceResult? {
  match (a, b) {
    (I64(x), I64(y)) => Some(Int(x, y))
    (U64(x), U64(y)) =>
      Some(big_pair(BigInt::from_uint64(x), BigInt::from_uint64(y)))
    (U128(x), U128(y)) =>
      Some(big_pair(wrap_u128_to_i128(x), wrap_u128_to_i128(y)))
    (Str(x, _), Str(y, _)) => Some(Str(x, y))
    (I128(x), I128(y)) => Some(big_pair(x, y))
    (F64(x), F64(y)) => Some(F64(x, y))
    (F64(x), _) =>
      match as_f64(b, lossy) {
        Some(y) => Some(F64(x, y))
        None => None
      }
    (_, F64(y)) =>
      match as_f64(a, lossy) {
        Some(x) => Some(F64(x, y))
        None => None
      }
    _ =>
      match (a.to_i128(), b.to_i128()) {
        (Some(x), Some(y)) => Some(big_pair(x, y))
        _ => None
      }
  }
}

///|
fn impossible_op(op : String, lhs : Value, rhs : Value) -> TemplateError {
  TemplateError::new(
    InvalidOperation,
    "tried to use \{op} operator on unsupported types \{lhs.kind()} and \{rhs.kind()}",
  )
}

///|
fn failed_op(op : String, lhs : Value, rhs : Value) -> TemplateError {
  TemplateError::new(
    InvalidOperation,
    "unable to calculate \{lhs} \{op} \{rhs}",
  )
}

///|
/// Checks a big integer result against the i128 range.
fn checked_i128(v : BigInt) -> BigInt? {
  if in_i128(v) {
    Some(v)
  } else {
    None
  }
}

///|
fn checked_i64_add(a : Int64, b : Int64) -> Int64? {
  let r = a + b
  if ((a ^ r) & (b ^ r)) < 0L {
    None
  } else {
    Some(r)
  }
}

///|
fn checked_i64_sub(a : Int64, b : Int64) -> Int64? {
  let r = a - b
  if ((a ^ b) & (a ^ r)) < 0L {
    None
  } else {
    Some(r)
  }
}

///|
fn small_mul_safe(a : Int64) -> Bool {
  a <= 3037000499L && a >= -3037000499L
}

///|
fn int_binop(
  op : String,
  lhs : Value,
  rhs : Value,
  big_op : (BigInt, BigInt) -> BigInt?,
  fast : (Int64, Int64) -> Int64?,
) -> Value raise TemplateError {
  guard coerce(lhs, rhs, true) is Some(c) else {
    raise impossible_op(op, lhs, rhs)
  }
  match c {
    Int(a, b) =>
      match fast(a, b) {
        Some(r) => I64(r)
        None =>
          match big_op(BigInt::from_int64(a), BigInt::from_int64(b)) {
            Some(r) => int_as_value(r)
            None => raise failed_op(op, lhs, rhs)
          }
      }
    Big(a, b) =>
      match big_op(a, b) {
        Some(r) => int_as_value(r)
        None => raise failed_op(op, lhs, rhs)
      }
    F64(a, b) =>
      match op {
        "-" => F64(a - b)
        "*" => F64(a * b)
        _ => F64(a + b)
      }
    Str(_, _) => raise impossible_op(op, lhs, rhs)
  }
}

///|
/// Adds two values.
fn value_add(lhs : Value, rhs : Value) -> Value raise TemplateError {
  // fast path for the most common case
  if small_int(lhs) is Some(a) &&
    small_int(rhs) is Some(b) &&
    checked_i64_add(a, b) is Some(r) {
    return I64(r)
  }
  if lhs.is_tuple() || rhs.is_tuple() {
    if lhs.is_tuple() && rhs.is_tuple() {
      let values = lhs.try_iter().to_array()
      for v in rhs.try_iter() {
        values.push(v)
      }
      return Value::from_tuple(values)
    }
    raise impossible_op("+", lhs, rhs)
  }
  if lhs.kind() is (Seq | Iterable) && rhs.kind() is (Seq | Iterable) {
    let values = [lhs, rhs]
    let depth = merge_seq_depth_for_values(values)
    if depth > merge_seq_max_depth {
      match (lhs.len(), rhs.len()) {
        (Some(a), Some(b)) => {
          let rv = Array(capacity=a + b)
          for v in lhs.try_iter() {
            rv.push(v)
          }
          for v in rhs.try_iter() {
            rv.push(v)
          }
          return Value::from_array(rv)
        }
        _ => ()
      }
    }
    return make_merge_seq(values, Iterable)
  }
  match coerce(lhs, rhs, true) {
    Some(Str(a, b)) => Value::from_string(a + b)
    Some(_) =>
      int_binop("+", lhs, rhs, (a, b) => checked_i128(a + b), checked_i64_add)
    None => raise impossible_op("+", lhs, rhs)
  }
}

///|
fn value_sub(lhs : Value, rhs : Value) -> Value raise TemplateError {
  if small_int(lhs) is Some(a) &&
    small_int(rhs) is Some(b) &&
    checked_i64_sub(a, b) is Some(r) {
    return I64(r)
  }
  int_binop("-", lhs, rhs, (a, b) => checked_i128(a - b), checked_i64_sub)
}

///|
fn value_mul(lhs : Value, rhs : Value) -> Value raise TemplateError {
  if small_int(lhs) is Some(a) &&
    small_int(rhs) is Some(b) &&
    small_mul_safe(a) &&
    small_mul_safe(b) {
    return I64(a * b)
  }
  let str_and_n = match (lhs.as_str(), rhs.as_str()) {
    (Some(s), _) => Some((s, rhs))
    (_, Some(s)) => Some((s, lhs))
    _ => None
  }
  if str_and_n is Some((s, n)) {
    guard n.as_usize() is Some(n) else {
      raise TemplateError::new(
        InvalidOperation,
        "strings can only be multiplied with integers",
      )
    }
    let byte_len = utf8_len(s)
    if n != 0 && byte_len > max_repeated_len / n {
      raise TemplateError::new(InvalidOperation, "repeated string is too large")
    }
    return Value::from_string(s.repeat(n))
  }
  let seq_and_n = match (lhs, rhs) {
    (Object(o), _) if o.repr() is (Iterable | Seq) => Some((o, rhs))
    (_, Object(o)) if o.repr() is (Iterable | Seq) => Some((o, lhs))
    _ => None
  }
  if seq_and_n is Some((seq, n)) {
    return repeat_iterable(n, seq)
  }
  int_binop("*", lhs, rhs, (a, b) => checked_i128(a * b), (a, b) => {
    if small_mul_safe(a) && small_mul_safe(b) {
      Some(a * b)
    } else {
      None
    }
  })
}

///|
fn repeat_iterable(n : Value, seq : DynObject) -> Value raise TemplateError {
  guard n.as_usize() is Some(n) else {
    raise TemplateError::new(
      InvalidOperation,
      "sequences and iterables can only be multiplied with integers",
    )
  }
  guard seq.enumerator_len() is Some(len) else {
    raise TemplateError::new(
      InvalidOperation,
      "cannot repeat unsized iterables",
    )
  }
  let is_tuple = seq.inner is Tuple(_)
  if len == 0 || n == 0 {
    return if is_tuple { Value::from_tuple([]) } else { Value::from_array([]) }
  }
  if len > max_repeated_len / n {
    raise TemplateError::new(InvalidOperation, "repeated sequence is too large")
  }
  let repeated_len = len * n
  if seq.inner is Tuple(items) {
    let values = Array(capacity=repeated_len)
    for _ in 0.. {
    let mut round = 0
    let mut current : Iter[Value]? = None
    Iter::new(
      () => {
        for ;; {
          match current {
            Some(it) =>
              match it.next() {
                Some(v) => break Some(v)
                None => current = None
              }
            None => {
              if round >= n {
                break None
              }
              round += 1
              current = match seq.try_iter() {
                Some(it) => Some(it)
                None =>
                  Some(
                    Iter::repeat(
                      Value::from_error(
                        TemplateError::new(
                          InvalidOperation,
                          "iterable did not iterate against expectations",
                        ),
                      ),
                    ).take(len),
                  )
              }
            }
          }
        }
      },
      size_hint=repeated_len,
    )
  })
}

///|
fn value_div(lhs : Value, rhs : Value) -> Value raise TemplateError {
  guard as_f64(lhs, true) is Some(a) else { raise impossible_op("/", lhs, rhs) }
  guard as_f64(rhs, true) is Some(b) else { raise impossible_op("/", lhs, rhs) }
  if b == 0.0 {
    raise failed_op("/", lhs, rhs)
  }
  F64(a / b)
}

///|
/// Floor division and remainder on big integers.
fn int_div_rem_floor(a : BigInt, b : BigInt) -> (BigInt, BigInt)? {
  // BigInt division truncates towards zero (like Rust's checked_div)
  let mut quotient = a / b
  let mut remainder = a % b
  if !in_i128(quotient) {
    return None
  }
  if !remainder.is_zero() && (remainder < 0N) != (b < 0N) {
    quotient = quotient - 1N
    remainder = remainder + b
    if !in_i128(quotient) || !in_i128(remainder) {
      return None
    }
  }
  Some((quotient, remainder))
}

///|
fn copysign(magnitude : Double, sign : Double) -> Double {
  let neg = sign.reinterpret_as_int64() < 0L
  let m = magnitude.abs()
  if neg {
    -m
  } else {
    m
  }
}

///|
fn float_div_rem_floor(a : Double, b : Double) -> (Double, Double) {
  let mut remainder = a % b
  let mut quotient = (a - remainder) / b
  if remainder != 0.0 {
    if (remainder < 0.0) != (b < 0.0) {
      remainder += b
      quotient -= 1.0
    }
  } else {
    remainder = copysign(0.0, b)
  }
  if quotient != 0.0 {
    let mut floored = quotient.floor()
    if quotient - floored > 0.5 {
      floored += 1.0
    }
    quotient = floored
  } else {
    quotient = copysign(0.0, a / b)
  }
  (quotient, remainder)
}

///|
fn value_rem(lhs : Value, rhs : Value) -> Value raise TemplateError {
  match coerce(lhs, rhs, true) {
    Some(Int(a, b)) if b != 0L =>
      if b == -1L {
        I64(0L)
      } else {
        let r = a % b
        if r != 0L && (r < 0L) != (b < 0L) {
          I64(r + b)
        } else {
          I64(r)
        }
      }
    Some(Big(a, b)) if !b.is_zero() =>
      if a == i128_min && b == -1N {
        I64(0L)
      } else {
        match int_div_rem_floor(a, b) {
          Some((_, r)) => int_as_value(r)
          None => raise failed_op("%", lhs, rhs)
        }
      }
    Some(F64(a, b)) if b != 0.0 => F64(float_div_rem_floor(a, b).1)
    Some(Int(_, _) | Big(_, _) | F64(_, _)) => raise failed_op("%", lhs, rhs)
    _ => raise impossible_op("%", lhs, rhs)
  }
}

///|
fn value_int_div(lhs : Value, rhs : Value) -> Value raise TemplateError {
  let big = match coerce(lhs, rhs, true) {
    Some(Int(a, b)) if b != 0L => {
      if a != -0x7FFFFFFFFFFFFFFFL - 1L || b != -1L {
        let q = a / b
        let r = a % b
        return if r != 0L && (r < 0L) != (b < 0L) {
          I64(q - 1L)
        } else {
          I64(q)
        }
      }
      Some((BigInt::from_int64(a), BigInt::from_int64(b)))
    }
    Some(Big(a, b)) if !b.is_zero() => Some((a, b))
    Some(F64(a, b)) if b != 0.0 => return F64(float_div_rem_floor(a, b).0)
    Some(Int(_, _) | Big(_, _) | F64(_, _)) => raise failed_op("//", lhs, rhs)
    _ => raise impossible_op("//", lhs, rhs)
  }
  guard big is Some((a, b)) else { abort("unreachable") }
  if a == i128_min && b == -1N {
    U128(min_i128_as_pos_u128)
  } else {
    match int_div_rem_floor(a, b) {
      Some((q, _)) => int_as_value(q)
      None => raise failed_op("//", lhs, rhs)
    }
  }
}

///|
fn checked_pow_i128(a : BigInt, b : BigInt) -> BigInt? {
  // the exponent must fit into a u32
  if b < 0N || b > BigInt::from_uint64(0xFFFFFFFFUL) {
    return None
  }
  if a.is_zero() {
    return Some(if b.is_zero() { 1N } else { 0N })
  }
  if a == 1N {
    return Some(1N)
  }
  if a == -1N {
    return Some(if (b % 2N).is_zero() { 1N } else { -1N })
  }
  // |a| >= 2, so anything beyond 2^127 overflows
  if b > 128N {
    return None
  }
  let mut rv = 1N
  let n = b.to_int()
  for _ in 0.. Value raise TemplateError {
  match coerce(lhs, rhs, true) {
    Some(Int(a, b)) =>
      match checked_pow_i128(BigInt::from_int64(a), BigInt::from_int64(b)) {
        Some(v) => int_as_value(v)
        None => raise failed_op("**", lhs, rhs)
      }
    Some(Big(a, b)) =>
      match checked_pow_i128(a, b) {
        Some(v) => int_as_value(v)
        None => raise failed_op("**", lhs, rhs)
      }
    Some(F64(a, b)) => F64(@math.pow(a, b))
    _ => raise impossible_op("**", lhs, rhs)
  }
}

///|
fn value_neg(val : Value) -> Value raise TemplateError {
  if val.kind() != Number {
    raise TemplateError::from_kind(InvalidOperation)
  }
  match val {
    F64(x) => F64(-x)
    U128(x) if x == min_i128_as_pos_u128 => U128(min_i128_as_pos_u128)
    I64(x) if x != -0x7FFFFFFFFFFFFFFFL - 1L => I64(-x)
    _ =>
      match val.to_i128() {
        Some(x) =>
          match checked_i128(-x) {
            Some(v) => int_as_value(v)
            None => raise TemplateError::new(InvalidOperation, "overflow")
          }
        None => raise TemplateError::from_kind(InvalidOperation)
      }
  }
}

///|
fn string_concat(left : Value, right : Value) -> Value {
  Value::from_string(left.to_string() + right.to_string())
}

///|
/// Implements a containment operation on values.
fn value_contains(
  container : Value,
  value : Value,
) -> Value raise TemplateError {
  // Special case where if the container is undefined, it cannot hold
  // values.  For strict containment checks the vm has a special case.
  if container.is_undefined() {
    return Bool(false)
  }
  let rv = match container.as_str() {
    Some(s) =>
      match value.as_str() {
        Some(s2) => s.contains(s2)
        None => s.contains(value.to_string())
      }
    None =>
      match container {
        Object(obj) =>
          match obj.repr() {
            Plain => false
            Map => obj.get_value(value) is Some(_)
            Seq | Iterable =>
              match obj.try_iter() {
                Some(iter) => iter.any(v => v == value)
                None => false
              }
          }
        _ =>
          raise TemplateError::new(
            InvalidOperation,
            "cannot perform a containment check on this value",
          )
      }
  }
  Bool(rv)
}

///|
fn get_offset_and_len(
  start : Int64?,
  stop : Int64?,
  end : () -> Int,
) -> (Int, Int) {
  let start_v = start.unwrap_or(0L)
  let stop_neg = match stop {
    None => true
    Some(x) => x < 0L
  }
  if start_v < 0L || stop_neg {
    let end = end().to_int64()
    let start = if start_v < 0L {
      let r = end + start_v
      if r < 0L {
        0L
      } else {
        r
      }
    } else {
      start_v
    }
    let stop = match stop {
      None => end
      Some(x) if x < 0L => {
        let r = end + x
        if r < 0L {
          0L
        } else {
          r
        }
      }
      Some(x) => x
    }
    let len = if stop > start { stop - start } else { 0L }
    (clamp_int(start), clamp_int(len))
  } else {
    let stop_v = stop.unwrap()
    let len = if stop_v > start_v { stop_v - start_v } else { 0L }
    (clamp_int(start_v), clamp_int(len))
  }
}

///|
fn clamp_int(v : Int64) -> Int {
  if v > 0x7FFFFFFFL {
    0x7FFFFFFF
  } else if v < -0x80000000L {
    -0x80000000
  } else {
    v.to_int()
  }
}

///|
/// Ceiling division for non-negative numbers without overflow.
fn ceil_div64(a : Int64, b : Int64) -> Int64 {
  a / b + (if a % b != 0L { 1L } else { 0L })
}

///|
/// Port of `range_step_backwards`: the indexes visited by a negative step.
/// `step` is the (positive) magnitude of the step.
fn range_step_backwards(
  start : Int64?,
  stop : Int64?,
  step : Int64,
  end : Int,
) -> Array[Int] {
  let end64 = end.to_int64()
  let last = if end64 > 0L { end64 - 1L } else { 0L }
  let start = match start {
    None => last
    Some(s) if s >= end64 => last
    Some(s) if s >= 0L => s
    Some(s) => {
      let r = end64 + s
      if r < 0L {
        0L
      } else {
        r
      }
    }
  }
  let stop = match stop {
    None => 0L
    Some(s) if s < 0L => {
      let r = end64 + s
      if r < 0L {
        0L
      } else {
        r
      }
    }
    Some(s) => s
  }
  let rv = []
  if stop > start {
    return rv
  }
  let length = if stop == 0L {
    start / step + 1L
  } else {
    ceil_div64(start - stop, step)
  }
  let mut i = start
  while i >= stop && rv.length().to_int64() < length {
    rv.push(i.to_int())
    if i < step {
      break
    }
    i -= step
  }
  rv
}

///|
fn[T] slice_array(
  items : Array[T],
  start : Int64?,
  stop : Int64?,
  step : Int64,
) -> Array[T] {
  if step > 0L {
    let (s, len) = get_offset_and_len(start, stop, () => items.length())
    let rv = []
    let n = items.length().to_int64()
    let s64 = s.to_int64()
    let end = if len.to_int64() > n - s64 { n } else { s64 + len.to_int64() }
    let mut i = s64
    while i < end {
      rv.push(items[i.to_int()])
      if step > end - i {
        break
      }
      i += step
    }
    rv
  } else {
    // `-step` would overflow for the minimum value
    let magnitude = if step == -0x7FFFFFFFFFFFFFFFL - 1L {
      0x7FFFFFFFFFFFFFFFL
    } else {
      -step
    }
    range_step_backwards(start, stop, magnitude, items.length()).map(i => {
      items[i]
    })
  }
}

///|
/// Slices a value.
fn value_slice(
  value : Value,
  start : Value,
  stop : Value,
  step : Value,
) -> Value raise TemplateError {
  let start = if start.is_none() {
    None
  } else {
    match start.as_i64() {
      Some(v) => Some(v)
      None => raise unsupported_conversion(start.kind(), "i64")
    }
  }
  let stop = if stop.is_none() {
    None
  } else {
    match stop.as_i64() {
      Some(v) => Some(v)
      None => raise unsupported_conversion(stop.kind(), "i64")
    }
  }
  let step = if step.is_none() {
    1L
  } else {
    match step.as_i64() {
      Some(v) => v
      None => raise unsupported_conversion(step.kind(), "i64")
    }
  }
  if step == 0L {
    raise TemplateError::new(InvalidOperation, "cannot slice by step size of 0")
  }
  let kind = value.kind()
  match value {
    Str(s, _) => {
      let chars = s.iter().to_array()
      Value::from_string(
        String::from_array(slice_array(chars, start, stop, step)),
      )
    }
    Bytes(b) => {
      let bytes = b.to_array()
      Value::from_bytes(
        Bytes::from_array(slice_array(bytes, start, stop, step)),
      )
    }
    Undefined(_) | NoneValue => Value::from_array([])
    Object(obj) if obj.repr() is (Seq | Iterable) => {
      if value.is_tuple() {
        let values = match obj.try_iter() {
          Some(iter) => iter.to_array()
          None => []
        }
        return Value::from_tuple(slice_array(values, start, stop, step))
      }
      if step > 0L {
        let total = obj.enumerator_len()
        let (s, len) = get_offset_and_len(start, stop, () => total.unwrap_or(0))
        let step = step.to_int()
        // like Rust's `skip(s).take(len).step_by(step)` the size is known if
        // the length of the underlying object is known.
        let size_hint = match total {
          Some(n) => {
            let avail = if n - s < 0 {
              0
            } else if n - s < len {
              n - s
            } else {
              len
            }
            Some((avail + step - 1) / step)
          }
          None => None
        }
        Value::make_iterable(() => {
          match obj.try_iter() {
            Some(iter) => {
              let mut skipped = false
              let mut remaining = len
              Iter::new(
                () => {
                  if !skipped {
                    skipped = true
                    for _ in 0.. Iter::empty()
          }
        })
      } else {
        Value::make_iterable(() => {
          match obj.try_iter() {
            Some(iter) => {
              let vec = iter.to_array()
              let magnitude = if step == -0x7FFFFFFFFFFFFFFFL - 1L {
                0x7FFFFFFFFFFFFFFFL
              } else {
                -step
              }
              range_step_backwards(start, stop, magnitude, vec.length())
              .map(i => vec[i])
              .iter()
            }
            None => Iter::empty()
          }
        })
      }
    }
    _ =>
      raise TemplateError::new(
        InvalidOperation,
        "value of type \{kind} cannot be sliced",
      )
  }
}

///|
fn unsupported_conversion(kind : ValueKind, target : String) -> TemplateError {
  TemplateError::new(InvalidOperation, "cannot convert \{kind} to \{target}")
}

///|
/// Number of bytes the string takes up as UTF-8.
fn utf8_len(s : String) -> Int {
  let mut n = 0
  for c in s {
    let cp = c.to_int()
    n += if cp < 0x80 {
      1
    } else if cp < 0x800 {
      2
    } else if cp < 0x10000 {
      3
    } else {
      4
    }
  }
  n
}

///|
/// Returns the value as `Int64` if it is a (signed or unsigned) 64 bit
/// integer that fits.  Used by arithmetic fast paths.
fn small_int(v : Value) -> Int64? {
  match v {
    I64(a) => Some(a)
    U64(a) if a <= 0x7FFFFFFFFFFFFFFFUL => Some(a.reinterpret_as_int64())
    _ => None
  }
}