diff --git a/Cargo.lock b/Cargo.lock index 3d8dd46..09b8f5d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,7 +28,7 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chemcreator" -version = "1.2.0" +version = "1.2.1" dependencies = [ "ruscii", "thiserror", diff --git a/Cargo.toml b/Cargo.toml index 7693433..5701a42 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "chemcreator" -version = "1.2.0" +version = "1.2.1" description = "A text-based tool for identifying organic molecules." authors = ["Gavin Tran"] readme = "README.md" diff --git a/README.md b/README.md index e92f50e..b0df789 100644 --- a/README.md +++ b/README.md @@ -36,9 +36,11 @@ you try it out and find something named incorrectly, please open an issue! - [x] Alkenes - [x] Alkynes - [x] Alcohols -- [ ] Aldehydes +- [x] Aldehydes +- [x] Amides +- [x] Amines - [x] Carboxylic acids - [ ] Ethers - [x] Ketones -- [ ] Halogenoalkanes +- [x] Halogenoalkanes - [ ] Esters diff --git a/src/chain.rs b/src/chain.rs index 4894c25..5e4b016 100644 --- a/src/chain.rs +++ b/src/chain.rs @@ -215,7 +215,6 @@ mod tests { use crate::graph_with; use crate::molecule::BondOrder::Single; use crate::molecule::Element::{C, H}; - use crate::test_utils::unwrap_atom; use crate::test_utils::GW::{A, B}; // Also checks accumulate_carbons @@ -226,7 +225,7 @@ mod tests { [1, 0; A(C)], [2, 0; A(C)], [2, 1; A(C)], [2, 2; A(C)], [3, 0; A(C)], - [4, 0; A(C)] + [4, 0; A(C)], ); let accumulator = endpoint_head_chains( Atom { @@ -251,7 +250,7 @@ mod tests { [1, 0; A(C)], [2, 0; A(C)], [2, 1; A(C)], [2, 2; A(C)], [3, 0; A(C)], - [4, 0; A(C)] + [4, 0; A(C)], ); let accumulator = endpoint_head_chains( Atom { @@ -302,7 +301,7 @@ mod tests { [1, 0; A(H)], [1, 1; A(C)], [1, 2; A(C)], - [2, 1; A(C)] + [2, 1; A(C)], ); let atoms = next_carbons(Vec2::xy(1, 1), None, &graph).unwrap(); let expected = vec![ @@ -310,7 +309,7 @@ mod tests { graph.get(Vec2::xy(2, 1)).unwrap(), ] .iter() - .map(|&cell| unwrap_atom(cell)) + .map(|&cell| cell.unwrap_atom()) .collect::>(); assert_eq!(atoms, expected); @@ -323,10 +322,10 @@ mod tests { [1, 0; A(H)], [1, 1; A(C)], [1, 2; A(C)], - [2, 1; A(C)] + [2, 1; A(C)], ); let atom = next_carbons(Vec2::xy(1, 1), Some(Vec2::xy(1, 2)), &graph).unwrap(); - let expected = vec![unwrap_atom(graph.get(Vec2::xy(2, 1)).unwrap())]; + let expected = vec![graph.get(Vec2::xy(2, 1)).unwrap().unwrap_atom()]; assert_eq!(atom, expected); } @@ -340,7 +339,7 @@ mod tests { [3, 0; A(H)], [3, 1; A(C)], [3, 2; A(H)], [4, 1; B(Single)], [5, 0; A(H)], [5, 1; A(C)], [5, 2; A(H)], - [6, 1; A(H)] + [6, 1; A(H)], ); let cells = endpoint_carbons(&graph).unwrap(); let expected = vec![ @@ -356,7 +355,7 @@ mod tests { let graph = graph_with!(3, 3, [0, 1; A(H)], [1, 0; A(H)], [1, 1; A(C)], [1, 2; A(H)], - [2, 1; A(H)] + [2, 1; A(H)], ); let cell = endpoint_carbons(&graph).unwrap(); let expected = vec![graph.get(Vec2::xy(1, 1)).unwrap()]; @@ -370,7 +369,7 @@ mod tests { [0, 0; A(C)], [1, 0; A(C)], [2, 0; A(C)], - [0, 2; A(C)] + [0, 2; A(C)], ); let network = get_connected_cells(Vec2::xy(0, 0), &graph).unwrap(); let expected = vec![ diff --git a/src/groups.rs b/src/groups.rs index fee74fe..ed14f1e 100644 --- a/src/groups.rs +++ b/src/groups.rs @@ -7,7 +7,7 @@ use crate::chain::{endpoint_head_chains, longest_chain}; use crate::compound; use crate::groups::InvalidGraphError::{Other, UnrecognizedGroup}; use crate::molecule::Group::{ - AcidHalide, Aldehyde, Alkene, Alkyne, Amine, Bromo, Carbonyl, Carboxyl, Chloro, Fluoro, + AcidHalide, Aldehyde, Alkene, Alkyne, Amide, Amine, Bromo, Carbonyl, Carboxyl, Chloro, Fluoro, Hydrogen, Hydroxyl, Iodo, Nitrile, }; use crate::molecule::Halogen::{Bromine, Chlorine, Fluorine, Iodine}; @@ -131,7 +131,8 @@ fn group_patterns(mut groups: Vec) -> Vec { [Carbonyl, Chloro => AcidHalide(Chlorine)], [Carbonyl, Bromo => AcidHalide(Bromine)], [Carbonyl, Iodo => AcidHalide(Iodine)], - [Carbonyl, Hydrogen => Aldehyde] + [Carbonyl, Amine => Amide], + [Carbonyl, Hydrogen => Aldehyde], ); groups.retain(|it| it != &Hydrogen); break; @@ -150,7 +151,7 @@ fn group_patterns(mut groups: Vec) -> Vec { /// groups to `out`. #[macro_export] macro_rules! compound { - ($groups:expr, $out:expr, $([$first:expr, $second:expr => $comp:expr]),*) => { + ($groups:expr, $out:expr, $([$first:expr, $second:expr => $comp:expr],)*) => { $( if $groups.contains(&$first) && $groups.contains(&$second) { $groups.retain(|it| it != &$first && it != &$second); @@ -323,7 +324,7 @@ mod tests { [2, 1; B(Single)], [3, 0; A(H)], [3, 1; A(C)], [3, 2; A(H)], [4, 1; A(O)], - [5, 1; A(H)] + [5, 1; A(H)], ); let chain = vec![ Atom { @@ -399,7 +400,7 @@ mod tests { [0, 1; B(Single)], [0, 2; A(O)], [0, 3; B(Single)], - [0, 4; A(H)] + [0, 4; A(H)], ); let a = group_node_tree(&graph, Vec2::xy(0, 0), Direction::Up).unwrap(); let b = GroupNode { @@ -420,7 +421,7 @@ mod tests { let graph = graph_with!(1, 3, [0, 0; A(C)], [0, 1; A(O)], - [0, 2; A(H)] + [0, 2; A(H)], ); let node = group_node_tree(&graph, Vec2::xy(0, 0), Direction::Up).unwrap(); let expected = GroupNode { @@ -441,7 +442,7 @@ mod tests { let graph = graph_with!(3, 3, [0, 0; A(H)], [0, 1; A(C)], [0, 2; A(H)], [1, 1; B(Double)], - [2, 0; A(H)], [2, 1; A(C)], [2, 2; A(H)] + [2, 0; A(H)], [2, 1; A(C)], [2, 2; A(H)], ); let node = group_node_tree(&graph, Vec2::xy(0, 1), Direction::Right).unwrap(); let expected = GroupNode { @@ -458,7 +459,7 @@ mod tests { let graph = graph_with!(3, 3, [0, 1; A(C)], [1, 0; A(H)], [1, 1; A(C)], - [2, 1; A(C)] + [2, 1; A(C)], ); let directions = next_directions(&graph, Vec2::xy(1, 1), Vec2::xy(0, 1)).unwrap(); let expected = vec![Direction::Down, Direction::Right]; @@ -472,7 +473,7 @@ mod tests { [0, 1; A(C)], [1, 0; A(H)], [1, 1; A(C)], - [2, 1; A(C)] + [2, 1; A(C)], ); let directions = next_directions(&graph, Vec2::xy(1, 1), Vec2::xy(0, 1)).unwrap(); let expected = vec![Direction::Down, Direction::Right]; @@ -485,7 +486,7 @@ mod tests { let graph = graph_with!(5, 5, [0, 2; A(C)], [1, 2; B(Single)], - [2, 0; A(C)], [2, 1; B(Double)], [2, 2; A(C)], [2, 3; A(O)], [2, 4; A(H)] + [2, 0; A(C)], [2, 1; B(Double)], [2, 2; A(C)], [2, 3; A(O)], [2, 4; A(H)], ); let branch = Branch { chain: vec![ @@ -514,7 +515,7 @@ mod tests { [1, 0; A(Br)], [1, 1; A(C)], [1, 2; A(I)], - [2, 1; A(F)] + [2, 1; A(F)], ); let branch = Branch { chain: vec![Atom { diff --git a/src/input.rs b/src/input.rs index ff001d1..79a0e2f 100644 --- a/src/input.rs +++ b/src/input.rs @@ -5,62 +5,55 @@ use crate::groups::debug_branches; use crate::macros::invoke_macro; use crate::molecule::BondOrder::{Double, Single, Triple}; -use crate::molecule::Element; -use crate::molecule::Element::{C, H, N, O}; +use crate::molecule::Element::{Cl, C, H, I, N, O}; +use crate::molecule::{ComponentType, Element}; use crate::naming::name_molecule; use crate::spatial::GridState; use crate::{AppState, Mode}; use ruscii::app::State; use ruscii::keyboard::{Key, KeyEvent}; use ruscii::spatial::Direction; +use Element::{Br, F}; pub(crate) fn input_insert_mode(app_state: &State, state: &mut AppState, graph: &mut GridState) { for key_event in app_state.keyboard().last_key_events() { match key_event { KeyEvent::Pressed(Key::B) => { - graph.put_atom(Element::Br); state.key = "B"; - update(state, graph); + update(state, graph, ComponentType::Element(Br)); } KeyEvent::Pressed(Key::C) => { - graph.put_atom(C); state.key = "C"; - update(state, graph); + update(state, graph, ComponentType::Element(C)); } KeyEvent::Pressed(Key::F) => { - graph.put_atom(Element::F); state.key = "F"; - update(state, graph); + update(state, graph, ComponentType::Element(F)); } KeyEvent::Pressed(Key::H) => { - graph.put_atom(H); state.key = "H"; - update(state, graph); + update(state, graph, ComponentType::Element(H)); } KeyEvent::Pressed(Key::I) => { - graph.put_atom(Element::I); state.key = "I"; - update(state, graph); + update(state, graph, ComponentType::Element(I)); } KeyEvent::Pressed(Key::L) => { - graph.put_atom(Element::Cl); state.key = "L"; - update(state, graph); + update(state, graph, ComponentType::Element(Cl)); } KeyEvent::Pressed(Key::N) => { - graph.put_atom(N); state.key = "N"; - update(state, graph); + update(state, graph, ComponentType::Element(N)); } KeyEvent::Pressed(Key::O) => { - graph.put_atom(O); state.key = "O"; - update(state, graph); + update(state, graph, ComponentType::Element(O)); } KeyEvent::Pressed(Key::F5) => { graph.clear_all(); state.key = "F5"; - update(state, graph); + update(state, graph, ComponentType::None); } KeyEvent::Pressed(Key::F7) => { state.macros_enabled = !state.macros_enabled; @@ -74,24 +67,20 @@ pub(crate) fn input_insert_mode(app_state: &State, state: &mut AppState, graph: state.key = "F12"; } KeyEvent::Pressed(Key::Num1) => { - graph.put_bond(Single); state.key = "1"; - update(state, graph); + update(state, graph, ComponentType::Order(Single)); } KeyEvent::Pressed(Key::Num2) => { - graph.put_bond(Double); state.key = "2"; - update(state, graph); + update(state, graph, ComponentType::Order(Double)); } KeyEvent::Pressed(Key::Num3) => { - graph.put_bond(Triple); state.key = "3"; - update(state, graph); + update(state, graph, ComponentType::Order(Triple)); } KeyEvent::Pressed(Key::Backspace) => { - graph.clear_cell(); state.key = "Backspace"; - update(state, graph); + update(state, graph, ComponentType::None); } KeyEvent::Pressed(Key::Right) => { graph.move_cursor(Direction::Right); @@ -134,9 +123,20 @@ pub(crate) fn input_view_mode(app_state: &State, state: &mut AppState) { } //noinspection RsBorrowChecker -pub(crate) fn update(state: &mut AppState, graph: &mut GridState) { +pub(crate) fn update(state: &mut AppState, graph: &mut GridState, comp: ComponentType) { + let previous = graph + .current_cell() + .expect("cell should be within bounds") + .comp(); + + match comp { + ComponentType::Element(it) => graph.put_atom(it), + ComponentType::Order(it) => graph.put_bond(it), + ComponentType::None => graph.clear_cell(), + } + if state.macros_enabled { - invoke_macro(graph); + invoke_macro(graph, comp, previous); } (state.name, state.err) = match name_molecule(graph) { diff --git a/src/macros.rs b/src/macros.rs index fc401bc..05f6681 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -3,36 +3,265 @@ //! Not to be confused with Rust's `macro_rules!` declarations, the `macros` module contains //! common actions that should be automatically performed for the user when they make an input. -use crate::molecule::ComponentType; -use crate::molecule::Element::{C, H}; +use crate::molecule::BondOrder::{Double, Single}; +use crate::molecule::Element::{C, H, O}; +use crate::molecule::{BondOrder, BondOrientation, Cell, ComponentType}; use crate::pointer::Pointer; -use crate::spatial::{EnumAll, GridState}; -use ruscii::spatial::Direction; +use crate::spatial::{EnumAll, GridState, ToVec2}; +use ruscii::spatial::{Direction, Vec2}; +use std::cmp::Ordering; -pub fn invoke_macro(graph: &mut GridState) { - match graph - .current_cell() - .expect("cell should be within bounds") - .comp() - { - ComponentType::Element(C) => fill_hydrogen(graph), +use crate::molecule::BondOrientation::Horiz; + +#[derive(Clone, Debug, PartialEq)] +struct CellBlock<'a> { + pub cells: Vec>, + pub direction: Direction, + pub graph: &'a GridState, +} + +impl<'a> CellBlock<'a> { + pub fn new(graph: &GridState) -> CellBlock { + CellBlock { + cells: vec![], + direction: Direction::Up, + graph, + } + } + + pub fn borrow(&self, group: usize, index: usize) -> Result<&'a Cell, String> { + let relative = self.cells[group][index]; + let rotated = match self.direction { + Direction::Up => relative, + Direction::Down => -relative, + Direction::Right => Vec2::xy(relative.y, -relative.x), + Direction::Left => Vec2::xy(-relative.y, relative.x), + Direction::None => panic!("Direction::None passed to borrow"), + }; + let absolute = rotated + self.graph.cursor; + + self.graph.get(absolute) + } +} + +macro_rules! block { + ($graph:expr, $([$(($xi:expr, $yi:expr)),*],)*) => { + { + let mut block = CellBlock::new($graph); + $( + { + let temp = vec![$(Vec2::xy($xi, $yi),)*]; + block.cells.push(temp); + } + )* + block + } + }; +} + +pub fn invoke_macro(graph: &mut GridState, new: ComponentType, _previous: ComponentType) { + match new { + ComponentType::Element(C) => { + for direction in Direction::all() { + let mut block = block!(graph, [(0, 1), (0, 2)],); + block.direction = direction; + + let first = match block.borrow(0, 0) { + Ok(it) => it, + Err(_) => continue, + }; + let first_pos = first.pos(); + let second = match block.borrow(0, 1) { + Ok(it) => it, + Err(_) => continue, + }; + + let carbon_ext = second.is_atom() && second.unwrap_atom().element == C; + + if carbon_ext { + graph.put(first_pos, ComponentType::Order(Single)); + } + } + hydrogen_extension(graph) + } + ComponentType::Element(O) => { + for direction in Direction::all() { + let mut block = block!(graph, [(0, 1), (0, 2), (0, -1)],); + block.direction = direction; + + let first = match block.borrow(0, 0) { + Ok(it) => it, + Err(_) => continue, + }; + let first_pos = first.pos(); + let second = match block.borrow(0, 1) { + Ok(it) => it, + Err(_) => continue, + }; + let second_pos = second.pos(); + let third = match block.borrow(0, 2) { + Ok(it) => it, + Err(_) => continue, + }; + let third_pos = third.pos(); + + let carbonyl_ext = second.is_atom() && second.unwrap_atom().element == C; + let hydroxyl_ext = first.is_atom() && first.unwrap_atom().element == C; + + if carbonyl_ext { + graph.put(first_pos, ComponentType::Order(Double)); + hydrogen_correction(graph, second_pos); + } + + if hydroxyl_ext { + graph.put(third_pos, ComponentType::Element(H)); + hydrogen_correction(graph, first_pos); + } + } + } + ComponentType::Order(_) => { + let dirs = if graph.current_cell().unwrap().unwrap_bond().orient == Horiz { + [Direction::Left, Direction::Right] + } else { + [Direction::Up, Direction::Down] + }; + let ptr = Pointer::new(graph, graph.cursor); + let first = match ptr.traverse_bond(dirs[0]) { + Ok(it) => it, + Err(_) => return, + }; + let second = match ptr.traverse_bond(dirs[1]) { + Ok(it) => it, + Err(_) => return, + }; + + if first.element == C && second.element == C { + hydrogen_correction(graph, first.pos); + hydrogen_correction(graph, second.pos) + } + } ComponentType::None => {} // just to appease Clippy _ => {} } } -pub fn fill_hydrogen(graph: &mut GridState) { +pub fn hydrogen_extension(graph: &mut GridState) { + let bond_count = Pointer::new(graph, graph.cursor).bond_count().unwrap(); + let mut bonds_needed = graph + .current_cell() + .unwrap() + .unwrap_atom() + .element + .bond_number() + - bond_count; + for direction in Direction::all() { - hydrogen_arm(graph, direction); + if bonds_needed <= 0 { + return; + } + if hydrogen_fill(graph, graph.cursor + direction.to_vec2()) { + bonds_needed -= 1; + } } } -fn hydrogen_arm(graph: &mut GridState, direction: Direction) { - let mut ptr = Pointer::new(graph, graph.cursor); - let first_neighbor = ptr.move_ptr(direction) && !ptr.borrow().unwrap().is_empty(); - let pos = ptr.pos; +fn hydrogen_fill(graph: &mut GridState, pos: Vec2) -> bool { + let ptr = Pointer::new(graph, pos); + let first_neighbor = !match ptr.borrow() { + Ok(it) => it.is_empty(), + Err(_) => return false, + }; if first_neighbor { graph.put(pos, ComponentType::Element(H)); + true + } else { + false + } +} + +pub fn hydrogen_correction(graph: &mut GridState, pos: Vec2) { + let bond_count = Pointer::new(graph, pos).bond_count().unwrap(); + let mut bonds_needed = graph.get(pos).unwrap().unwrap_atom().element.bond_number() - bond_count; + + for direction in Direction::all() { + let adjusted = pos + direction.to_vec2(); + + match bonds_needed.cmp(&0) { + Ordering::Equal => return, + Ordering::Less => { + if hydrogen_remove(graph, adjusted) { + bonds_needed += 1; + } + } + Ordering::Greater => { + if hydrogen_fill(graph, adjusted) { + bonds_needed -= 1; + } + } + } + } +} + +fn hydrogen_remove(graph: &mut GridState, pos: Vec2) -> bool { + let cell = graph.get(pos).unwrap(); + + if cell.is_atom() && cell.unwrap_atom().element == H { + graph.put(pos, ComponentType::None); + true + } else { + false + } +} + +pub fn bond_conversion( + graph: &mut GridState, + pos: Vec2, + order: BondOrder, + orient: BondOrientation, +) { + let directions = if orient == Horiz { + [Direction::Left, Direction::Right] + } else { + [Direction::Up, Direction::Down] + }; + + 'outer: for direction in directions { + let mut pos = pos; + + loop { + pos += direction.to_vec2(); + let current_cell = match graph.get_mut(pos) { + Ok(it) => it, + Err(_) => continue 'outer, + }; + + match current_cell { + Cell::Bond(it) if it.orient == orient => it.order = order, + _ => continue 'outer, + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::graph_with; + use crate::test_utils::GW::{A, B}; + + #[test] + fn hydrogen_correction_leaves_full_bonds() { + let mut graph = graph_with!(3, 3, + [1, 1; A(C)], + [0, 1; A(H)], + [1, 0; A(H)], + [1, 2; B(Double)], + [2, 1; B(Single)], + ); + hydrogen_correction(&mut graph, Vec2::xy(1, 1)); + let ptr = Pointer::new(&graph, Vec2::xy(1, 1)); + + assert_eq!(ptr.bond_count(), Ok(4)); } } diff --git a/src/main.rs b/src/main.rs index fb987fa..5c1c07e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -88,10 +88,6 @@ fn main() { .draw_text( " └────┘ ", Vec2::xy(graph.size.x + 2, graph.size.y / 2 - 2).inv(&graph), - ) - .draw_text( - " Release 1 ", - Vec2::xy(graph.size.x + 2, graph.size.y / 2 - 3).inv(&graph), ); } _ => { @@ -221,21 +217,13 @@ mod test_utils { B(BondOrder), } - pub(crate) fn unwrap_atom(cell: &Cell) -> Atom { - if let Cell::Atom(atom) = cell { - atom.to_owned() - } else { - panic!("Called unwrap_atom on non-atom value") - } - } - /// Creates a [`GridState`] with the given `vals` at (`x`, `y`). Used with the [`GW`] enum. #[macro_export] macro_rules! graph_with { ($width:expr, $height:expr) => { GridState::new($width, $height) }; - ($width:expr, $height:expr, $([$x:expr, $y:expr; $val:expr]),*) => {{ + ($width:expr, $height:expr, $([$x:expr, $y:expr; $val:expr],)*) => {{ let mut graph = GridState::new($width, $height); $( graph.cursor = Vec2::xy($x, $y); @@ -251,10 +239,10 @@ mod test_utils { #[test] fn unwrap_atom_returns_atom() { let graph = graph_with!(1, 1, - [0, 0; A(C)] + [0, 0; A(C)], ); let cell = graph.get(Vec2::zero()).unwrap(); - let atom = unwrap_atom(cell); + let atom = cell.unwrap_atom(); assert_eq!( atom, @@ -296,7 +284,7 @@ mod test_utils { [0, 1; A(C)], [1, 1; B(Single)], [2, 1; A(O)], - [2, 0; A(H)] + [2, 0; A(H)], ); assert_eq!(a, b) diff --git a/src/molecule.rs b/src/molecule.rs index 8a2a841..918b621 100644 --- a/src/molecule.rs +++ b/src/molecule.rs @@ -36,6 +36,7 @@ pub enum Group { Aldehyde, AcidHalide(Halogen), Carboxyl, + Amide, /* Chain groups */ Ester, Ether, @@ -53,14 +54,15 @@ impl Group { /// the main group_ (i.e., always a prefix). pub const fn priority(self) -> Option { let priority = match self { - Group::Carboxyl => 12, - Group::Ester => 11, + Group::Carboxyl => 13, + Group::Ester => 12, Group::AcidHalide(halogen) => match halogen { - Halogen::Fluorine => 10, - Halogen::Chlorine => 9, - Halogen::Bromine => 8, - Halogen::Iodine => 7, + Halogen::Fluorine => 11, + Halogen::Chlorine => 10, + Halogen::Bromine => 9, + Halogen::Iodine => 8, }, + Group::Amide => 7, Group::Nitrile => 6, Group::Aldehyde => 5, Group::Carbonyl => 4, @@ -102,6 +104,7 @@ impl Display for Group { Group::Ester => "ester", Group::Ether => "ether", Group::Amine => "amino", + Group::Amide => "carbamoyl", Group::Nitrile => "cyano", }; write!(f, "{str}") @@ -139,6 +142,7 @@ impl FromStr for Group { "ester" => Group::Ester, "ether" => Group::Ether, "amino" => Group::Amine, + "carbamoyl" => Group::Amide, "cyano" => Group::Nitrile, _ => return Err(()), }; @@ -240,6 +244,22 @@ impl Cell { pub fn is_empty(&self) -> bool { !matches!(self, Cell::None(_)) } + + pub fn unwrap_atom(&self) -> Atom { + match self { + Cell::Atom(atom) => atom.to_owned(), + Cell::Bond(_) => panic!("called Cell::unwrap_atom() on a Cell::Bond value"), + Cell::None(_) => panic!("called Cell::unwrap_atom() on a Cell::None value"), + } + } + + pub fn unwrap_bond(&self) -> Bond { + match self { + Cell::Atom(_) => panic!("called Cell::unwrap_bond() on a Cell::Atom value"), + Cell::Bond(bond) => bond.to_owned(), + Cell::None(_) => panic!("called Cell::unwrap_bond() on a Cell::None value"), + } + } } #[derive(Copy, Clone, Debug, PartialEq)] @@ -293,7 +313,7 @@ pub enum Element { impl Element { /// Returns the number of bonds the current [`Element`] should have. - pub const fn bond_number(&self) -> u8 { + pub const fn bond_number(&self) -> i32 { match *self { C => 4, N => 3, @@ -364,7 +384,7 @@ pub enum BondOrder { } impl BondOrder { - pub const fn order(&self) -> u8 { + pub const fn order(&self) -> i32 { match self { Single => 1, Double => 2, @@ -575,7 +595,7 @@ mod tests { [0, 0; A(C)], [1, 0; A(H)], [0, 1; A(O)], - [1, 1; B(Single)] + [1, 1; B(Single)], ); assert_eq!(graph.get(Vec2::xy(0, 0)).unwrap().color(), LightGrey); @@ -589,7 +609,7 @@ mod tests { let graph = graph_with!(2, 2, [1, 0; A(H)], [0, 1; A(O)], - [1, 1; B(Single)] + [1, 1; B(Single)], ); assert_eq!(graph.cells[0][1].pos(), Vec2::xy(0, 1)); @@ -690,7 +710,7 @@ mod tests { let graph = graph_with!(1, 3, [0, 0; A(C)], [0, 1; A(O)], - [0, 2; A(H)] + [0, 2; A(H)], ); let group_node = group_node_tree(&graph, Vec2::xy(0, 0), Direction::Up).unwrap(); diff --git a/src/naming.rs b/src/naming.rs index 4907224..c480e08 100644 --- a/src/naming.rs +++ b/src/naming.rs @@ -110,6 +110,8 @@ fn suffix(fragment: SubFragment) -> Result { return Ok(format!("edioyl di{it}")); } Group::AcidHalide(it) => return Ok(format!("oyl {it}")), + Group::Amide if fragment.locants.len() == 2 => return Ok("ediamide".to_string()), + Group::Amide => return Ok("amide".to_string()), Group::Amine => "amine", _ => return Ok("e".to_string()), }; @@ -318,6 +320,7 @@ impl NamingError { Group::Carbonyl => "carbonyl", Group::Ester => "ester", Group::Ether => "ether", + Group::Amide => "amide", Group::Amine => "amine", } .to_string(), diff --git a/src/pointer.rs b/src/pointer.rs index 493d660..b5e82d9 100644 --- a/src/pointer.rs +++ b/src/pointer.rs @@ -238,7 +238,7 @@ impl<'a> Pointer<'a> { /// /// If the current cell is not a [`Cell::Atom`] a message describing the error and the position /// at which it occurred is returned. - pub(crate) fn bond_count(&self) -> Result { + pub(crate) fn bond_count(&self) -> Result { let mut out = 0; match self.borrow().unwrap() { @@ -248,14 +248,14 @@ impl<'a> Pointer<'a> { "Pointer cannot access bond count: atom was expected at ({}, {}) but bond was found", self.pos.x, self.pos.y - )) + )); } Cell::None(_) => { return Err(format!( "Pointer cannot access bond count: atom was expected at ({}, {}) but none was found", self.pos.x, self.pos.y - )) + )); } }; @@ -291,7 +291,6 @@ mod tests { use crate::graph_with; use crate::molecule::BondOrder::{Double, Single, Triple}; use crate::molecule::Element::{C, H, O}; - use crate::test_utils::unwrap_atom; use crate::test_utils::GW::{A, B}; #[test] @@ -318,7 +317,7 @@ mod tests { [1, 0; A(H)], [0, 1; A(H)], [1, 1; A(C)], - [2, 1; B(Single)] + [2, 1; B(Single)], ); let ptr = Pointer::new(&graph, Vec2::xy(1, 1)); let a = ptr.connected(); @@ -336,7 +335,7 @@ mod tests { let graph = graph_with!(2, 2, [0, 0; A(C)], [1, 0; A(H)], - [0, 1; B(Triple)] + [0, 1; B(Triple)], ); let ptr = Pointer::new(&graph, Vec2::zero()); let a = ptr.connected(); @@ -352,7 +351,7 @@ mod tests { fn connected_returns_directions() { let graph = graph_with!(4, 4, [0, 1; A(C)], - [1, 0; A(H)], [1, 1; A(C)], [1, 2; B(Double)], [1, 3; A(O)] + [1, 0; A(H)], [1, 1; A(C)], [1, 2; B(Double)], [1, 3; A(O)], ); let ptr = Pointer::new(&graph, Vec2::xy(1, 1)); let directions = ptr.connected_directions(); @@ -372,7 +371,7 @@ mod tests { [3, 2; B(Single)], [4, 2; A(H)], [2, 3; B(Double)], - [2, 4; A(O)] + [2, 4; A(O)], ); let ptr = Pointer::new(&graph, Vec2::xy(2, 2)); let a = ptr.bonded().unwrap(); @@ -382,7 +381,7 @@ mod tests { graph.get(Vec2::xy(4, 2)).unwrap(), ] .iter() - .map(|&cell| unwrap_atom(cell)) + .map(|&cell| cell.unwrap_atom()) .collect::>(); assert_eq!(a, b); @@ -394,7 +393,7 @@ mod tests { [1, 0; A(H)], [0, 1; A(H)], [1, 1; A(C)], - [2, 1; A(H)] + [2, 1; A(H)], ); let ptr = Pointer::new(&graph, Vec2::xy(1, 1)); let a = ptr.bonded().unwrap(); @@ -404,7 +403,7 @@ mod tests { graph.get(Vec2::xy(2, 1)).unwrap(), ] .iter() - .map(|&cell| unwrap_atom(cell)) + .map(|&cell| cell.unwrap_atom()) .collect::>(); assert_eq!(a, b); @@ -416,7 +415,7 @@ mod tests { [0, 0; A(C)], [0, 1; A(H)], [1, 0; B(Single)], - [2, 0; A(H)] + [2, 0; A(H)], ); let ptr = Pointer::new(&graph, Vec2::zero()); let bonded = ptr.bonded().unwrap(); @@ -425,7 +424,7 @@ mod tests { graph.get(Vec2::xy(2, 0)).unwrap(), ] .iter() - .map(|&cell| unwrap_atom(cell)) + .map(|&cell| cell.unwrap_atom()) .collect::>(); assert_eq!(bonded, expected); @@ -438,7 +437,7 @@ mod tests { [0, 1; A(C)], [1, 1; A(C)], [2, 1; A(C)], - [1, 2; A(H)] + [1, 2; A(H)], ); let ptr = Pointer::new(&graph, Vec2::xy(1, 1)); let a = ptr.bonded_carbons().unwrap(); @@ -447,7 +446,7 @@ mod tests { graph.get(Vec2::xy(2, 1)).unwrap(), ] .iter() - .map(|&cell| unwrap_atom(cell)) + .map(|&cell| cell.unwrap_atom()) .collect::>(); assert_eq!(a, b); @@ -460,7 +459,7 @@ mod tests { [0, 1; A(C)], [1, 1; A(C)], [2, 1; A(C)], - [1, 2; A(H)] + [1, 2; A(H)], ); let ptr = Pointer::new(&graph, Vec2::xy(1, 1)); let carbons = ptr.bonded_carbon_count().unwrap(); @@ -473,7 +472,7 @@ mod tests { let graph = graph_with!(1, 3, [0, 0; A(C)], [0, 1; B(Single)], - [0, 2; A(H)] + [0, 2; A(H)], ); let ptr = Pointer::new(&graph, Vec2::zero()); let atom = ptr.traverse_bond(Direction::Up).unwrap(); @@ -482,7 +481,7 @@ mod tests { atom, Atom { element: H, - pos: Vec2::y(2) + pos: Vec2::y(2), } ); } @@ -499,7 +498,7 @@ mod tests { [0, 6; B(Single)], [0, 7; B(Single)], [0, 8; B(Single)], - [0, 9; A(H)] + [0, 9; A(H)], ); let ptr = Pointer::new(&graph, Vec2::zero()); let atom = ptr.traverse_bond(Direction::Up).unwrap(); @@ -508,7 +507,7 @@ mod tests { atom, Atom { element: H, - pos: Vec2::xy(0, 9) + pos: Vec2::xy(0, 9), } ); } @@ -517,7 +516,7 @@ mod tests { fn traverse_bond_implicit() { let graph = graph_with!(1, 2, [0, 0; A(C)], - [0, 1; A(H)] + [0, 1; A(H)], ); let ptr = Pointer::new(&graph, Vec2::zero()); let atom = ptr.traverse_bond(Direction::Up).unwrap(); @@ -526,7 +525,7 @@ mod tests { atom, Atom { element: H, - pos: Vec2::y(1) + pos: Vec2::y(1), } ); } @@ -536,7 +535,7 @@ mod tests { let graph = graph_with!(3, 1, [0, 0; A(C)], [1, 0; B(Single)], - [2, 0; B(Single)] + [2, 0; B(Single)], ); let ptr = Pointer::new(&graph, Vec2::zero()); let err = ptr.traverse_bond(Direction::Right); @@ -549,7 +548,7 @@ mod tests { let graph = graph_with!(2, 2, [0, 0; A(C)], [0, 1; B(Triple)], - [1, 0; B(Double)] + [1, 0; B(Double)], ); let ptr = Pointer::new(&graph, Vec2::zero()); let a = ptr.bond_order(Direction::Up).unwrap(); @@ -564,7 +563,7 @@ mod tests { let graph = graph_with!(2, 2, [0, 0; A(C)], [0, 1; A(O)], - [1, 0; A(H)] + [1, 0; A(H)], ); let ptr = Pointer::new(&graph, Vec2::zero()); let a = ptr.bond_order(Direction::Up).unwrap(); @@ -579,7 +578,7 @@ mod tests { #[test] fn bond_order_out_of_bounds() { let graph = graph_with!(1, 1, - [0, 0; A(C)] + [0, 0; A(C)], ); let ptr = Pointer::new(&graph, Vec2::zero()); let option = ptr.bond_order(Direction::Right); diff --git a/src/spatial.rs b/src/spatial.rs index 20a3e2f..c09ec70 100644 --- a/src/spatial.rs +++ b/src/spatial.rs @@ -3,6 +3,7 @@ //! The `spatial` module provides functionality for the state and traversal of the grid with which //! the user interacts, including the [`GridState`] struct. +use crate::macros::bond_conversion; use crate::molecule::BondOrientation::{Horiz, Vert}; use crate::molecule::{Atom, Bond, BondOrder, Cell, ComponentType, Element}; use crate::pointer::Pointer; @@ -96,18 +97,20 @@ impl GridState { /// 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 orient = if self.atom_adjacent(self.cursor) { + Horiz + } else { + Vert + }; let bond = Cell::Bond(Bond { pos: self.cursor, order, - orient: if self.atom_adjacent(self.cursor) { - Horiz - } else { - Vert - }, + orient, }); *self .current_cell_mut() .expect("cursor should be within bounds") = bond; + bond_conversion(self, self.cursor, order, orient); } /// Sets the current [`Cell`] pointed to by the cursor to [`Cell::None`]. @@ -121,11 +124,15 @@ impl GridState { pub fn put(&mut self, pos: Vec2, comp: ComponentType) { *self.get_mut(pos).unwrap() = match comp { ComponentType::Element(it) => Cell::Atom(Atom { element: it, pos }), - ComponentType::Order(it) => Cell::Bond(Bond { - pos, - order: it, - orient: if self.atom_adjacent(pos) { Horiz } else { Vert }, - }), + ComponentType::Order(it) => { + let orient = if self.atom_adjacent(pos) { Horiz } else { Vert }; + bond_conversion(self, self.cursor, it, orient); + Cell::Bond(Bond { + pos, + order: it, + orient, + }) + } ComponentType::None => Cell::None(pos), } } @@ -374,7 +381,7 @@ mod tests { let graph = graph_with!(2, 2, [0, 1; A(C)], [1, 0; A(O)], - [1, 1; B(Single)] + [1, 1; B(Single)], ); assert_eq!( diff --git a/src/validation.rs b/src/validation.rs index 0599406..14bea9f 100644 --- a/src/validation.rs +++ b/src/validation.rs @@ -116,7 +116,6 @@ mod tests { use crate::graph_with; use crate::molecule::BondOrder::{Single, Triple}; use crate::molecule::Element::{C, H, O}; - use crate::test_utils::unwrap_atom; use crate::test_utils::GW::{A, B}; #[test] @@ -126,7 +125,7 @@ mod tests { [1, 0; A(H)], [0, 1; A(H)], [2, 1; A(H)], - [1, 2; A(H)] + [1, 2; A(H)], ); let ok = check_structure(&graph); @@ -137,7 +136,7 @@ mod tests { fn check_structure_returns_discontinuity() { let graph = graph_with!(3, 1, [0, 0; A(C)], - [2, 0; A(C)] + [2, 0; A(C)], ); let err = check_structure(&graph); @@ -154,7 +153,7 @@ mod tests { [1, 0; B(Single)], [0, 1; B(Single)], [2, 1; B(Single)], - [1, 2; B(Single)] + [1, 2; B(Single)], ); let err = check_structure(&graph); @@ -169,7 +168,7 @@ mod tests { [5, 1; A(H)], [4, 1; B(Single)], [2, 1; B(Single)], - [0, 1; B(Triple)] + [0, 1; B(Triple)], ); let input_atoms = vec![ graph.get(Vec2::xy(1, 1)).unwrap(), @@ -177,7 +176,7 @@ mod tests { graph.get(Vec2::xy(5, 1)).unwrap(), ] .iter() - .map(|&cell| unwrap_atom(cell)) + .map(|&cell| cell.unwrap_atom()) .collect::>(); let references = input_atoms.iter().collect::>(); @@ -190,9 +189,9 @@ mod tests { [1, 1; A(C)], [0, 1; B(Single)], [2, 1; B(Single)], - [1, 2; B(Single)] + [1, 2; B(Single)], ); - let input_atom = unwrap_atom(graph.get(Vec2::xy(1, 1)).unwrap()); + let input_atom = graph.get(Vec2::xy(1, 1)).unwrap().unwrap_atom(); let err = check_valence(vec![&input_atom], &graph); assert_eq!(err, Err(InvalidGraphError::UnfilledValence(Vec2::xy(1, 1)))); @@ -204,9 +203,9 @@ mod tests { [1, 1; A(C)], [0, 1; B(Triple)], [2, 1; B(Triple)], - [1, 2; B(Triple)] + [1, 2; B(Triple)], ); - let input_atom = unwrap_atom(graph.get(Vec2::xy(1, 1)).unwrap()); + let input_atom = graph.get(Vec2::xy(1, 1)).unwrap().unwrap_atom(); let err = check_valence(vec![&input_atom], &graph); assert_eq!(