From 90906a17248e8861c5359d59cb0ad453599568c7 Mon Sep 17 00:00:00 2001 From: Gavin Tran Date: Thu, 30 Mar 2023 20:53:26 -0400 Subject: [PATCH] Update macro system (#33) --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/macros.rs | 325 +++++++++++++++++++++++++++++++++++------------- src/main.rs | 1 + src/molecule.rs | 5 +- src/naming.rs | 136 ++------------------ src/numerics.rs | 187 ++++++++++++++++++++++++++++ 7 files changed, 442 insertions(+), 216 deletions(-) create mode 100644 src/numerics.rs diff --git a/Cargo.lock b/Cargo.lock index 09b8f5d..c912262 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,7 +28,7 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chemcreator" -version = "1.2.1" +version = "1.3.0" dependencies = [ "ruscii", "thiserror", diff --git a/Cargo.toml b/Cargo.toml index 5701a42..133fa82 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "chemcreator" -version = "1.2.1" +version = "1.3.0" description = "A text-based tool for identifying organic molecules." authors = ["Gavin Tran"] readme = "README.md" diff --git a/src/macros.rs b/src/macros.rs index 05f6681..5b95e40 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -3,8 +3,8 @@ //! 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::BondOrder::{Double, Single}; -use crate::molecule::Element::{C, H, O}; +use crate::molecule::BondOrder::{Double, Single, Triple}; +use crate::molecule::Element::{C, H, N, O}; use crate::molecule::{BondOrder, BondOrientation, Cell, ComponentType}; use crate::pointer::Pointer; use crate::spatial::{EnumAll, GridState, ToVec2}; @@ -62,112 +62,261 @@ macro_rules! 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)); - } + let x = + carbon_extension(graph) || methane_creation(graph) || carbon_extension_alt(graph); + if !x { + hydrogen_correction(graph, graph.cursor); } - 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); - } + let _ = carbonyl_extension(graph) || hydroxyl_extension(graph); + } + ComponentType::Element(N) => { + let _ = nitrile_extension(graph) || amine_extension(graph); + } + ComponentType::Order(_) => cxc_bond_correction(graph), + _ => {} + } +} - if hydroxyl_ext { - graph.put(third_pos, ComponentType::Element(H)); - hydrogen_correction(graph, first_pos); - } - } +fn cxc_bond_correction(graph: &mut GridState) { + 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) + } +} + +fn amine_extension(graph: &mut GridState) -> bool { + let mut block = block!(graph, [(0, -1), (0, 1), (-1, 1), (1, 1)],); + + for direction in Direction::all() { + 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_pos = match block.borrow(0, 2) { + Ok(it) => it, + Err(_) => continue, } - 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, - }; + .pos(); + let fourth_pos = match block.borrow(0, 3) { + Ok(it) => it, + Err(_) => continue, + } + .pos(); + + let condition = first.is_atom() + && first.unwrap_atom().element == C + && (second.is_empty() || (second.is_atom() && second.unwrap_atom().element == N)); + + if condition { + graph.put(second_pos, ComponentType::Element(N)); + graph.put(graph.cursor, ComponentType::Order(Single)); + graph.put(third_pos, ComponentType::Element(H)); + graph.put(fourth_pos, ComponentType::Element(H)); + hydrogen_correction(graph, first_pos); + return true; + } + } + + false +} + +fn nitrile_extension(graph: &mut GridState) -> bool { + let mut block = block!(graph, [(0, 1), (0, 2)],); + + for direction in Direction::all() { + 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 condition = second.is_atom() && second.unwrap_atom().element == C; + + if condition { + graph.put(first_pos, ComponentType::Order(Triple)); + hydrogen_correction(graph, second_pos); + hydrogen_correction(graph, graph.cursor); + return true; + } + } - if first.element == C && second.element == C { - hydrogen_correction(graph, first.pos); - hydrogen_correction(graph, second.pos) + false +} + +fn carbon_extension(graph: &mut GridState) -> bool { + let mut block = block!(graph, [(0, 1), (0, 2)],); + + for direction in Direction::all() { + 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 atom_condition = second.is_atom() && second.unwrap_atom().element == C; + let bond_condition = second.is_bond() + && BondOrientation::from(direction) == second.unwrap_bond().orient + && !first.is_atom(); + + if atom_condition || bond_condition { + graph.put(first_pos, ComponentType::Order(Single)); + hydrogen_correction(graph, graph.cursor); + + let ptr = Pointer::new(graph, graph.cursor); + let connected_atom_pos = ptr.traverse_bond(direction); + if let Ok(it) = connected_atom_pos { + hydrogen_correction(graph, it.pos); } + return true; } - ComponentType::None => {} // just to appease Clippy - _ => {} } + + false } -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; +fn carbon_extension_alt(graph: &mut GridState) -> bool { + let mut block = block!(graph, [(0, -1), (0, 1)],); for direction in Direction::all() { - if bonds_needed <= 0 { - return; + 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 condition = first.is_atom() + && first.unwrap_atom().element == C + && (second.is_empty() || (second.is_atom() && second.unwrap_atom().element == C)); + + if condition { + graph.put(second_pos, ComponentType::Element(C)); + graph.put(graph.cursor, ComponentType::Order(Single)); + hydrogen_correction(graph, second_pos); + hydrogen_correction(graph, first_pos); + return true; } - if hydrogen_fill(graph, graph.cursor + direction.to_vec2()) { - bonds_needed -= 1; + } + + false +} + +fn methane_creation(graph: &mut GridState) -> bool { + let ptr = Pointer::new(graph, graph.cursor); + + if ptr.connected_directions().is_empty() { + hydrogen_correction(graph, graph.cursor); + return true; + } + + false +} + +fn hydroxyl_extension(graph: &mut GridState) -> bool { + let mut block = block!(graph, [(0, 1), (0, -1)],); + + for direction in Direction::all() { + 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 condition = first.is_atom() && first.unwrap_atom().element == C; + + if condition { + graph.put(second_pos, ComponentType::Element(H)); + hydrogen_correction(graph, first_pos); + return true; + } + } + + false +} + +fn carbonyl_extension(graph: &mut GridState) -> bool { + let mut block = block!(graph, [(0, 1), (0, 2)],); + + for direction in Direction::all() { + 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 condition = second.is_atom() && second.unwrap_atom().element == C; + + if condition { + graph.put(first_pos, ComponentType::Order(Double)); + hydrogen_correction(graph, second_pos); + return true; } } + + false } fn hydrogen_fill(graph: &mut GridState, pos: Vec2) -> bool { let ptr = Pointer::new(graph, pos); - let first_neighbor = !match ptr.borrow() { + let first_neighbor = match ptr.borrow() { Ok(it) => it.is_empty(), Err(_) => return false, }; diff --git a/src/main.rs b/src/main.rs index 3ecee28..f32bf6c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,6 +26,7 @@ mod input; mod macros; mod molecule; mod naming; +mod numerics; mod pointer; mod spatial; mod validation; diff --git a/src/molecule.rs b/src/molecule.rs index 1102384..21ba793 100644 --- a/src/molecule.rs +++ b/src/molecule.rs @@ -5,7 +5,8 @@ use crate::molecule::BondOrder::{Double, Single, Triple}; use crate::molecule::BondOrientation::{Horiz, Vert}; use crate::molecule::Element::{C, H, N, O}; -use crate::naming::{major_numeric, process_name}; +use crate::naming::process_name; +use crate::numerics::major_numeric; use crate::spatial::EnumAll; use ruscii::spatial::{Direction, Vec2}; use ruscii::terminal::Color; @@ -241,7 +242,7 @@ impl Cell { } pub fn is_empty(&self) -> bool { - !matches!(self, Cell::None(_)) + matches!(self, Cell::None(_)) } pub fn unwrap_atom(&self) -> Atom { diff --git a/src/naming.rs b/src/naming.rs index 79b46c6..defa90d 100644 --- a/src/naming.rs +++ b/src/naming.rs @@ -9,7 +9,7 @@ use crate::groups::InvalidGraphError::Other; use crate::molecule::Group::Alkane; use crate::molecule::{Atom, Branch, Group, Substituent}; use crate::spatial::GridState; -use crate::{chain, groups, validation}; +use crate::{chain, groups, numerics, validation}; use chain::get_all_chains; use groups::link_groups; use std::fmt::{Display, Formatter}; @@ -61,7 +61,7 @@ pub(crate) fn process_name(branch: Branch) -> Result { let name = format!( "{}{}{}{}", prefix(collection.secondary_group_fragments())?, - major_numeric(len)?, + numerics::major_numeric(len)?, bonding(collection.unsaturated_group_fragments())?, suffix(collection.primary_group_fragment())?, ); @@ -89,7 +89,7 @@ pub(crate) fn substitute_names(name: &str) -> Option<&str> { /// /// ## Errors /// -/// If the occurrences of one of the `fragments` exceeds the limit of [`minor_numeric`], a +/// If the occurrences of one of the `fragments` exceeds the limit of [`numerics::minor_numeric`], a /// [`NamingError::GroupOccurrence`] is returned. pub(crate) fn prefix(mut fragments: Vec) -> Result { fragments.sort_by_key(|fragment| fragment.subst.to_string()); @@ -170,7 +170,7 @@ fn suffix(fragment: SubFragment) -> Result { /// /// ## Errors /// -/// If the number of `locations` given exceeds the limit of [`minor_numeric`], a +/// If the number of `locations` given exceeds the limit of [`numerics::minor_numeric`], a /// [`NamingError::GroupOccurrence`] without a group is returned. pub fn locants(mut locations: Vec) -> Result { locations.sort(); @@ -181,7 +181,10 @@ pub fn locants(mut locations: Vec) -> Result { .collect::>() .join(","); - let out = format!("{numbers}-{}", minor_numeric(locations.len() as i32)?,); + let out = format!( + "{numbers}-{}", + numerics::minor_numeric(locations.len() as i32)?, + ); Ok(out) } @@ -194,125 +197,10 @@ pub fn complex_branch_locants(mut locations: Vec) -> Result>() .join(","); - let out = format!("{numbers}-{}", branch_numeric(locations.len() as i32)?,); - Ok(out) -} - -/// Returns the numeric prefix for group prefixes for the given `value`. -/// -/// ## Errors -/// -/// If `value` exceeds 20, a [`NamingError::GroupOccurrence`] without a group is returned. -pub fn minor_numeric(value: i32) -> Result<&'static str, NamingError> { - let out = match value { - 1 => "", - 2 => "di", - 3 => "tri", - 4 => "tetra", - 5 => "penta", - 6 => "hexa", - 7 => "hepta", - 8 => "octa", - 9 => "nona", - 10 => "deca", - 11 => "undeca", - 12 => "dodeca", - 13 => "trideca", - 14 => "tetradeca", - 15 => "pentadeca", - 16 => "hexadeca", - 17 => "heptadeca", - 18 => "octadeca", - 19 => "nonadeca", - 20 => "icosa", - _ => return Err(NamingError::GroupOccurrence(None, value)), - }; - Ok(out) -} - -/// Returns the numeric prefix for branch prefixes for the given `value`. -/// -/// ## Errors -/// -/// If `value` exceeds 20, a [`NamingError::GroupOccurrence`] without a group is returned. -pub fn branch_numeric(value: i32) -> Result<&'static str, NamingError> { - let out = match value { - 1 => "", - 2 => "bis", - 3 => "tris", - 4 => "tetrakis", - 5 => "pentakis", - 6 => "hexakis", - 7 => "heptakis", - 8 => "octakis", - 9 => "nonakis", - 10 => "decakis", - 11 => "undecakis", - 12 => "dodecakis", - 13 => "tridecakis", - 14 => "tetradecakis", - 15 => "pentadecakis", - 16 => "hexadecakis", - 17 => "heptadecakis", - 18 => "octadecakis", - 19 => "nonadecakis", - 20 => "icosakis", - _ => return Err(NamingError::GroupOccurrence(None, value)), - }; - Ok(out) -} - -/// Returns the numeric prefix for suffixes for the given `value`. -/// -/// ## Errors -/// -/// If `value` exceeds 42, a [`NamingError::CarbonCount`] is returned. -pub fn major_numeric(value: i32) -> Result<&'static str, NamingError> { - let out = match value { - 1 => "meth", - 2 => "eth", - 3 => "prop", - 4 => "but", - 5 => "pent", - 6 => "hex", - 7 => "hept", - 8 => "oct", - 9 => "non", - 10 => "dec", - 11 => "undec", - 12 => "dodec", - 13 => "tridec", - 14 => "tetradec", - 15 => "pentadec", - 16 => "hexadec", - 17 => "heptadec", - 18 => "octadec", - 19 => "nonadec", - 20 => "icos", - 21 => "henicos", - 22 => "docos", - 23 => "tricos", - 24 => "tetracos", - 25 => "pentacos", - 26 => "hexacos", - 27 => "heptacos", - 28 => "octacos", - 29 => "nonacos", - 30 => "triacont", - 31 => "untriacont", - 32 => "dotriacont", - 33 => "tritriacont", - 34 => "tetratriacont", - 35 => "pentatriacont", - 36 => "hexatriacont", - 37 => "heptatriacont", - 38 => "octatriacont", - 39 => "nonatriacont", - 40 => "tetracont", - 41 => "hentetracont", - 42 => "dotetracont", - _ => return Err(NamingError::CarbonCount(value)), - }; + let out = format!( + "{numbers}-{}", + numerics::branch_numeric(locations.len() as i32)?, + ); Ok(out) } diff --git a/src/numerics.rs b/src/numerics.rs new file mode 100644 index 0000000..d43bb49 --- /dev/null +++ b/src/numerics.rs @@ -0,0 +1,187 @@ +//! # Numerics +//! +//! The `numerics` module provides functions that give names to numbers. + +use crate::naming::NamingError; + +/// Returns the numeric prefix for group prefixes for the given `value`. +/// +/// ## Errors +/// +/// If `value` exceeds 20, a [`NamingError::GroupOccurrence`] without a group is returned. +pub fn minor_numeric(value: i32) -> Result<&'static str, NamingError> { + let out = match value { + 1 => "", + 2 => "di", + 3 => "tri", + 4 => "tetra", + 5 => "penta", + 6 => "hexa", + 7 => "hepta", + 8 => "octa", + 9 => "nona", + 10 => "deca", + 11 => "undeca", + 12 => "dodeca", + 13 => "trideca", + 14 => "tetradeca", + 15 => "pentadeca", + 16 => "hexadeca", + 17 => "heptadeca", + 18 => "octadeca", + 19 => "nonadeca", + 20 => "icosa", + 21 => "henicosa", + 22 => "docosa", + 23 => "tricosa", + 24 => "tetracosa", + 25 => "pentacosa", + 26 => "hexacosa", + 27 => "heptacosa", + 28 => "octacosa", + 29 => "nonacosa", + 30 => "triconta", + 31 => "hentriconta", + 32 => "dotriconta", + 33 => "tritriconta", + 34 => "tetratriconta", + 35 => "pentatriconta", + 36 => "hexatriconta", + 37 => "heptatriconta", + 38 => "octatriconta", + 39 => "nonatriconta", + 40 => "tetraconta", + 41 => "hentetraconta", + 42 => "dotetraconta", + _ => return Err(NamingError::GroupOccurrence(None, value)), + }; + Ok(out) +} + +/// Returns the numeric prefix for branch prefixes for the given `value`. +/// +/// ## Errors +/// +/// If `value` exceeds 20, a [`NamingError::GroupOccurrence`] without a group is returned. +pub fn branch_numeric(value: i32) -> Result<&'static str, NamingError> { + let out = match value { + 1 => "", + 2 => "bis", + 3 => "tris", + 4 => "tetrakis", + 5 => "pentakis", + 6 => "hexakis", + 7 => "heptakis", + 8 => "octakis", + 9 => "nonakis", + 10 => "decakis", + 11 => "undecakis", + 12 => "dodecakis", + 13 => "tridecakis", + 14 => "tetradecakis", + 15 => "pentadecakis", + 16 => "hexadecakis", + 17 => "heptadecakis", + 18 => "octadecakis", + 19 => "nonadecakis", + 20 => "icosakis", + _ => return Err(NamingError::GroupOccurrence(None, value)), + }; + Ok(out) +} + +/// Returns the numeric prefix for suffixes for the given `value`. +/// +/// ## Errors +/// +/// If `value` exceeds 84, a [`NamingError::CarbonCount`] is returned. +pub fn major_numeric(value: i32) -> Result<&'static str, NamingError> { + let out = match value { + 1 => "meth", + 2 => "eth", + 3 => "prop", + 4 => "but", + 5 => "pent", + 6 => "hex", + 7 => "hept", + 8 => "oct", + 9 => "non", + 10 => "dec", + 11 => "undec", + 12 => "dodec", + 13 => "tridec", + 14 => "tetradec", + 15 => "pentadec", + 16 => "hexadec", + 17 => "heptadec", + 18 => "octadec", + 19 => "nonadec", + 20 => "icos", + 21 => "henicos", + 22 => "docos", + 23 => "tricos", + 24 => "tetracos", + 25 => "pentacos", + 26 => "hexacos", + 27 => "heptacos", + 28 => "octacos", + 29 => "nonacos", + 30 => "triacont", + 31 => "untriacont", + 32 => "dotriacont", + 33 => "tritriacont", + 34 => "tetratriacont", + 35 => "pentatriacont", + 36 => "hexatriacont", + 37 => "heptatriacont", + 38 => "octatriacont", + 39 => "nonatriacont", + 40 => "tetracont", + 41 => "hentetracont", + 42 => "dotetracont", + 43 => "tritetracont", + 44 => "tetratetracont", + 45 => "pentatetracont", + 46 => "hexatetracont", + 47 => "heptatetracont", + 48 => "octatetracont", + 49 => "nonatetracont", + 50 => "pentacont", + 51 => "henpentacont", + 52 => "dopentacont", + 53 => "tripentacont", + 54 => "tetrapentacont", + 55 => "pentapentacont", + 56 => "hexapentacont", + 57 => "heptapentacont", + 58 => "octapentacont", + 59 => "nonapentacont", + 60 => "hexacont", + 61 => "henhexacont", + 62 => "dohexacont", + 63 => "trihexacont", + 64 => "tetrahexacont", + 65 => "pentahexacont", + 66 => "hexahexacont", + 67 => "heptahexacont", + 68 => "octahexacont", + 69 => "nonahexacont", + 70 => "heptacont", + 71 => "henheptacont", + 72 => "doheptacont", + 73 => "triheptacont", + 74 => "tetraheptacont", + 75 => "pentaheptacont", + 76 => "hexaheptacont", + 77 => "heptaheptacont", + 78 => "octaheptacont", + 79 => "nonaheptacont", + 80 => "octacont", + 81 => "henoctacont", + 82 => "dooctacont", + 83 => "trioctacont", + 84 => "tetraoctacont", + _ => return Err(NamingError::CarbonCount(value)), + }; + Ok(out) +}