diff --git a/src/_macros.rs b/src/_macros.rs index 9947d401c..779b63503 100644 --- a/src/_macros.rs +++ b/src/_macros.rs @@ -375,24 +375,16 @@ macro_rules! handle_metadata_return { } macro_rules! build_owned_tables { - ($name: ty, $deref: ident, $llname: ty, $init: ident, $free: ident, $clear: expr) => { + ($name: ty, $deref: ident, $lltype: ty, $tsktable: ty) => { impl $name { fn new() -> Self { - let temp = unsafe { libc::malloc(std::mem::size_of::<$llname>()) as *mut $llname }; - let nonnull = match std::ptr::NonNull::<$llname>::new(temp) { - Some(x) => x, - None => panic!("out of memory"), - }; - let mut table = unsafe { mbox::MBox::from_non_null_raw(nonnull) }; - let rv = unsafe { $init(&mut (*table), 0) }; - assert_eq!(rv, 0); + let table = <$lltype>::new(); Self { table } } /// Clear the table. pub fn clear(&mut self) -> $crate::TskReturnValue { - let rv = unsafe { $clear(self.as_mut_ptr()) }; - handle_tsk_return_value!(rv) + self.table.clear().map_err(|e| e.into()) } } @@ -420,22 +412,13 @@ macro_rules! build_owned_tables { } } - impl Drop for $name { - fn drop(&mut self) { - let rv = unsafe { $free(&mut (*self.table)) }; - if rv != 0 { - panic!("error when calling {}: {}", stringify!(free), rv); - } - } - } - impl $name { - pub fn as_ptr(&self) -> *const $llname { - &*self.table + pub fn as_ptr(&self) -> *const $tsktable { + self.table.as_ptr() } - pub fn as_mut_ptr(&mut self) -> *mut $llname { - &mut *self.table as *mut $llname + pub fn as_mut_ptr(&mut self) -> *mut $tsktable { + self.table.as_mut_ptr() } } }; @@ -451,7 +434,7 @@ macro_rules! node_table_add_row_details { $table: expr) => {{ let rv = unsafe { $crate::bindings::tsk_node_table_add_row( - &mut $table, + $table, $flags.into().bits(), $time.into().into(), $population.into().into(), @@ -532,7 +515,7 @@ macro_rules! edge_table_add_row_details { $table: expr) => {{ let rv = unsafe { $crate::bindings::tsk_edge_table_add_row( - &mut $table, + $table, $left.into().into(), $right.into().into(), $parent.into().into(), @@ -605,7 +588,7 @@ macro_rules! edge_table_add_row_with_metadata { macro_rules! population_table_add_row_details { ($metadata: expr, $metadata_len: expr, $table: expr) => {{ let rv = unsafe { - $crate::bindings::tsk_population_table_add_row(&mut $table, $metadata, $metadata_len) + $crate::bindings::tsk_population_table_add_row($table, $metadata, $metadata_len) }; handle_tsk_return_value!(rv, rv.into()) }}; @@ -640,7 +623,7 @@ macro_rules! individual_table_add_row_details { $table: expr) => {{ let rv = unsafe { $crate::bindings::tsk_individual_table_add_row( - &mut $table, + $table, $flags.into().bits(), $location.get_slice().as_ptr().cast::(), $location.get_slice().len() as $crate::bindings::tsk_size_t, @@ -715,7 +698,7 @@ macro_rules! mutation_table_add_row_details { let dstate = process_state_input!($derived_state); let rv = unsafe { $crate::bindings::tsk_mutation_table_add_row( - &mut $table, + $table, $site.into().into(), $node.into().into(), $parent.into().into(), @@ -796,7 +779,7 @@ macro_rules! site_table_add_row_details { let astate = process_state_input!($ancestral_state); let rv = unsafe { $crate::bindings::tsk_site_table_add_row( - &mut $table, + $table, $position.into().into(), astate.0, astate.1, @@ -853,7 +836,7 @@ macro_rules! migration_table_add_row_details { $table: expr) => {{ let rv = unsafe { $crate::bindings::tsk_migration_table_add_row( - &mut $table, + $table, $span.0.into().into(), $span.1.into().into(), $node.into().into(), @@ -928,7 +911,7 @@ macro_rules! provenance_table_add_row { let timestamp = humantime::format_rfc3339(std::time::SystemTime::now()).to_string(); let rv = unsafe { $crate::bindings::tsk_provenance_table_add_row( - &mut $table, + $table, timestamp.as_ptr() as *mut i8, timestamp.len() as tsk_size_t, record.as_ptr() as *mut i8, @@ -943,22 +926,18 @@ macro_rules! provenance_table_add_row { macro_rules! build_owned_table_type { ($(#[$attr:meta])* => $name: ident, $deref_type: ident, - $tskname: ident, - $tskinit: ident, - $tskfree: ident, - $tskclear: expr) => { + $lltype: ty, + $tsktable: ty) => { $(#[$attr])* pub struct $name { - table: mbox::MBox<$crate::bindings::$tskname>, + table: $lltype } build_owned_tables!( $name, $deref_type, - $crate::bindings::$tskname, - $tskinit, - $tskfree, - $tskclear + $lltype, + $tsktable ); }; } diff --git a/src/edge_table.rs b/src/edge_table.rs index c9f16b3f6..822b62b3d 100644 --- a/src/edge_table.rs +++ b/src/edge_table.rs @@ -4,7 +4,6 @@ use crate::sys; use crate::Position; use crate::{tsk_id_t, TskitError}; use crate::{EdgeId, NodeId}; -use ll_bindings::{tsk_edge_table_free, tsk_edge_table_init}; /// Row of an [`EdgeTable`] #[derive(Debug)] @@ -360,13 +359,11 @@ build_owned_table_type!( /// ``` => OwningEdgeTable, EdgeTable, - tsk_edge_table_t, - tsk_edge_table_init, - tsk_edge_table_free, - crate::bindings::tsk_edge_table_clear + crate::sys::LLOwningEdgeTable, + crate::bindings::tsk_edge_table_t ); impl OwningEdgeTable { - edge_table_add_row!(=> add_row, self, *self.table); - edge_table_add_row_with_metadata!(=> add_row_with_metadata, self, *self.table); + edge_table_add_row!(=> add_row, self, self.as_mut_ptr()); + edge_table_add_row_with_metadata!(=> add_row_with_metadata, self, self.as_mut_ptr()); } diff --git a/src/individual_table.rs b/src/individual_table.rs index f517440fc..383c0182d 100644 --- a/src/individual_table.rs +++ b/src/individual_table.rs @@ -5,7 +5,6 @@ use crate::IndividualFlags; use crate::IndividualId; use crate::Location; use crate::{tsk_id_t, TskitError}; -use ll_bindings::{tsk_individual_table_free, tsk_individual_table_init}; /// Row of a [`IndividualTable`] #[derive(Debug)] @@ -488,13 +487,11 @@ build_owned_table_type!( /// ``` => OwningIndividualTable, IndividualTable, - tsk_individual_table_t, - tsk_individual_table_init, - tsk_individual_table_free, - crate::bindings::tsk_individual_table_clear + crate::sys::LLOwningIndividualTable, + crate::bindings::tsk_individual_table_t ); impl OwningIndividualTable { - individual_table_add_row!(=> add_row, self, *self.table); - individual_table_add_row_with_metadata!(=> add_row_with_metadata, self, *self.table); + individual_table_add_row!(=> add_row, self, self.as_mut_ptr()); + individual_table_add_row_with_metadata!(=> add_row_with_metadata, self, self.as_mut_ptr()); } diff --git a/src/migration_table.rs b/src/migration_table.rs index b8a10f90d..e4a5efe50 100644 --- a/src/migration_table.rs +++ b/src/migration_table.rs @@ -6,7 +6,6 @@ use crate::SizeType; use crate::Time; use crate::{tsk_id_t, TskitError}; use crate::{MigrationId, NodeId, PopulationId}; -use ll_bindings::{tsk_migration_table_free, tsk_migration_table_init}; /// Row of a [`MigrationTable`] #[derive(Debug)] @@ -419,13 +418,11 @@ build_owned_table_type!( /// ``` => OwningMigrationTable, MigrationTable, - tsk_migration_table_t, - tsk_migration_table_init, - tsk_migration_table_free, - ll_bindings::tsk_migration_table_clear + crate::sys::LLOwningMigrationTable, + crate::bindings::tsk_migration_table_t ); impl OwningMigrationTable { - migration_table_add_row!(=> add_row, self, *self.table); - migration_table_add_row_with_metadata!(=> add_row_with_metadata, self, *self.table); + migration_table_add_row!(=> add_row, self, self.as_mut_ptr()); + migration_table_add_row_with_metadata!(=> add_row_with_metadata, self, self.as_mut_ptr()); } diff --git a/src/mutation_table.rs b/src/mutation_table.rs index 825a912ae..01ccfdce6 100644 --- a/src/mutation_table.rs +++ b/src/mutation_table.rs @@ -5,7 +5,6 @@ use crate::SizeType; use crate::Time; use crate::{tsk_id_t, TskitError}; use crate::{MutationId, NodeId, SiteId}; -use ll_bindings::{tsk_mutation_table_free, tsk_mutation_table_init}; /// Row of a [`MutationTable`] #[derive(Debug)] @@ -394,13 +393,11 @@ build_owned_table_type!( /// ``` => OwningMutationTable, MutationTable, - tsk_mutation_table_t, - tsk_mutation_table_init, - tsk_mutation_table_free, - ll_bindings::tsk_mutation_table_clear + crate::sys::LLOwningMutationTable, + crate::bindings::tsk_mutation_table_t ); impl OwningMutationTable { - mutation_table_add_row!(=> add_row, self, *self.table); - mutation_table_add_row_with_metadata!(=> add_row_with_metadata, self, *self.table); + mutation_table_add_row!(=> add_row, self, self.as_mut_ptr()); + mutation_table_add_row_with_metadata!(=> add_row_with_metadata, self, self.as_mut_ptr()); } diff --git a/src/node_table.rs b/src/node_table.rs index 84127ab92..c78dbdc47 100644 --- a/src/node_table.rs +++ b/src/node_table.rs @@ -6,7 +6,6 @@ use crate::SizeType; use crate::Time; use crate::{tsk_id_t, TskitError}; use crate::{IndividualId, NodeId, PopulationId}; -use ll_bindings::{tsk_node_table_free, tsk_node_table_init}; /// Row of a [`NodeTable`] #[derive(Debug)] @@ -583,15 +582,13 @@ build_owned_table_type!( /// ``` => OwningNodeTable, NodeTable, - tsk_node_table_t, - tsk_node_table_init, - tsk_node_table_free, - ll_bindings::tsk_node_table_clear + crate::sys::LLOwningNodeTable, + crate::bindings::tsk_node_table_t ); impl OwningNodeTable { - node_table_add_row!(=> add_row, self, (*self.table)); - node_table_add_row_with_metadata!(=> add_row_with_metadata, self, (*self.table)); + node_table_add_row!(=> add_row, self, self.as_mut_ptr()); + node_table_add_row_with_metadata!(=> add_row_with_metadata, self, self.as_mut_ptr()); } #[cfg(test)] diff --git a/src/population_table.rs b/src/population_table.rs index ae955c033..154aa3c36 100644 --- a/src/population_table.rs +++ b/src/population_table.rs @@ -5,7 +5,6 @@ use crate::tsk_id_t; use crate::PopulationId; use crate::SizeType; use crate::TskitError; -use ll_bindings::{tsk_population_table_free, tsk_population_table_init}; /// Row of a [`PopulationTable`] #[derive(Eq, Debug)] @@ -258,13 +257,11 @@ build_owned_table_type!( /// ``` => OwningPopulationTable, PopulationTable, - tsk_population_table_t, - tsk_population_table_init, - tsk_population_table_free, - ll_bindings::tsk_population_table_clear + crate::sys::LLOwningPopulationTable, + crate::bindings::tsk_population_table_t ); impl OwningPopulationTable { - population_table_add_row!(=> add_row, self, *self.table); - population_table_add_row_with_metadata!(=> add_row_with_metadata, self, *self.table); + population_table_add_row!(=> add_row, self, self.as_mut_ptr()); + population_table_add_row_with_metadata!(=> add_row_with_metadata, self, self.as_mut_ptr()); } diff --git a/src/provenance.rs b/src/provenance.rs index c832a0f4e..1bf5a7d95 100644 --- a/src/provenance.rs +++ b/src/provenance.rs @@ -14,7 +14,6 @@ use crate::bindings as ll_bindings; use crate::sys; use crate::SizeType; use crate::{tsk_id_t, tsk_size_t, ProvenanceId}; -use ll_bindings::{tsk_provenance_table_free, tsk_provenance_table_init}; #[derive(Eq, Debug)] /// Row of a [`ProvenanceTable`]. @@ -287,14 +286,12 @@ build_owned_table_type!( /// ``` => OwningProvenanceTable, ProvenanceTable, - tsk_provenance_table_t, - tsk_provenance_table_init, - tsk_provenance_table_free, - ll_bindings::tsk_provenance_table_clear + crate::sys::LLOwningProvenanceTable, + crate::bindings::tsk_provenance_table_t ); impl OwningProvenanceTable { - provenance_table_add_row!(=> add_row, self, *self.table); + provenance_table_add_row!(=> add_row, self, self.as_mut_ptr()); } #[cfg(test)] diff --git a/src/site_table.rs b/src/site_table.rs index 54b6b9beb..ebd0ea2c8 100644 --- a/src/site_table.rs +++ b/src/site_table.rs @@ -6,7 +6,6 @@ use crate::Position; use crate::SiteId; use crate::SizeType; use crate::TskitError; -use ll_bindings::{tsk_site_table_free, tsk_site_table_init}; /// Row of a [`SiteTable`] #[derive(Debug)] @@ -309,13 +308,11 @@ build_owned_table_type!( /// ``` => OwningSiteTable, SiteTable, - tsk_site_table_t, - tsk_site_table_init, - tsk_site_table_free, - ll_bindings::tsk_site_table_clear + crate::sys::LLOwningSiteTable, + crate::bindings::tsk_site_table_t ); impl OwningSiteTable { - site_table_add_row!(=> add_row, self, *self.table); - site_table_add_row_with_metadata!(=> add_row_with_metadata, self, *self.table); + site_table_add_row!(=> add_row, self, self.as_mut_ptr()); + site_table_add_row_with_metadata!(=> add_row_with_metadata, self, self.as_mut_ptr()); } diff --git a/src/sys.rs b/src/sys.rs index 60114db8f..1d75affdf 100644 --- a/src/sys.rs +++ b/src/sys.rs @@ -1,17 +1,51 @@ use std::ffi::CString; use std::ptr::NonNull; +use mbox::MBox; use thiserror::Error; use crate::bindings; + use bindings::tsk_edge_table_t; use bindings::tsk_individual_table_t; use bindings::tsk_migration_table_t; use bindings::tsk_mutation_table_t; use bindings::tsk_node_table_t; use bindings::tsk_population_table_t; +#[cfg(feature = "provenance")] +use bindings::tsk_provenance_table_t; use bindings::tsk_site_table_t; +use bindings::tsk_edge_table_init; +use bindings::tsk_individual_table_init; +use bindings::tsk_migration_table_init; +use bindings::tsk_mutation_table_init; +use bindings::tsk_node_table_init; +use bindings::tsk_population_table_init; +#[cfg(feature = "provenance")] +use bindings::tsk_provenance_table_init; +use bindings::tsk_site_table_init; + +use bindings::tsk_edge_table_free; +use bindings::tsk_individual_table_free; +use bindings::tsk_migration_table_free; +use bindings::tsk_mutation_table_free; +use bindings::tsk_node_table_free; +use bindings::tsk_population_table_free; +#[cfg(feature = "provenance")] +use bindings::tsk_provenance_table_free; +use bindings::tsk_site_table_free; + +use bindings::tsk_edge_table_clear; +use bindings::tsk_individual_table_clear; +use bindings::tsk_migration_table_clear; +use bindings::tsk_mutation_table_clear; +use bindings::tsk_node_table_clear; +use bindings::tsk_population_table_clear; +#[cfg(feature = "provenance")] +use bindings::tsk_provenance_table_clear; +use bindings::tsk_site_table_clear; + #[non_exhaustive] #[derive(Error, Debug)] pub enum Error { @@ -21,9 +55,6 @@ pub enum Error { Code(i32), } -#[cfg(feature = "provenance")] -use bindings::tsk_provenance_table_t; - macro_rules! basic_lltableref_impl { ($lltable: ident, $tsktable: ident) => { #[repr(transparent)] @@ -60,6 +91,119 @@ basic_lltableref_impl!(LLIndividualTableRef, tsk_individual_table_t); #[cfg(feature = "provenance")] basic_lltableref_impl!(LLProvenanceTableRef, tsk_provenance_table_t); +macro_rules! basic_llowningtable_impl { + ($llowningtable: ident, $tsktable: ident, $init: ident, $free: ident, $clear: ident) => { + #[repr(transparent)] + #[derive(Debug)] + pub struct $llowningtable(MBox<$tsktable>); + + impl $llowningtable { + pub fn new() -> Self { + let temp = + unsafe { libc::malloc(std::mem::size_of::<$tsktable>()) as *mut $tsktable }; + let nonnull = match std::ptr::NonNull::<$tsktable>::new(temp) { + Some(x) => x, + None => panic!("out of memory"), + }; + let mut table = unsafe { mbox::MBox::from_non_null_raw(nonnull) }; + let rv = unsafe { $init(&mut (*table), 0) }; + assert_eq!(rv, 0); + Self(table) + } + + pub fn as_ptr(&self) -> *const $tsktable { + MBox::<$tsktable>::as_ptr(&self.0) + } + + pub fn as_mut_ptr(&mut self) -> *mut $tsktable { + MBox::<$tsktable>::as_mut_ptr(&mut self.0) + } + + fn free(&mut self) -> Result<(), Error> { + match unsafe { $free(self.as_mut_ptr()) } { + code if code < 0 => Err(Error::Code(code)), + _ => Ok(()), + } + } + + pub fn clear(&mut self) -> Result { + match unsafe { $clear(self.as_mut_ptr()) } { + code if code < 0 => Err(Error::Code(code)), + code => Ok(code), + } + } + } + + impl Drop for $llowningtable { + fn drop(&mut self) { + match self.free() { + Ok(_) => (), + Err(e) => panic!("{}", e), + } + } + } + }; +} + +basic_llowningtable_impl!( + LLOwningEdgeTable, + tsk_edge_table_t, + tsk_edge_table_init, + tsk_edge_table_free, + tsk_edge_table_clear +); +basic_llowningtable_impl!( + LLOwningNodeTable, + tsk_node_table_t, + tsk_node_table_init, + tsk_node_table_free, + tsk_node_table_clear +); +basic_llowningtable_impl!( + LLOwningSiteTable, + tsk_site_table_t, + tsk_site_table_init, + tsk_site_table_free, + tsk_site_table_clear +); +basic_llowningtable_impl!( + LLOwningMutationTable, + tsk_mutation_table_t, + tsk_mutation_table_init, + tsk_mutation_table_free, + tsk_mutation_table_clear +); +basic_llowningtable_impl!( + LLOwningIndividualTable, + tsk_individual_table_t, + tsk_individual_table_init, + tsk_individual_table_free, + tsk_individual_table_clear +); +basic_llowningtable_impl!( + LLOwningMigrationTable, + tsk_migration_table_t, + tsk_migration_table_init, + tsk_migration_table_free, + tsk_migration_table_clear +); +basic_llowningtable_impl!( + LLOwningPopulationTable, + tsk_population_table_t, + tsk_population_table_init, + tsk_population_table_free, + tsk_population_table_clear +); + +#[cfg(feature = "provenance")] +basic_llowningtable_impl!( + LLOwningProvenanceTable, + tsk_provenance_table_t, + tsk_provenance_table_init, + tsk_provenance_table_free, + tsk_provenance_table_clear +); + #[repr(transparent)] pub struct LLTreeSeq(bindings::tsk_treeseq_t); diff --git a/src/table_collection.rs b/src/table_collection.rs index 0f3e6c817..ac069f8e4 100644 --- a/src/table_collection.rs +++ b/src/table_collection.rs @@ -259,7 +259,7 @@ impl TableCollection { /// /// See [`TableCollection::check_integrity`] for how to catch these data model /// violations. - => add_edge, self, self.inner.edges); + => add_edge, self, &mut(*self.as_mut_ptr()).edges); edge_table_add_row_with_metadata!( /// Add a row with optional metadata to the edge table @@ -282,7 +282,7 @@ impl TableCollection { /// assert!(tables.add_edge_with_metadata(0., 53., 1, 11, &metadata).is_ok()); /// # } /// ``` - => add_edge_with_metadata, self, self.inner.edges); + => add_edge_with_metadata, self, &mut(*self.as_mut_ptr()).edges); individual_table_add_row!( /// Add a row to the individual table @@ -321,7 +321,7 @@ impl TableCollection { /// # None => panic!("expected parents"), /// # } /// ``` - => add_individual, self, self.inner.individuals); + => add_individual, self, &mut (*self.as_mut_ptr()).individuals); individual_table_add_row_with_metadata!( /// Add a row with metadata to the individual table @@ -347,7 +347,7 @@ impl TableCollection { /// # let decoded = tables.individuals().metadata::(0.into()).unwrap().unwrap(); /// # assert_eq!(decoded.x, 1); /// # } - => add_individual_with_metadata, self, self.inner.individuals); + => add_individual_with_metadata, self, &mut (*self.as_mut_ptr()).individuals); migration_table_add_row!( /// Add a row to the migration table @@ -365,7 +365,7 @@ impl TableCollection { /// (0, 1), /// 53.5).is_ok()); /// ``` - => add_migration, self, self.inner.migrations); + => add_migration, self, &mut (*self.as_mut_ptr()).migrations); migration_table_add_row_with_metadata!( /// Add a row with optional metadata to the migration table @@ -397,15 +397,13 @@ impl TableCollection { /// /// Migration tables are not currently supported /// by tree sequence simplification. - => add_migration_with_metadata, self, self.inner.migrations); + => add_migration_with_metadata, self, &mut (*self.as_mut_ptr()).migrations); node_table_add_row!( /// Add a row to the node table - => add_node, self, self.inner.nodes - ); + => add_node, self, &mut (*self.as_mut_ptr()).nodes); node_table_add_row_with_metadata!( - /// Add a row with optional metadata to the node table /// /// # Examples @@ -426,11 +424,11 @@ impl TableCollection { /// assert!(tables.add_node_with_metadata(0, 0.0, -1, -1, &metadata).is_ok()); /// # } /// ``` - => add_node_with_metadata, self, self.inner.nodes); + => add_node_with_metadata, self, &mut (*self.as_mut_ptr()).nodes); site_table_add_row!( /// Add a row to the site table - => add_site, self, self.inner.sites); + => add_site, self, &mut (*self.as_mut_ptr()).sites); site_table_add_row_with_metadata!( /// Add a row with optional metadata to the site table @@ -455,11 +453,11 @@ impl TableCollection { /// &metadata).is_ok()); /// # } /// ``` - => add_site_with_metadata, self, self.inner.sites); + => add_site_with_metadata, self, &mut (*self.as_mut_ptr()).sites); mutation_table_add_row!( /// Add a row to the mutation table. - => add_mutation, self, self.inner.mutations); + => add_mutation, self, &mut (*self.as_mut_ptr()).mutations); mutation_table_add_row_with_metadata!( /// Add a row with optional metadata to the mutation table. @@ -483,7 +481,7 @@ impl TableCollection { /// &metadata).is_ok()); /// # } /// ``` - => add_mutation_with_metadata, self, self.inner.mutations); + => add_mutation_with_metadata, self, &mut (*self.as_mut_ptr()).mutations); population_table_add_row!( /// Add a row to the population_table @@ -494,7 +492,7 @@ impl TableCollection { /// # let mut tables = tskit::TableCollection::new(55.0).unwrap(); /// tables.add_population().unwrap(); /// ``` - => add_population, self, self.inner.populations); + => add_population, self, &mut (*self.as_mut_ptr()).populations); population_table_add_row_with_metadata!( /// Add a row with optional metadata to the population_table @@ -516,7 +514,7 @@ impl TableCollection { /// let metadata = PopulationMetadata{x: 1}; /// assert!(tables.add_population_with_metadata(&metadata).is_ok()); /// # } - => add_population_with_metadata, self, self.inner.populations); + => add_population_with_metadata, self, &mut (*self.as_mut_ptr()).populations); /// Build the "input" and "output" /// indexes for the edge table. @@ -900,7 +898,7 @@ impl TableCollection { /// assert_eq!(treeseq.provenances().record(0).unwrap(), row_0.record); /// # } /// ``` - => add_provenance, self, self.inner.provenances); + => add_provenance, self, &mut (*self.as_mut_ptr()).provenances); /// Set the edge table from an [`OwningEdgeTable`](`crate::OwningEdgeTable`) ///