///|
fn int64_max() -> Int64 {
  9223372036854775807L
}

///|
fn int64_min() -> Int64 {
  -9223372036854775807L - 1L
}

///|
fn uint64_max() -> UInt64 {
  18446744073709551615UL
}

///|
fn safe_negate_int(value : Int64, span : SourceSpan) -> Value raise EvalError {
  if value == int64_min() {
    raise NumericOverflow(span~)
  }
  Value::Int(-value)
}

///|
fn safe_add_int(
  left : Int64,
  right : Int64,
  span : SourceSpan,
) -> Value raise EvalError {
  if (right > 0L && left > int64_max() - right) ||
    (right < 0L && left < int64_min() - right) {
    raise NumericOverflow(span~)
  }
  Value::Int(left + right)
}

///|
fn safe_add_uint(
  left : UInt64,
  right : UInt64,
  span : SourceSpan,
) -> Value raise EvalError {
  if left > uint64_max() - right {
    raise NumericOverflow(span~)
  }
  Value::UInt(left + right)
}

///|
fn safe_subtract_int(
  left : Int64,
  right : Int64,
  span : SourceSpan,
) -> Value raise EvalError {
  if (right < 0L && left > int64_max() + right) ||
    (right > 0L && left < int64_min() + right) {
    raise NumericOverflow(span~)
  }
  Value::Int(left - right)
}

///|
fn safe_subtract_uint(
  left : UInt64,
  right : UInt64,
  span : SourceSpan,
) -> Value raise EvalError {
  if left < right {
    raise NumericOverflow(span~)
  }
  Value::UInt(left - right)
}

///|
fn int_multiply_overflows(left : Int64, right : Int64) -> Bool {
  if left == 0L || right == 0L {
    return false
  }
  if (left == int64_min() && right == -1L) ||
    (right == int64_min() && left == -1L) {
    return true
  }
  if left > 0L {
    if right > 0L {
      left > int64_max() / right
    } else {
      right < int64_min() / left
    }
  } else if right > 0L {
    left < int64_min() / right
  } else {
    right < int64_max() / left
  }
}

///|
fn safe_multiply_int(
  left : Int64,
  right : Int64,
  span : SourceSpan,
) -> Value raise EvalError {
  if int_multiply_overflows(left, right) {
    raise NumericOverflow(span~)
  }
  Value::Int(left * right)
}

///|
fn safe_multiply_uint(
  left : UInt64,
  right : UInt64,
  span : SourceSpan,
) -> Value raise EvalError {
  if right != 0UL && left > uint64_max() / right {
    raise NumericOverflow(span~)
  }
  Value::UInt(left * right)
}

///|
fn safe_divide_int(
  left : Int64,
  right : Int64,
  span : SourceSpan,
) -> Value raise EvalError {
  if right == 0L {
    raise DivisionByZero(span~)
  }
  if left == int64_min() && right == -1L {
    raise NumericOverflow(span~)
  }
  Value::Int(left / right)
}

///|
fn safe_modulo_int(
  left : Int64,
  right : Int64,
  span : SourceSpan,
) -> Value raise EvalError {
  if right == 0L {
    raise DivisionByZero(span~)
  }
  if left == int64_min() && right == -1L {
    return Value::Int(0L)
  }
  Value::Int(left % right)
}