Skip to content

Commit 22ff1a4

Browse files
committed
Cli interface for stoik
1 parent 6f9127d commit 22ff1a4

4 files changed

Lines changed: 241 additions & 65 deletions

File tree

stoik-gui/src/app.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ impl StoikApp {
165165
.monospace(),
166166
);
167167
} else if !self.mode_data.lhs_mols.is_empty() && !self.mode_data.rhs_mols.is_empty() {
168-
self.show_balance_summery(ui);
168+
self.show_balance_summary(ui);
169169
}
170170
}
171171

@@ -346,7 +346,7 @@ impl StoikApp {
346346
}
347347
}
348348

349-
fn show_balance_summery(&mut self, ui: &mut Ui) {
349+
fn show_balance_summary(&mut self, ui: &mut Ui) {
350350
if self.mode_data.changed {
351351
self.mode_data.lhs.clear();
352352
self.mode_data.rhs.clear();
@@ -376,9 +376,9 @@ impl StoikApp {
376376
let balanced = self.mode_data.balanced.values().all(|x| *x);
377377

378378
if balanced {
379-
ui.heading("Your equasion is balanced");
379+
ui.heading("Your equation is balanced");
380380
} else {
381-
ui.heading("Your equasion is not balanced");
381+
ui.heading("Your equation is not balanced");
382382
}
383383

384384
if self.all_atoms || !balanced {

stoik/src/formula/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//! This module is the main module for parsing chemical equasions
1+
//! This module is the main module for parsing chemical equations
22
//!
33
//! See the documentaion for [`Molecule`], [`assemble_tree`], [`TokenStream`] for more info
44
//! ```
@@ -56,7 +56,7 @@ pub enum SyntaxNode {
5656
/// It requires an iterator of [`Token`] passed to it, idealy to be
5757
/// based off of [`TokenStream`]
5858
///
59-
/// # Exampless
59+
/// # Examples
6060
///
6161
/// Basic usage:
6262
/// ```

stoik/src/help_msg.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Usage: stoik [FLAGS] ... [EQUATION] ...
2+
Computes whether EQUATION is chemically balanced or not
3+
4+
All non-flag args are concatonated together to form EQUATION
5+
e.g. `stoik A + B -> C + D` is the same as `stoik "A + B -> C + D"`
6+
7+
-t, --time Shows the time taken for the processing of each formula
8+
-h, --help Shows this

stoik/src/main.rs

Lines changed: 227 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,79 +1,247 @@
11
mod err;
22
mod formula;
33

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,
98
};
109

11-
fn main() {
12-
// troll Na15[Mo126Mo28O462H14(H2O)70][Mo124Mo28O457H14(H2O)68]
10+
use err::StoikError;
11+
use formula::{Molecule, TokenStream};
1312

14-
let total_inst = Instant::now();
13+
const HELP_MSG: &str = include_str!("help_msg.txt");
1514

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<_>>();
1824

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+
}
2236
}
23-
println!("Took {:.3?} to tokenise", stream_inst.elapsed());
37+
equation = equation.trim().to_string();
2438

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;
2942
}
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;
4057
}
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())),
4165
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;
6168
}
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+
}
64120

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(),
69139
}
70-
println!("Took {:.3?} to build molecules", molar_inst.elapsed());
140+
}
71141

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);
76149
}
77150
}
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+
}
79247
}

0 commit comments

Comments
 (0)