|
1 | 1 | mod err; |
2 | 2 | mod formula; |
3 | 3 |
|
4 | | -use std::{env, time::Instant}; |
5 | | - |
6 | | -use crate::{ |
7 | | - err::StoikError, |
8 | | - formula::{assemble_tree, Molecule, TokenStream}, |
| 4 | +use std::{ |
| 5 | + collections::{hash_map::Entry, HashMap}, |
| 6 | + env, |
| 7 | + time::Instant, |
9 | 8 | }; |
10 | 9 |
|
11 | | -fn main() { |
12 | | - // troll Na15[Mo126Mo28O462H14(H2O)70][Mo124Mo28O457H14(H2O)68] |
| 10 | +use err::StoikError; |
| 11 | +use formula::{Molecule, TokenStream}; |
13 | 12 |
|
14 | | - let total_inst = Instant::now(); |
| 13 | +const HELP_MSG: &str = include_str!("help_msg.txt"); |
15 | 14 |
|
16 | | - let args: Vec<String> = env::args().skip(1).collect(); |
17 | | - let mut streams = Vec::new(); |
| 15 | +fn main() { |
| 16 | + // C30H64N2O2Si4 (s) + C8H4Cl2O2 (s) -> C26H38N2O4Si2 (s) + 2C6H15ClSi (s) |
| 17 | + let mut time_mode = false; |
| 18 | + let mut all_moles = false; |
| 19 | + let mut equation = String::new(); |
| 20 | + let mut time_table = vec!["Formula", "Tokenise", "Tree building", "Parsing", "Total"] |
| 21 | + .iter() |
| 22 | + .map(|x| vec![x.to_string()]) |
| 23 | + .collect::<Vec<_>>(); |
18 | 24 |
|
19 | | - let stream_inst = Instant::now(); |
20 | | - for i in args.iter() { |
21 | | - streams.push(TokenStream::new(i).collect::<Vec<_>>().into_iter()); |
| 25 | + for arg in env::args().skip(1) { |
| 26 | + if arg == "--time" || arg == "-t" { |
| 27 | + time_mode = true; |
| 28 | + } else if arg == "--help" || arg == "-h" { |
| 29 | + println!("{}", HELP_MSG); |
| 30 | + return; |
| 31 | + } else if arg == "--all-moles" || arg == "-a" { |
| 32 | + all_moles = true; |
| 33 | + } else { |
| 34 | + equation = format!("{equation} {arg}"); |
| 35 | + } |
22 | 36 | } |
23 | | - println!("Took {:.3?} to tokenise", stream_inst.elapsed()); |
| 37 | + equation = equation.trim().to_string(); |
24 | 38 |
|
25 | | - let mut trees = Vec::new(); |
26 | | - let tree_inst = Instant::now(); |
27 | | - for stream in streams { |
28 | | - trees.push(assemble_tree(stream)); |
| 39 | + if !(equation.contains("->") || equation.contains("=>")) { |
| 40 | + println!("Products are not given, please use `=>` or `->` to seperate the two sides"); |
| 41 | + return; |
29 | 42 | } |
30 | | - println!("Took {:.3?} to build trees", tree_inst.elapsed()); |
31 | | - |
32 | | - let filtered_trees = trees |
33 | | - .into_iter() |
34 | | - .enumerate() |
35 | | - .filter_map(|(i, tree)| match tree { |
36 | | - Ok(tree) => { |
37 | | - #[cfg(debug_assertions)] |
38 | | - println!("{}: Syntax tree built", &args[i]); |
39 | | - Some(tree) |
| 43 | + |
| 44 | + let temp_equ = equation.replace("=>", "->"); |
| 45 | + let (mut reactants_str, mut products_str) = temp_equ.split_once("->").unwrap(); |
| 46 | + |
| 47 | + reactants_str = reactants_str.trim(); |
| 48 | + products_str = products_str.trim(); |
| 49 | + |
| 50 | + let mut reactants = Vec::new(); |
| 51 | + for formula in reactants_str.split('+').map(|x| x.trim()) { |
| 52 | + match construct_mole(formula, time_mode, &mut time_table) { |
| 53 | + Ok(mol) => reactants.push((mol, formula.to_string())), |
| 54 | + Err(e) => { |
| 55 | + println!("{}", generate_error_msg(e, formula)); |
| 56 | + return; |
40 | 57 | } |
| 58 | + } |
| 59 | + } |
| 60 | + |
| 61 | + let mut products = Vec::new(); |
| 62 | + for formula in products_str.split('+').map(|x| x.trim()) { |
| 63 | + match construct_mole(formula, time_mode, &mut time_table) { |
| 64 | + Ok(mol) => products.push((mol, formula.to_string())), |
41 | 65 | Err(e) => { |
42 | | - let msg = match e { |
43 | | - StoikError::InvalidToken(loc) => { |
44 | | - loc.format_msg(&args[i], "Malformed formula", "Illegal token") |
45 | | - } |
46 | | - StoikError::NumberFirst(loc) => loc.format_msg( |
47 | | - &args[i], |
48 | | - "Malformed formula", |
49 | | - "Compound groups cannot start with numbers", |
50 | | - ), |
51 | | - StoikError::UnpairedParenthesis(loc) => { |
52 | | - loc.format_msg(&args[i], "Malformed formula", "Unpaired parenthesis") |
53 | | - } |
54 | | - StoikError::UnpairedBracket(loc) => { |
55 | | - loc.format_msg(&args[i], "Malformed formula", "Unpaired bracket") |
56 | | - } |
57 | | - e => e.to_string(), |
58 | | - }; |
59 | | - println!("{msg}"); |
60 | | - None |
| 66 | + println!("{}", generate_error_msg(e, formula)); |
| 67 | + return; |
61 | 68 | } |
62 | | - }) |
63 | | - .collect::<Vec<_>>(); |
| 69 | + } |
| 70 | + } |
| 71 | + |
| 72 | + let mut lhs = HashMap::new(); |
| 73 | + for mol in reactants { |
| 74 | + extend_mol_map(&mut lhs, mol.0.get_map()); |
| 75 | + } |
| 76 | + |
| 77 | + let mut rhs = HashMap::new(); |
| 78 | + for mol in products { |
| 79 | + extend_mol_map(&mut rhs, mol.0.get_map()); |
| 80 | + } |
| 81 | + |
| 82 | + let mut keys = lhs.keys().collect::<Vec<_>>(); |
| 83 | + let mut balanced = HashMap::new(); |
| 84 | + keys.extend(rhs.keys()); |
| 85 | + keys.dedup(); |
| 86 | + |
| 87 | + for key in keys { |
| 88 | + balanced.insert(key.to_string(), lhs.get(key) == rhs.get(key)); |
| 89 | + } |
| 90 | + |
| 91 | + let is_balanced = balanced.values().all(|x| *x); |
| 92 | + |
| 93 | + if is_balanced { |
| 94 | + println!("`{equation}` is balanced") |
| 95 | + } else { |
| 96 | + println!("`{equation}` is not balanced") |
| 97 | + } |
| 98 | + |
| 99 | + if !is_balanced || all_moles { |
| 100 | + let mut table = vec!["Element", "Reactants", "Products", "Balanced"] |
| 101 | + .iter() |
| 102 | + .map(|x| vec![x.to_string()]) |
| 103 | + .collect::<Vec<_>>(); |
| 104 | + for (element, bal) in balanced { |
| 105 | + if !bal || all_moles { |
| 106 | + table[1].push(lhs[&element].to_string()); |
| 107 | + table[2].push(rhs[&element].to_string()); |
| 108 | + table[0].push(element); |
| 109 | + table[3].push(bal.to_string()); |
| 110 | + } |
| 111 | + } |
| 112 | + print_table(table); |
| 113 | + } |
| 114 | + |
| 115 | + if time_mode { |
| 116 | + println!("\nTime summary"); |
| 117 | + print_table(time_table); |
| 118 | + } |
| 119 | +} |
64 | 120 |
|
65 | | - let mut molecules = Vec::new(); |
66 | | - let molar_inst = Instant::now(); |
67 | | - for tree in filtered_trees { |
68 | | - molecules.push(Molecule::construct_from_tree(tree)); |
| 121 | +// Move to a pub func from being copy pasted here and in stoik-gui/src/main.rs |
| 122 | +fn generate_error_msg(e: StoikError, formula: &str) -> String { |
| 123 | + match e { |
| 124 | + StoikError::InvalidToken(loc) => { |
| 125 | + loc.format_msg(formula, "Malformed formula", "Illegal token") |
| 126 | + } |
| 127 | + StoikError::NumberFirst(loc) => loc.format_msg( |
| 128 | + formula, |
| 129 | + "Malformed formula", |
| 130 | + "Compound groups cannot start with numbers", |
| 131 | + ), |
| 132 | + StoikError::UnpairedParenthesis(loc) => { |
| 133 | + loc.format_msg(formula, "Malformed formula", "Unpaired parenthesis") |
| 134 | + } |
| 135 | + StoikError::UnpairedBracket(loc) => { |
| 136 | + loc.format_msg(formula, "Malformed formula", "Unpaired bracket") |
| 137 | + } |
| 138 | + e => e.to_string(), |
69 | 139 | } |
70 | | - println!("Took {:.3?} to build molecules", molar_inst.elapsed()); |
| 140 | +} |
71 | 141 |
|
72 | | - for (i, mole) in molecules.into_iter().enumerate() { |
73 | | - match mole { |
74 | | - Ok(mole) => println!("{} -> {mole}", args[i]), |
75 | | - Err(e) => println!("Error with {}: {e}", args[i]), |
| 142 | +// same as generate_error_msg |
| 143 | +fn extend_mol_map(main: &mut HashMap<String, i64>, mol: HashMap<String, i64>) { |
| 144 | + for (key, mol_val) in mol { |
| 145 | + if let Entry::Occupied(mut entry) = main.entry(key.clone()) { |
| 146 | + *entry.get_mut() += mol_val; |
| 147 | + } else { |
| 148 | + main.insert(key, mol_val); |
76 | 149 | } |
77 | 150 | } |
78 | | - println!("Took {:.3?} in total", total_inst.elapsed()); |
| 151 | +} |
| 152 | + |
| 153 | +// This is some *sus* code |
| 154 | +// im too tired to write nice code for it |
| 155 | +fn print_table(table: Vec<Vec<String>>) { |
| 156 | + let widths = table |
| 157 | + .iter() |
| 158 | + .map(|x| x.iter().max_by(|x, y| x.len().cmp(&y.len())).unwrap()) |
| 159 | + .map(|x| x.len()) |
| 160 | + .collect::<Vec<usize>>(); |
| 161 | + println!( |
| 162 | + "{}", |
| 163 | + "╔═".to_string() |
| 164 | + + &widths |
| 165 | + .iter() |
| 166 | + .map(|x| "═".repeat(*x)) |
| 167 | + .collect::<Vec<String>>() |
| 168 | + .join("═╦═") |
| 169 | + + "═╗" |
| 170 | + ); |
| 171 | + println!( |
| 172 | + "{}", |
| 173 | + "║ ".to_string() |
| 174 | + + &table |
| 175 | + .iter() |
| 176 | + .enumerate() |
| 177 | + .map(|(pos, x)| pad_string(&x[0], widths[pos])) |
| 178 | + .collect::<Vec<String>>() |
| 179 | + .join(" ║ ") |
| 180 | + + " ║" |
| 181 | + ); |
| 182 | + println!( |
| 183 | + "{}", |
| 184 | + "╠═".to_string() |
| 185 | + + &widths |
| 186 | + .iter() |
| 187 | + .map(|x| "═".repeat(*x)) |
| 188 | + .collect::<Vec<String>>() |
| 189 | + .join("═╬═") |
| 190 | + + "═╣" |
| 191 | + ); |
| 192 | + for i in 1..table[0].len() { |
| 193 | + println!( |
| 194 | + "{}", |
| 195 | + "║ ".to_string() |
| 196 | + + &table |
| 197 | + .iter() |
| 198 | + .enumerate() |
| 199 | + .map(|(pos, x)| pad_string(&x[i], widths[pos])) |
| 200 | + .collect::<Vec<String>>() |
| 201 | + .join(" ║ ") |
| 202 | + + " ║" |
| 203 | + ); |
| 204 | + } |
| 205 | + println!( |
| 206 | + "{}", |
| 207 | + "╚═".to_string() |
| 208 | + + &widths |
| 209 | + .iter() |
| 210 | + .map(|x| "═".repeat(*x)) |
| 211 | + .collect::<Vec<String>>() |
| 212 | + .join("═╩═") |
| 213 | + + "═╝" |
| 214 | + ); |
| 215 | +} |
| 216 | + |
| 217 | +fn pad_string(input: &String, length: usize) -> String { |
| 218 | + let pad_len = length - input.chars().count(); |
| 219 | + input.to_string() + &" ".repeat(pad_len) |
| 220 | +} |
| 221 | + |
| 222 | +fn construct_mole( |
| 223 | + formula: &str, |
| 224 | + time_mode: bool, |
| 225 | + time_table: &mut Vec<Vec<String>>, |
| 226 | +) -> Result<Molecule, StoikError> { |
| 227 | + if time_mode { |
| 228 | + time_table[0].push(formula.to_string()); |
| 229 | + |
| 230 | + let tokenise_inst = Instant::now(); |
| 231 | + let tokenstream = TokenStream::new(formula); |
| 232 | + time_table[1].push(format!("{:>09.3?}", tokenise_inst.elapsed())); |
| 233 | + |
| 234 | + let tree_inst = Instant::now(); |
| 235 | + let root = formula::assemble_tree(tokenstream)?; |
| 236 | + time_table[2].push(format!("{:>09.3?}", tree_inst.elapsed())); |
| 237 | + |
| 238 | + let mol_inst = Instant::now(); |
| 239 | + let mol = Molecule::construct_from_tree(root)?; |
| 240 | + time_table[3].push(format!("{:>09.3?}", mol_inst.elapsed())); |
| 241 | + |
| 242 | + time_table[4].push(format!("{:>09.3?}", tokenise_inst.elapsed())); |
| 243 | + Ok(mol) |
| 244 | + } else { |
| 245 | + Molecule::from_formula(formula) |
| 246 | + } |
79 | 247 | } |
0 commit comments