From c2251f7a4c1ff5f459a95264f00fdc4cfa247121 Mon Sep 17 00:00:00 2001 From: Gavin Tran Date: Sat, 18 Mar 2023 14:44:03 -0700 Subject: [PATCH] Clean up and remove mockall. (#9) --- Cargo.lock | 146 +-------------------------------------- Cargo.toml | 5 +- src/chain.rs | 13 ++-- src/groups.rs | 52 +++++++------- src/input.rs | 11 +-- src/main.rs | 5 +- src/molecule.rs | 123 ++++++++++++++++----------------- src/pointer.rs | 103 ++++++---------------------- src/spatial.rs | 169 +++++++++++++++++++++++++--------------------- src/validation.rs | 6 +- 10 files changed, 212 insertions(+), 421 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d71b168..c24c95d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,15 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "aho-corasick" -version = "0.7.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" -dependencies = [ - "memchr", -] - [[package]] name = "autocfg" version = "1.1.0" @@ -37,9 +28,8 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chemcreator" -version = "1.1.0" +version = "0.2.0" dependencies = [ - "mockall", "ruscii", "thiserror", ] @@ -92,39 +82,6 @@ dependencies = [ "x11", ] -[[package]] -name = "difflib" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" - -[[package]] -name = "downcast" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" - -[[package]] -name = "either" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" - -[[package]] -name = "float-cmp" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" -dependencies = [ - "num-traits", -] - -[[package]] -name = "fragile" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa" - [[package]] name = "fuchsia-zircon" version = "0.3.3" @@ -156,15 +113,6 @@ dependencies = [ "libc", ] -[[package]] -name = "itertools" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] - [[package]] name = "kernel32-sys" version = "0.2.2" @@ -205,12 +153,6 @@ dependencies = [ "cfg-if 1.0.0", ] -[[package]] -name = "memchr" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" - [[package]] name = "mio" version = "0.6.23" @@ -242,33 +184,6 @@ dependencies = [ "ws2_32-sys", ] -[[package]] -name = "mockall" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50e4a1c770583dac7ab5e2f6c139153b783a53a1bbee9729613f193e59828326" -dependencies = [ - "cfg-if 1.0.0", - "downcast", - "fragile", - "lazy_static", - "mockall_derive", - "predicates", - "predicates-tree", -] - -[[package]] -name = "mockall_derive" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "832663583d5fa284ca8810bf7015e46c9fff9622d3cf34bd1eea5003fec06dd0" -dependencies = [ - "cfg-if 1.0.0", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "net2" version = "0.2.38" @@ -280,12 +195,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "normalize-line-endings" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" - [[package]] name = "num" version = "0.2.1" @@ -393,36 +302,6 @@ version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" -[[package]] -name = "predicates" -version = "2.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59230a63c37f3e18569bdb90e4a89cbf5bf8b06fea0b84e65ea10cc4df47addd" -dependencies = [ - "difflib", - "float-cmp", - "itertools", - "normalize-line-endings", - "predicates-core", - "regex", -] - -[[package]] -name = "predicates-core" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72f883590242d3c6fc5bf50299011695fa6590c2c70eac95ee1bdb9a733ad1a2" - -[[package]] -name = "predicates-tree" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54ff541861505aabf6ea722d2131ee980b8276e10a1297b94e896dd8b621850d" -dependencies = [ - "predicates-core", - "termtree", -] - [[package]] name = "proc-macro2" version = "1.0.51" @@ -459,23 +338,6 @@ version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" -[[package]] -name = "regex" -version = "1.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.6.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" - [[package]] name = "ruscii" version = "0.3.2" @@ -539,12 +401,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "termtree" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95059e91184749cb66be6dc994f67f182b6d897cb3df74a5bf66b5e709295fd8" - [[package]] name = "thiserror" version = "1.0.38" diff --git a/Cargo.toml b/Cargo.toml index 1eac351..07467ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "chemcreator" -version = "1.1.0" +version = "0.2.0" description = "A text-based tool for identifying organic molecules." authors = ["Gavin Tran"] readme = "README.md" @@ -13,6 +13,3 @@ repository = "https://github.com/pumken/chemcreator" [dependencies] ruscii = "0.3.2" thiserror = "1.0.38" - -[dev-dependencies] -mockall = "0.11.3" diff --git a/src/chain.rs b/src/chain.rs index 3a251ed..0dbd3e0 100644 --- a/src/chain.rs +++ b/src/chain.rs @@ -11,11 +11,6 @@ use crate::pointer::Pointer; use crate::spatial::GridState; use ruscii::spatial::Vec2; -pub(crate) fn debug_chain(graph: &GridState) -> Fallible> { - let all_chains = get_all_chains(graph)?; - longest_chain(all_chains) -} - /// Gets the longest of the given [`Vec`] of chains, assuming that it is non-empty. /// /// ## Errors @@ -122,7 +117,7 @@ fn create_branches( /// If one of the bonds to the current cell is found to be dangling, an /// [`IncompleteBond`] will be returned. fn next_carbons(pos: Vec2, previous_pos: Option, graph: &GridState) -> Fallible> { - let ptr = Pointer { graph, pos }; + let ptr = Pointer::new(graph, pos); let mut out = ptr.bonded_carbons()?; if let Some(it) = previous_pos { @@ -146,7 +141,7 @@ pub(crate) fn endpoint_carbons(graph: &GridState) -> Fallible> { let mut out = vec![]; for carbon in all_carbons { - let ptr = Pointer::new(carbon, graph); + let ptr = Pointer::new(graph, carbon.pos()); if ptr.bonded_carbon_count()? <= 1 { out.push(carbon); } @@ -168,7 +163,7 @@ pub(crate) fn endpoint_carbons(graph: &GridState) -> Fallible> { pub(crate) fn get_connected_cells(pos: Vec2, graph: &GridState) -> Fallible>> { if let Cell::None(_) = graph .get(pos) - .expect("pos should be a valid point on the graph.") + .expect("pos should be a valid point on the graph") { panic!( "Passed empty cell ({}, {}) to get_connected_cells", @@ -185,7 +180,7 @@ pub(crate) fn get_connected_cells(pos: Vec2, graph: &GridState) -> Fallible Fallible<()> { accumulator[pos.x as usize][pos.y as usize] = true; - let ptr = Pointer { graph, pos }; + let ptr = Pointer::new(graph, pos); for cell in ptr.connected() { if let Some(it) = previous_pos { if cell.pos() == it { diff --git a/src/groups.rs b/src/groups.rs index 212c158..aea70ff 100644 --- a/src/groups.rs +++ b/src/groups.rs @@ -11,6 +11,8 @@ use crate::spatial::{FromVec2, GridState}; use ruscii::spatial::{Direction, Vec2}; use thiserror::Error; +/// Generates a [`Branch`] from the given `chain` containing all functional groups attached +/// to it. pub(crate) fn link_groups(graph: &GridState, chain: Vec) -> Fallible { let mut branch = Branch::new(chain); @@ -50,11 +52,10 @@ pub(crate) fn debug_branches(graph: &GridState) -> Fallible { /// /// If a given [`GroupNode`] is not recognized, [`UnrecognizedGroup`] will be returned. fn convert_nodes(group_nodes: Vec) -> Fallible> { - let mut groups = vec![]; - - for node in group_nodes { - groups.push(identify_single_bond_group(node)?) - } + let groups = group_nodes + .into_iter() + .map(identify_single_bond_group) + .collect::>>()?; let new_groups = group_patterns(groups); @@ -68,13 +69,12 @@ fn convert_nodes(group_nodes: Vec) -> Fallible> { /// Returns [`UnrecognizedGroup`] if the structure is not valid. fn identify_single_bond_group(node: GroupNode) -> Fallible { let string = node.to_string(); - let id = string.as_str(); - - let out = match id { + let out = match string.as_str() { "1O(1H)" => Hydroxyl, "2O" => Carbonyl, _ => return Err(UnrecognizedGroup), }; + Ok(out) } @@ -90,6 +90,7 @@ fn group_patterns(mut groups: Vec) -> Vec { } break; } + let mut rest = groups .into_iter() .map(Substituent::Group) @@ -115,7 +116,7 @@ pub(crate) fn group_node_tree( pos: Vec2, direction: Direction, ) -> Fallible { - let ptr = Pointer { graph, pos }; + let ptr = Pointer::new(graph, pos); let bond = ptr.bond_order(direction).unwrap(); let atom = ptr.traverse_bond(direction)?; let mut next = vec![]; @@ -140,18 +141,22 @@ pub(crate) fn group_node_tree( /// points to a valid [`Cell::Atom`], and that there are no dangling bonds. If any of these /// contracts are broken, this function will panic. fn next_directions(graph: &GridState, pos: Vec2, previous_pos: Vec2) -> Fallible> { - let ptr = Pointer { graph, pos }; - let bonded = ptr.bonded()?; - let mut out = vec![]; - - for atom in bonded { - let result = match Direction::from_points(pos, atom.pos) { - Ok(it) => it, - Err(_) => return Err(Other("An unexpected error occurred (G151).")), - }; - out.push(result) - } - out.retain(|&dir| dir != Direction::from_points(pos, previous_pos).unwrap()); + let ptr = Pointer::new(graph, pos); + let out = ptr + .bonded()? + .into_iter() + .map(|atom| { + Direction::from_points(pos, atom.pos) + .map_err(|_| Other("An unexpected error occurred (groups/next_directions).")) + }) + .filter(|dir| { + if let Ok(it) = dir { + it != &Direction::from_points(pos, previous_pos).unwrap() + } else { + true + } + }) + .collect::>>()?; Ok(out) } @@ -168,10 +173,7 @@ fn group_directions( accumulator: &Branch, index: usize, ) -> Fallible> { - let ptr = Pointer { - graph, - pos: accumulator.chain[index].pos, - }; + let ptr = Pointer::new(graph, accumulator.chain[index].pos); let directions = ptr .connected_directions() .into_iter() diff --git a/src/input.rs b/src/input.rs index 323ba2b..10f73e4 100644 --- a/src/input.rs +++ b/src/input.rs @@ -2,7 +2,6 @@ //! //! The `input` module contains functions that interpret user input. -use crate::chain::debug_chain; use crate::groups::debug_branches; use crate::molecule::BondOrder::{Double, Single, Triple}; use crate::molecule::Element::{C, H, O}; @@ -28,14 +27,6 @@ pub(crate) fn input_insert_mode(app_state: &State, state: &mut AppState, graph: state.key = "O"; } KeyEvent::Pressed(Key::F5) => { - state.debug = match debug_chain(graph) { - Ok(it) => it.iter().fold("".to_string(), |a, b| { - format!("{a} ({}, {}),", b.pos.x, b.pos.y) - }), - Err(it) => it.to_string(), - }; - } - KeyEvent::Pressed(Key::F6) => { state.debug = match debug_branches(graph) { Ok(it) => it.to_string(), Err(it) => it.to_string(), @@ -54,7 +45,7 @@ pub(crate) fn input_insert_mode(app_state: &State, state: &mut AppState, graph: state.key = "3"; } KeyEvent::Pressed(Key::Backspace) => { - graph.clear(); + graph.clear_cell(); state.key = "Backspace"; } KeyEvent::Pressed(Key::Right) => { diff --git a/src/main.rs b/src/main.rs index f5103e2..cf662e7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -42,7 +42,10 @@ fn main() { Err(it) => ("unidentified".to_string(), it.to_string()), }; state.pos = format!("({}, {})", graph.cursor.x, graph.cursor.y); - state.sym = match graph.current_cell() { + state.sym = match graph + .current_cell() + .expect("current cell should be within bounds") + { Cell::Atom(it) => { format!("Atom {}", it.symbol()) } diff --git a/src/molecule.rs b/src/molecule.rs index 8017963..aefc92e 100644 --- a/src/molecule.rs +++ b/src/molecule.rs @@ -5,10 +5,10 @@ use crate::molecule::BondOrder::{Double, Single, Triple}; use crate::molecule::BondOrientation::{Horiz, Vert}; use crate::molecule::Element::{C, H, O}; -use crate::spatial::Cellular; use ruscii::spatial::{Direction, Vec2}; use ruscii::terminal::Color; use ruscii::terminal::Color::{LightGrey, Red, White}; +use std::fmt; use std::fmt::{Display, Formatter}; /// Represents a type of functional group on a molecule. @@ -41,9 +41,9 @@ pub enum Group { Ether, } -impl ToString for Group { - fn to_string(&self) -> String { - match *self { +impl Display for Group { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> { + let str = match *self { Group::Methyl => "methyl", Group::Ethyl => "ethyl", Group::Propyl => "propyl", @@ -64,8 +64,8 @@ impl ToString for Group { Group::Carboxyl => "carboxyl", Group::Ester => "ester", Group::Ether => "ether", - } - .to_string() + }; + write!(f, "{str}") } } @@ -89,7 +89,7 @@ impl Cell { } } - pub(crate) fn pos(&self) -> Vec2 { + pub fn pos(&self) -> Vec2 { match self { Cell::Atom(it) => it.pos, Cell::Bond(it) => it.pos, @@ -97,19 +97,19 @@ impl Cell { } } - pub(crate) fn is_atom(&self) -> bool { + pub fn is_atom(&self) -> bool { matches!(self, Cell::Atom(_)) } } #[derive(Clone, Debug, PartialEq)] pub struct Atom { - pub(crate) element: Element, - pub(crate) pos: Vec2, + pub element: Element, + pub pos: Vec2, } impl Atom { - pub(crate) const fn symbol(&self) -> &str { + pub const fn symbol(&self) -> &str { match self.element { C => "[C]", H => "[H]", @@ -127,7 +127,7 @@ pub enum Element { impl Element { /// Returns the number of bonds the current [`Element`] should have. - pub(crate) const fn bond_number(&self) -> u8 { + pub const fn bond_number(&self) -> u8 { match *self { C => 4, H => 1, @@ -135,7 +135,8 @@ impl Element { } } - pub(crate) const fn id(&self) -> &str { + /// Returns the atomic symbol of the [`Element`]. + pub const fn id(&self) -> &str { match *self { C => "C", H => "H", @@ -145,7 +146,7 @@ impl Element { } impl Display for Element { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!( f, "{}", @@ -158,15 +159,15 @@ impl Display for Element { } } -#[derive(Copy, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub struct Bond { - pub(crate) pos: Vec2, - pub(crate) order: BondOrder, - pub(crate) orient: BondOrientation, + pub pos: Vec2, + pub order: BondOrder, + pub orient: BondOrientation, } impl Bond { - pub(crate) fn symbol(&self) -> &str { + pub fn symbol(&self) -> &str { match (&self.order, &self.orient) { (Single, Horiz) => "———", (Single, Vert) => " | ", @@ -178,12 +179,6 @@ impl Bond { } } -impl Cellular for Bond { - fn pos(&self) -> Vec2 { - self.pos - } -} - #[derive(Clone, Copy, Debug, PartialEq)] pub enum BondOrder { Single, @@ -206,7 +201,7 @@ impl BondOrder { } #[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub(crate) enum BondOrientation { +pub enum BondOrientation { Vert, Horiz, } @@ -226,49 +221,49 @@ impl BondOrientation { } } -impl Clone for Bond { - fn clone(&self) -> Bond { - Bond { - pos: self.pos, - order: self.order, - orient: self.orient, - } - } -} - -#[derive(Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub enum Substituent { Branch(Branch), Group(Group), } -impl ToString for Substituent { - fn to_string(&self) -> String { - match self { - Substituent::Branch(_) => "".to_string(), - Substituent::Group(it) => it.to_string(), - } +impl Display for Substituent { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> { + write!( + f, + "{}", + match self { + Substituent::Branch(_) => "".to_string(), + Substituent::Group(it) => it.to_string(), + } + ) } } -#[derive(Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub struct Branch { pub chain: Vec, pub groups: Vec>, } -impl ToString for Branch { - fn to_string(&self) -> String { - let mut index = 0; - self.groups.iter().fold("".to_string(), |a, b| { - let str = format!( - "{a}{index}: {} ", - b.iter() - .fold("".to_string(), |c, d| { format!("{c}{} ", d.to_string()) }) - ); - index += 1; - str - }) +impl Display for Branch { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> { + let out = self + .groups + .iter() + .enumerate() + .map(|(index, it)| { + format!( + "{index}: {}", + it.iter() + .map(|it| it.to_string()) + .collect::>() + .join(", ") + ) + }) + .collect::>() + .join("; "); + write!(f, "{out}") } } @@ -291,14 +286,14 @@ pub struct GroupNode { pub next: Vec, } -impl ToString for GroupNode { - fn to_string(&self) -> String { - let start = format!("{}{}", self.bond.id(), self.atom.id()); +impl Display for GroupNode { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> { + let primary = format!("{}{}", self.bond.id(), self.atom.id()); - let mut mid = self.next.clone(); - mid.sort_by_key(|a| a.to_string()); - mid.iter() - .fold(start, |a, b| format!("{a}({})", b.to_string())) + let mut tree = self.next.clone(); + tree.sort_by_key(|node| node.to_string()); + let out = tree.iter().fold(primary, |a, b| format!("{a}({b})")); + write!(f, "{}", out) } } @@ -366,7 +361,7 @@ mod tests { ], }; - assert_eq!(branch.to_string(), "0: hydroxyl carbonyl 1: bromo ") + assert_eq!(branch.to_string(), "0: hydroxyl, carbonyl; 1: bromo") } #[test] diff --git a/src/pointer.rs b/src/pointer.rs index c50754d..8e71343 100644 --- a/src/pointer.rs +++ b/src/pointer.rs @@ -16,17 +16,15 @@ use ruscii::spatial::{Direction, Vec2}; /// /// > There are only two hard things in Computer Science: cache invalidation and naming things. /// > — Phil Karlton +#[derive(Debug, Clone)] pub(crate) struct Pointer<'a> { pub(crate) graph: &'a GridState, pub(crate) pos: Vec2, } impl<'a> Pointer<'a> { - pub(crate) fn new(cell: &Cell, graph: &'a GridState) -> Pointer<'a> { - Pointer { - graph, - pos: cell.pos(), - } + pub(crate) fn new(graph: &'a GridState, pos: Vec2) -> Pointer<'a> { + Pointer { graph, pos } } /// Returns a reference to the cell currently pointed to. @@ -168,10 +166,7 @@ impl<'a> Pointer<'a> { panic!("Cannot pass Direction::None to traverse_bond"); } - let mut traversal_ptr = Pointer { - graph: self.graph, - pos: self.pos, - }; + let mut traversal_ptr = self.clone(); loop { traversal_ptr.move_ptr(direction); @@ -198,10 +193,7 @@ impl<'a> Pointer<'a> { /// If the current cell is not a [`Cell::Atom`] or [`Direction::None`] is given, this function /// will panic. pub fn bond_order(&self, direction: Direction) -> Option { - let mut traversal_ptr = Pointer { - graph: self.graph, - pos: self.pos, - }; + let mut traversal_ptr = self.clone(); if let Direction::None = direction { panic!("Cannot pass Direction::None to bond_order") @@ -289,10 +281,7 @@ mod tests { #[test] fn borrow_returns_correct_cell() { let graph = graph_with!(2, 2); - let ptr = Pointer { - graph: &graph, - pos: Vec2::xy(1, 1), - }; + let ptr = Pointer::new(&graph, Vec2::xy(1, 1)); let cell = ptr.borrow().unwrap(); assert_eq!(cell, graph.get(Vec2::xy(1, 1)).unwrap()) @@ -301,10 +290,7 @@ mod tests { #[test] fn borrow_out_of_bounds_returns_err() { let graph = graph_with!(1, 1); - let ptr = Pointer { - graph: &graph, - pos: Vec2::xy(-1, -1), - }; + let ptr = Pointer::new(&graph, Vec2::xy(-1, -1)); let cell = ptr.borrow(); assert!(matches!(cell, Err(_))) @@ -318,10 +304,7 @@ mod tests { [1, 1; A(C)], [2, 1; B(Single)] ); - let ptr = Pointer { - graph: &graph, - pos: Vec2::xy(1, 1), - }; + let ptr = Pointer::new(&graph, Vec2::xy(1, 1)); let a = ptr.connected(); let b = vec![ graph.get(Vec2::xy(1, 0)).unwrap(), @@ -339,10 +322,7 @@ mod tests { [1, 0; A(H)], [0, 1; B(Triple)] ); - let ptr = Pointer { - graph: &graph, - pos: Vec2::zero(), - }; + let ptr = Pointer::new(&graph, Vec2::zero()); let a = ptr.connected(); let b = vec![ graph.get(Vec2::xy(0, 1)).unwrap(), @@ -358,10 +338,7 @@ mod tests { [0, 1; A(C)], [1, 0; A(H)], [1, 1; A(C)], [1, 2; B(Double)], [1, 3; A(O)] ); - let ptr = Pointer { - graph: &graph, - pos: Vec2::xy(1, 1), - }; + let ptr = Pointer::new(&graph, Vec2::xy(1, 1)); let directions = ptr.connected_directions(); assert_eq!( @@ -381,10 +358,7 @@ mod tests { [2, 3; B(Double)], [2, 4; A(O)] ); - let ptr = Pointer { - graph: &graph, - pos: Vec2::xy(2, 2), - }; + let ptr = Pointer::new(&graph, Vec2::xy(2, 2)); let a = ptr.bonded().unwrap(); let b = vec![ graph.get(Vec2::xy(2, 4)).unwrap(), // ordered by direction u/d/l/r @@ -406,10 +380,7 @@ mod tests { [1, 1; A(C)], [2, 1; A(H)] ); - let ptr = Pointer { - graph: &graph, - pos: Vec2::xy(1, 1), - }; + let ptr = Pointer::new(&graph, Vec2::xy(1, 1)); let a = ptr.bonded().unwrap(); let b = vec![ graph.get(Vec2::xy(1, 0)).unwrap(), @@ -431,10 +402,7 @@ mod tests { [1, 0; B(Single)], [2, 0; A(H)] ); - let ptr = Pointer { - graph: &graph, - pos: Vec2::zero(), - }; + let ptr = Pointer::new(&graph, Vec2::zero()); let bonded = ptr.bonded().unwrap(); let expected = vec![ graph.get(Vec2::xy(0, 1)).unwrap(), @@ -456,10 +424,7 @@ mod tests { [2, 1; A(C)], [1, 2; A(H)] ); - let ptr = Pointer { - graph: &graph, - pos: Vec2::xy(1, 1), - }; + let ptr = Pointer::new(&graph, Vec2::xy(1, 1)); let a = ptr.bonded_carbons().unwrap(); let b = vec![ graph.get(Vec2::xy(0, 1)).unwrap(), @@ -481,10 +446,7 @@ mod tests { [2, 1; A(C)], [1, 2; A(H)] ); - let ptr = Pointer { - graph: &graph, - pos: Vec2::xy(1, 1), - }; + let ptr = Pointer::new(&graph, Vec2::xy(1, 1)); let carbons = ptr.bonded_carbon_count().unwrap(); assert_eq!(carbons, 3usize); @@ -497,10 +459,7 @@ mod tests { [0, 1; B(Single)], [0, 2; A(H)] ); - let ptr = Pointer { - graph: &graph, - pos: Vec2::zero(), - }; + let ptr = Pointer::new(&graph, Vec2::zero()); let atom = ptr.traverse_bond(Direction::Up).unwrap(); assert_eq!( @@ -526,10 +485,7 @@ mod tests { [0, 8; B(Single)], [0, 9; A(H)] ); - let ptr = Pointer { - graph: &graph, - pos: Vec2::zero(), - }; + let ptr = Pointer::new(&graph, Vec2::zero()); let atom = ptr.traverse_bond(Direction::Up).unwrap(); assert_eq!( @@ -547,10 +503,7 @@ mod tests { [0, 0; A(C)], [0, 1; A(H)] ); - let ptr = Pointer { - graph: &graph, - pos: Vec2::zero(), - }; + let ptr = Pointer::new(&graph, Vec2::zero()); let atom = ptr.traverse_bond(Direction::Up).unwrap(); assert_eq!( @@ -569,10 +522,7 @@ mod tests { [1, 0; B(Single)], [2, 0; B(Single)] ); - let ptr = Pointer { - graph: &graph, - pos: Vec2::zero(), - }; + let ptr = Pointer::new(&graph, Vec2::zero()); let err = ptr.traverse_bond(Direction::Right); assert_eq!(err, Err(IncompleteBond(Vec2::x(2)))); @@ -585,10 +535,7 @@ mod tests { [0, 1; B(Triple)], [1, 0; B(Double)] ); - let ptr = Pointer { - graph: &graph, - pos: Vec2::zero(), - }; + let ptr = Pointer::new(&graph, Vec2::zero()); let a = ptr.bond_order(Direction::Up).unwrap(); let b = ptr.bond_order(Direction::Right).unwrap(); @@ -603,10 +550,7 @@ mod tests { [0, 1; A(O)], [1, 0; A(H)] ); - let ptr = Pointer { - graph: &graph, - pos: Vec2::zero(), - }; + let ptr = Pointer::new(&graph, Vec2::zero()); let a = ptr.bond_order(Direction::Up).unwrap(); let b = ptr.bond_order(Direction::Right).unwrap(); let c = ptr.bond_order(Direction::Left); @@ -621,10 +565,7 @@ mod tests { let graph = graph_with!(1, 1, [0, 0; A(C)] ); - let ptr = Pointer { - graph: &graph, - pos: Vec2::zero(), - }; + let ptr = Pointer::new(&graph, Vec2::zero()); let option = ptr.bond_order(Direction::Right); assert_eq!(option, None) diff --git a/src/spatial.rs b/src/spatial.rs index eabe58f..6d8d985 100644 --- a/src/spatial.rs +++ b/src/spatial.rs @@ -13,15 +13,15 @@ use std::cmp::Ordering; /// Represents the state of a grid, including all its [Cell]s and the position of the cursor. #[derive(Clone, Debug)] pub struct GridState { - pub(crate) cells: Vec>, - pub(crate) size: Vec2, - pub(crate) cursor: Vec2, + pub cells: Vec>, + pub size: Vec2, + pub cursor: Vec2, } impl GridState { - /// Creates a new [GridState] with the given `width` and `height` populated by [Cell]s that - /// all contain [Cell::None] and with the cursor set to the center. - pub(crate) fn new(width: usize, height: usize) -> GridState { + /// Creates a new [`GridState`] with the given `width` and `height` populated by [`Cell`]s that + /// all contain [`Cell::None`] and with the cursor set to the center. + pub fn new(width: usize, height: usize) -> GridState { let mut cells = vec![]; for x in 0usize..width { @@ -36,15 +36,15 @@ impl GridState { GridState { cells, size: Vec2::xy(width, height), - cursor: Vec2::xy(width / 2, height / 2), + cursor: Vec2::xy((width - 1) / 2, (height - 1) / 2), } } - /// Returns a reference to the cell at the given `pos`. + /// Returns a reference to the [`Cell`] at the given `pos`. /// /// ## Errors /// - /// If a `pos` is not a valid point within the graph, this function returns [Err]. + /// If the `pos` is not a valid point within the graph, this function returns [`Err`]. pub fn get(&self, pos: Vec2) -> Result<&Cell, String> { if !self.contains(pos) { return Err(format!("Invalid access of graph at ({}, {})", pos.x, pos.y)); @@ -52,35 +52,71 @@ impl GridState { Ok(&self.cells[pos.x as usize][pos.y as usize]) } - /// Sets the current [Cell] pointed to by the cursor to a [Cell::Atom] with the given [Element]. + /// Returns a reference to the [`Cell`] at the given `pos`. + /// + /// ## Errors + /// + /// If the `pos` is not a valid point within the graph, this function returns [`Err`]. + fn get_mut(&mut self, pos: Vec2) -> Result<&mut Cell, String> { + if !self.contains(pos) { + return Err(format!("Invalid access of graph at ({}, {})", pos.x, pos.y)); + } + Ok(&mut self.cells[pos.x as usize][pos.y as usize]) + } + + /// Returns a reference to the [`Cell`] to which the cursor is currently pointing. + /// + /// ## Errors + /// + /// If the `pos` is not a valid point within the graph, this function returns [`Err`]. + pub fn current_cell(&self) -> Result<&Cell, String> { + self.get(self.cursor) + } + + /// Returns a mutable reference to the [`Cell`] to which the cursor is currently pointing. + /// + /// ## Errors + /// + /// If the `pos` is not a valid point within the graph, this function returns [`Err`]. + fn current_cell_mut(&mut self) -> Result<&mut Cell, String> { + self.get_mut(self.cursor) + } + + /// Sets the current [`Cell`] pointed to by the cursor to a [`Cell::Atom`] with the given + /// [`Element`]. pub fn put_atom(&mut self, element: Element) { - let cursor_pos = (self.cursor.x as usize, self.cursor.y as usize); - let new_atom = Cell::Atom(Atom { + let atom = Cell::Atom(Atom { element, - pos: cursor_pos.to_vec2(), + pos: self.cursor, }); - self.cells[cursor_pos.0][cursor_pos.1] = new_atom; + *self + .current_cell_mut() + .expect("cursor should be within bounds") = atom; } - /// Sets the current [Cell] pointed to by the cursor to an [Cell::Bond] with the given - /// [BondOrder]. + /// Sets the current [`Cell`] pointed to by the cursor to an [`Cell::Bond`] with the given + /// [`BondOrder`]. pub fn put_bond(&mut self, order: BondOrder) { - let cursor_pos = (self.cursor.x as usize, self.cursor.y as usize); - let new_atom = Cell::Bond(Bond { - pos: cursor_pos.to_vec2(), + let bond = Cell::Bond(Bond { + pos: self.cursor, order, orient: if self.atom_adjacent() { Horiz } else { Vert }, }); - self.cells[cursor_pos.0][cursor_pos.1] = new_atom; + *self + .current_cell_mut() + .expect("cursor should be within bounds") = bond; } - /// Sets the current [Cell] pointed to by the cursor to [Cell::None]. - pub(crate) fn clear(&mut self) { - let cursor_pos = (self.cursor.x as usize, self.cursor.y as usize); - let new_none = Cell::None(cursor_pos.to_vec2()); - self.cells[cursor_pos.0][cursor_pos.1] = new_none; + /// Sets the current [`Cell`] pointed to by the cursor to [`Cell::None`]. + pub(crate) fn clear_cell(&mut self) { + let empty_cell = Cell::None(self.cursor); + *self + .current_cell_mut() + .expect("cursor should be within bounds") = empty_cell; } + /// Checks if the [`GridState`] is empty, i.e., all the cells it contains are set to + /// [`Cell::None`]. pub fn is_empty(&self) -> bool { for cell in self.cells.iter().flatten() { match cell { @@ -99,11 +135,6 @@ impl GridState { }) } - /// Returns a reference to the [Cell] to which the cursor is currently pointing. - pub(crate) fn current_cell(&self) -> &Cell { - &self.cells[self.cursor.x as usize][self.cursor.y as usize] - } - /// Moves the cursor safely in the given `direction`. pub fn move_cursor(&mut self, direction: Direction) { let displacement = direction.to_vec2(); @@ -112,20 +143,18 @@ impl GridState { } } - /// Returns whether the given position is a valid point on the grid or not. - pub(crate) fn contains(&self, pos: Vec2) -> bool { + /// Returns whether the given `pos` is a valid point on the grid or not. + pub fn contains(&self, pos: Vec2) -> bool { pos.x >= 0 && pos.y >= 0 && pos.x < self.size.x && pos.y < self.size.y } - /// Returns the number of [Cell]s that satisfy the given `predicate`. - pub(crate) fn count(&self, predicate: fn(&Cell) -> bool) -> i32 { - let mut count = 0; - for cell in self.cells.iter().flatten() { - if predicate(cell) { - count += 1; - } - } - count + /// Returns the number of [`Cell`]s that satisfy the given `predicate`. + pub fn count(&self, predicate: fn(&Cell) -> bool) -> i32 { + self.cells + .iter() + .flatten() + .filter(|&cell| predicate(cell)) + .count() as i32 } /// Returns a reference to the first occurrence of a [`Cell`] that satisfies the given @@ -134,38 +163,40 @@ impl GridState { self.cells.iter().flatten().find(|&cell| predicate(cell)) } - /// Returns a [`Vec`] of references to all [`Cells`] that satisfy the given `predicate`. + /// Returns a [`Vec`] of references to all [`Cell`]s that satisfy the given `predicate`. pub fn find_all(&self, predicate: fn(&Cell) -> bool) -> Vec<&Cell> { - let mut out = vec![]; - - for cell in self.cells.iter().flatten() { - if predicate(cell) { - out.push(cell); - } - } - out + self.cells + .iter() + .flatten() + .filter(|&cell| predicate(cell)) + .collect::>() } - /// Determines if there are any [Atom]s that are horizontally adjacent to the current [Cell]. + /// Determines if there are any [`Atom`]s that are horizontally adjacent to the current + /// [`Cell`]. fn atom_adjacent(&self) -> bool { - let mut lptr = Pointer::new(self.current_cell(), self); + let mut lptr = Pointer::new( + self, + self.current_cell() + .expect("current cell should be within bounds") + .pos(), + ); let left = { lptr.move_ptr(Direction::Left); lptr.borrow() }; - let mut rptr = Pointer::new(self.current_cell(), self); + let mut rptr = Pointer::new( + self, + self.current_cell() + .expect("current cell should be within bounds") + .pos(), + ); let right = { rptr.move_ptr(Direction::Right); rptr.borrow() }; - if let Ok(Cell::Atom(_)) = left { - return true; - } - if let Ok(Cell::Atom(_)) = right { - return true; - } - false + matches!(left, Ok(Cell::Atom(_))) || matches!(right, Ok(Cell::Atom(_))) } /// A temporary function that identifies the molecule in the [GridState] by counting @@ -215,6 +246,7 @@ impl GridState { } } +// Implemented to ignore cursor impl PartialEq for GridState { fn eq(&self, other: &Self) -> bool { if self.size != other.size { @@ -236,13 +268,6 @@ pub(crate) trait ToVec2 { fn to_vec2(self) -> Vec2; } -impl ToVec2 for (usize, usize) { - /// Converts the tuple to a [`Vec2`]. - fn to_vec2(self) -> Vec2 { - Vec2::xy(self.0, self.1) - } -} - impl ToVec2 for Direction { fn to_vec2(self) -> Vec2 { match self { @@ -255,10 +280,6 @@ impl ToVec2 for Direction { } } -pub(crate) trait Cellular { - fn pos(&self) -> Vec2; -} - /// A trait made specifically for [`Vec2`] and [`Direction`] to invert the graph so that the origin /// is in the bottom-left corner rather than the top left corner. pub(crate) trait Invert { @@ -399,12 +420,6 @@ mod tests { ) } - #[test] - fn to_vec2_converts_correctly() { - let x = (8usize, 2usize).to_vec2(); - assert_eq!(x, Vec2::xy(8, 2)); - } - #[test] fn all_returns_every_direction() { let x = Direction::all(); diff --git a/src/validation.rs b/src/validation.rs index 583a86c..7d380ed 100644 --- a/src/validation.rs +++ b/src/validation.rs @@ -54,10 +54,7 @@ pub fn check_structure(graph: &GridState) -> Fallible<()> { pub fn check_valence(atoms: Vec<&Atom>, graph: &GridState) -> Fallible<()> { for atom in atoms { // this function eventually could be removed and incorporated into bonded() ? - let ptr = Pointer { - graph, - pos: atom.pos, - }; + let ptr = Pointer::new(graph, atom.pos); let bond_count = match ptr.bond_count() { Ok(it) => it, Err(it) => panic!("{}", it), @@ -100,7 +97,6 @@ mod tests { [0, 0; A(C)], [2, 0; A(C)] ); - let err = check_structure(&graph); assert!(matches!(err, Err(InvalidGraphError::Discontinuity)));