Initial commit
This commit is contained in:
		
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					/target
 | 
				
			||||||
							
								
								
									
										8
									
								
								.idea/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								.idea/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
				
			|||||||
 | 
					# Default ignored files
 | 
				
			||||||
 | 
					/shelf/
 | 
				
			||||||
 | 
					/workspace.xml
 | 
				
			||||||
 | 
					# Editor-based HTTP Client requests
 | 
				
			||||||
 | 
					/httpRequests/
 | 
				
			||||||
 | 
					# Datasource local storage ignored files
 | 
				
			||||||
 | 
					/dataSources/
 | 
				
			||||||
 | 
					/dataSources.local.xml
 | 
				
			||||||
							
								
								
									
										11
									
								
								.idea/mcalc.iml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								.idea/mcalc.iml
									
									
									
										generated
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					<?xml version="1.0" encoding="UTF-8"?>
 | 
				
			||||||
 | 
					<module type="CPP_MODULE" version="4">
 | 
				
			||||||
 | 
					  <component name="NewModuleRootManager">
 | 
				
			||||||
 | 
					    <content url="file://$MODULE_DIR$">
 | 
				
			||||||
 | 
					      <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
 | 
				
			||||||
 | 
					      <excludeFolder url="file://$MODULE_DIR$/target" />
 | 
				
			||||||
 | 
					    </content>
 | 
				
			||||||
 | 
					    <orderEntry type="inheritedJdk" />
 | 
				
			||||||
 | 
					    <orderEntry type="sourceFolder" forTests="false" />
 | 
				
			||||||
 | 
					  </component>
 | 
				
			||||||
 | 
					</module>
 | 
				
			||||||
							
								
								
									
										6
									
								
								.idea/misc.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.idea/misc.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
				
			|||||||
 | 
					<?xml version="1.0" encoding="UTF-8"?>
 | 
				
			||||||
 | 
					<project version="4">
 | 
				
			||||||
 | 
					  <component name="MarkdownSettingsMigration">
 | 
				
			||||||
 | 
					    <option name="stateVersion" value="1" />
 | 
				
			||||||
 | 
					  </component>
 | 
				
			||||||
 | 
					</project>
 | 
				
			||||||
							
								
								
									
										8
									
								
								.idea/modules.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								.idea/modules.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
				
			|||||||
 | 
					<?xml version="1.0" encoding="UTF-8"?>
 | 
				
			||||||
 | 
					<project version="4">
 | 
				
			||||||
 | 
					  <component name="ProjectModuleManager">
 | 
				
			||||||
 | 
					    <modules>
 | 
				
			||||||
 | 
					      <module fileurl="file://$PROJECT_DIR$/.idea/mcalc.iml" filepath="$PROJECT_DIR$/.idea/mcalc.iml" />
 | 
				
			||||||
 | 
					    </modules>
 | 
				
			||||||
 | 
					  </component>
 | 
				
			||||||
 | 
					</project>
 | 
				
			||||||
							
								
								
									
										6
									
								
								.idea/vcs.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.idea/vcs.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
				
			|||||||
 | 
					<?xml version="1.0" encoding="UTF-8"?>
 | 
				
			||||||
 | 
					<project version="4">
 | 
				
			||||||
 | 
					  <component name="VcsDirectoryMappings">
 | 
				
			||||||
 | 
					    <mapping directory="" vcs="Git" />
 | 
				
			||||||
 | 
					  </component>
 | 
				
			||||||
 | 
					</project>
 | 
				
			||||||
							
								
								
									
										2435
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										2435
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										30
									
								
								Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								Cargo.toml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
				
			|||||||
 | 
					[package]
 | 
				
			||||||
 | 
					name = "mcalc"
 | 
				
			||||||
 | 
					version = "0.1.0"
 | 
				
			||||||
 | 
					edition = "2021"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[dependencies]
 | 
				
			||||||
 | 
					glium = "0.32.1"
 | 
				
			||||||
 | 
					imgui-glium-renderer = "0.11.0"
 | 
				
			||||||
 | 
					imgui-winit-support = "0.11.0"
 | 
				
			||||||
 | 
					rfd = "0.11.3"
 | 
				
			||||||
 | 
					serde_json = "1.0.96"
 | 
				
			||||||
 | 
					thousands = "0.2.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[dependencies.serde]
 | 
				
			||||||
 | 
					version = "1.0.160"
 | 
				
			||||||
 | 
					features = ["derive"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[dependencies.lalrpop-util]
 | 
				
			||||||
 | 
					version = "0.19.12"
 | 
				
			||||||
 | 
					features = ["lexer"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[dependencies.imgui]
 | 
				
			||||||
 | 
					version = "0.11.0"
 | 
				
			||||||
 | 
					features = ["docking", "tables-api"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[build-dependencies]
 | 
				
			||||||
 | 
					lalrpop = "0.19.12"
 | 
				
			||||||
							
								
								
									
										5
									
								
								build.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								build.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					extern crate lalrpop;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn main() {
 | 
				
			||||||
 | 
					    lalrpop::process_root().unwrap();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										43
									
								
								src/grammar.lalrpop
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/grammar.lalrpop
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,43 @@
 | 
				
			|||||||
 | 
					use crate::parser::Node;
 | 
				
			||||||
 | 
					use std::str::FromStr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					grammar;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Num: f64 = {
 | 
				
			||||||
 | 
					    r"[0-9]+(\.[0-9]+)?" => f64::from_str(<>).unwrap_or_else(|e| { println!("{}", <>); panic!("{:#?}", e); })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Ident: String = {
 | 
				
			||||||
 | 
					    r"[a-zA-Z_][a-zA-Z0-9_]*" => String::from(<>)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Var: Node = {
 | 
				
			||||||
 | 
					    Ident => Node::Var(<>),
 | 
				
			||||||
 | 
					    Num => Node::Num(<>),
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					E3: Node = {
 | 
				
			||||||
 | 
					    Var,
 | 
				
			||||||
 | 
					    "(" <e:E0> ")" => e,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					E2: Node = {
 | 
				
			||||||
 | 
					    "-" <e:E3> => Node::Neg(Box::new(e)),
 | 
				
			||||||
 | 
					    E3
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					E1: Node = {
 | 
				
			||||||
 | 
					    <l:E1> "*" <r:E2> => Node::Mul(Box::new(l), Box::new(r)),
 | 
				
			||||||
 | 
					    <l:E1> "/" <r:E2> => Node::Div(Box::new(l), Box::new(r)),
 | 
				
			||||||
 | 
					    E2
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					E0: Node = {
 | 
				
			||||||
 | 
					    <l:E0> "+" <r:E1> => Node::Plus(Box::new(l), Box::new(r)),
 | 
				
			||||||
 | 
					    <l:E0> "-" <r:E1> => Node::Minus(Box::new(l), Box::new(r)),
 | 
				
			||||||
 | 
					    E1
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub Assign: (String, Node) = {
 | 
				
			||||||
 | 
					    <i:Ident> "=" <e:E0> => (i, e)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										256
									
								
								src/main.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										256
									
								
								src/main.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,256 @@
 | 
				
			|||||||
 | 
					mod parser;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use glium::Surface;
 | 
				
			||||||
 | 
					use imgui::{Condition, Context, FocusedWidget, TableFlags, Ui, WindowFlags};
 | 
				
			||||||
 | 
					use lalrpop_util::lalrpop_mod;
 | 
				
			||||||
 | 
					use thousands::Separable;
 | 
				
			||||||
 | 
					use crate::parser::Node;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					lalrpop_mod!(grammar);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, serde::Deserialize, serde::Serialize)]
 | 
				
			||||||
 | 
					struct Variable {
 | 
				
			||||||
 | 
					    name: String,
 | 
				
			||||||
 | 
					    expr: String,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[serde(skip)]
 | 
				
			||||||
 | 
					    value: Option<f64>,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    parsed_expr: Node,
 | 
				
			||||||
 | 
					    used: Vec<String>
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Default)]
 | 
				
			||||||
 | 
					struct State {
 | 
				
			||||||
 | 
					    inputs: Vec<String>,
 | 
				
			||||||
 | 
					    input_buf: String,
 | 
				
			||||||
 | 
					    error_msg: Option<String>,
 | 
				
			||||||
 | 
					    variables: Vec<Variable>,
 | 
				
			||||||
 | 
					    last_eval: Option<std::time::Duration>
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn eval_vars(state: &mut State) {
 | 
				
			||||||
 | 
					    let start = std::time::Instant::now();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let var_map = state.variables.iter()
 | 
				
			||||||
 | 
					        .map(|v| (v.name.clone(), v.parsed_expr.clone()))
 | 
				
			||||||
 | 
					        .collect();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for v in &mut state.variables {
 | 
				
			||||||
 | 
					        let val = v.parsed_expr.eval(&var_map);
 | 
				
			||||||
 | 
					        v.value = val;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    state.last_eval = Some(start.elapsed());
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn handle_input(input: String, state: &mut State) -> bool {
 | 
				
			||||||
 | 
					    state.error_msg.take();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let node: Result<(String, Node), lalrpop_util::ParseError<usize, lalrpop_util::lexer::Token<'_>, &str>>
 | 
				
			||||||
 | 
					        = grammar::AssignParser::new().parse(&input);
 | 
				
			||||||
 | 
					    match node  {
 | 
				
			||||||
 | 
					        Ok((name, expr)) => {
 | 
				
			||||||
 | 
					            let used_vars = expr.used_vars();
 | 
				
			||||||
 | 
					            if used_vars.contains(&name) {
 | 
				
			||||||
 | 
					                state.error_msg = Some("Variable can't depend on itself".to_string());
 | 
				
			||||||
 | 
					                return false;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            let seen = vec![name.clone()];
 | 
				
			||||||
 | 
					            if let Some(mut e) = used_vars.iter().filter_map(|v| find_dependency_recursive(state, v, seen.clone()).err()).next() {
 | 
				
			||||||
 | 
					                e.push(name.clone());
 | 
				
			||||||
 | 
					                e.reverse();
 | 
				
			||||||
 | 
					                e.push(name.clone());
 | 
				
			||||||
 | 
					                state.error_msg = Some(format!("Found recursive dependency:\n{}", e.join(" -> ")));
 | 
				
			||||||
 | 
					                return false;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if let Some(v) = state.variables.iter_mut().filter(|v| v.name == name).next() {
 | 
				
			||||||
 | 
					                v.parsed_expr = expr;
 | 
				
			||||||
 | 
					                v.expr = input.clone();
 | 
				
			||||||
 | 
					                v.used = used_vars;
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                state.variables.push(Variable {
 | 
				
			||||||
 | 
					                    name,
 | 
				
			||||||
 | 
					                    expr: input.clone(),
 | 
				
			||||||
 | 
					                    value: None,
 | 
				
			||||||
 | 
					                    parsed_expr: expr,
 | 
				
			||||||
 | 
					                    used: used_vars
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        Err(e) => {
 | 
				
			||||||
 | 
					            state.error_msg = Some(e.to_string());
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    eval_vars(state);
 | 
				
			||||||
 | 
					    state.inputs.push(input);
 | 
				
			||||||
 | 
					    true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn find_dependency_recursive(state: &State, name: &String, mut seen: Vec<String>) -> Result<(), Vec<String>> {
 | 
				
			||||||
 | 
					    if let Some(var) = state.variables.iter().filter(|v| v.name == *name).next() {
 | 
				
			||||||
 | 
					        seen.push(name.clone());
 | 
				
			||||||
 | 
					        if var.used.iter().any(|v| seen.contains(v)) {
 | 
				
			||||||
 | 
					            return Err(vec![name.clone()]);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if let Some(mut e) = var.used.iter().filter_map(|v| find_dependency_recursive(state, v, seen.clone()).err()).next() {
 | 
				
			||||||
 | 
					            e.push(name.clone());
 | 
				
			||||||
 | 
					            return Err(e);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    Ok(())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn render(main_pos_size: ([f32; 2], [f32; 2]), state: &mut State, ui: &mut Ui) {
 | 
				
			||||||
 | 
					    if let Some(t) = ui.begin_main_menu_bar() {
 | 
				
			||||||
 | 
					        if ui.menu_item("Clear") {
 | 
				
			||||||
 | 
					            *state = State::default();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if ui.menu_item("Open") {
 | 
				
			||||||
 | 
					            if let Some(f) = rfd::FileDialog::new().add_filter("json", &["json"]).pick_file() {
 | 
				
			||||||
 | 
					                let data =  std::fs::read_to_string(f).unwrap_or("}{".to_string());
 | 
				
			||||||
 | 
					                let vars: Result<Vec<Variable>, _> = serde_json::from_str(&data);
 | 
				
			||||||
 | 
					                match vars {
 | 
				
			||||||
 | 
					                    Ok(v) => {
 | 
				
			||||||
 | 
					                        *state = State::default();
 | 
				
			||||||
 | 
					                        state.variables = v;
 | 
				
			||||||
 | 
					                        eval_vars(state);
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    Err(e) => println!("Error while loading:\n{:#?}", e)
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if ui.menu_item("Save") {
 | 
				
			||||||
 | 
					            if let Some(f) = rfd::FileDialog::new().add_filter("json", &["json"]).save_file() {
 | 
				
			||||||
 | 
					                if let Err(e) = std::fs::write(f, serde_json::to_string(&state.variables).unwrap()) {
 | 
				
			||||||
 | 
					                    println!("Error while saving:\n{:#?}", e);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        t.end();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let wflags = WindowFlags::NO_SCROLLBAR | WindowFlags::NO_MOVE | WindowFlags::NO_DECORATION | WindowFlags::NO_RESIZE | WindowFlags::NO_TITLE_BAR | WindowFlags::NO_COLLAPSE | WindowFlags::NO_DOCKING;
 | 
				
			||||||
 | 
					    if let Some(t) = ui.window("MainWindow")
 | 
				
			||||||
 | 
					        .position(main_pos_size.0, Condition::Always)
 | 
				
			||||||
 | 
					        .size(main_pos_size.1, Condition::Always)
 | 
				
			||||||
 | 
					        .flags(wflags)
 | 
				
			||||||
 | 
					        .begin() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ui.child_window("InputText")
 | 
				
			||||||
 | 
					            .size([0.0, 300.0])
 | 
				
			||||||
 | 
					            .always_vertical_scrollbar(true)
 | 
				
			||||||
 | 
					            .border(true)
 | 
				
			||||||
 | 
					            .build(|| state.inputs.iter().for_each(|i| ui.text(i)));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if let Some(err) = &state.error_msg {
 | 
				
			||||||
 | 
					            ui.text_colored([1.0, 0.0, 0.0, 1.0], "Error: ");
 | 
				
			||||||
 | 
					            ui.text(err);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ui.text("Input: ");
 | 
				
			||||||
 | 
					        ui.same_line();
 | 
				
			||||||
 | 
					        if ui.input_text("##Input", &mut state.input_buf)
 | 
				
			||||||
 | 
					            .enter_returns_true(true)
 | 
				
			||||||
 | 
					            .build() {
 | 
				
			||||||
 | 
					            if handle_input(state.input_buf.clone(), state) {
 | 
				
			||||||
 | 
					                state.input_buf.clear();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            ui.set_keyboard_focus_here_with_offset(FocusedWidget::Previous);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if let Some(last_eval) = &state.last_eval {
 | 
				
			||||||
 | 
					            ui.same_line();
 | 
				
			||||||
 | 
					            ui.text(format!("Eval took: {:?}", last_eval));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ui.spacing();
 | 
				
			||||||
 | 
					        ui.separator();
 | 
				
			||||||
 | 
					        ui.spacing();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if let Some(t) = ui.child_window("Variables").begin() {
 | 
				
			||||||
 | 
					            if let Some(t) = ui.begin_table_with_flags("VariableTable", 4, TableFlags::BORDERS | TableFlags::ROW_BG | TableFlags::RESIZABLE) {
 | 
				
			||||||
 | 
					                ui.table_setup_column("Name");
 | 
				
			||||||
 | 
					                ui.table_setup_column("Value");
 | 
				
			||||||
 | 
					                ui.table_setup_column("Expression");
 | 
				
			||||||
 | 
					                ui.table_setup_column("Actions");
 | 
				
			||||||
 | 
					                ui.table_headers_row();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                let mut to_remove = vec![];
 | 
				
			||||||
 | 
					                for v in &state.variables {
 | 
				
			||||||
 | 
					                    ui.table_next_row();
 | 
				
			||||||
 | 
					                    ui.table_next_column();
 | 
				
			||||||
 | 
					                    ui.text(&v.name);
 | 
				
			||||||
 | 
					                    ui.table_next_column();
 | 
				
			||||||
 | 
					                    ui.text(v.value.map_or("-".to_string(), |v| v.separate_with_spaces()));
 | 
				
			||||||
 | 
					                    ui.table_next_column();
 | 
				
			||||||
 | 
					                    ui.text(&v.expr);
 | 
				
			||||||
 | 
					                    ui.table_next_column();
 | 
				
			||||||
 | 
					                    if ui.button(format!("Remove##{}", v.name)) {
 | 
				
			||||||
 | 
					                        to_remove.push(v.name.clone());
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                if !to_remove.is_empty() {
 | 
				
			||||||
 | 
					                    state.variables.retain(|v| !to_remove.contains(&v.name));
 | 
				
			||||||
 | 
					                    eval_vars(state);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                t.end();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            t.end();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        t.end();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn main() {
 | 
				
			||||||
 | 
					    let mut state = State::default();
 | 
				
			||||||
 | 
					    let ev = glium::glutin::event_loop::EventLoop::new();
 | 
				
			||||||
 | 
					    let ctx = glium::glutin::ContextBuilder::new().with_vsync(false);
 | 
				
			||||||
 | 
					    let builder = glium::glutin::window::WindowBuilder::new()
 | 
				
			||||||
 | 
					        .with_title("MCalc");
 | 
				
			||||||
 | 
					    let display = glium::Display::new(builder, ctx, &ev).unwrap();
 | 
				
			||||||
 | 
					    let mut imgui = Context::create();
 | 
				
			||||||
 | 
					    imgui.set_ini_filename(None);
 | 
				
			||||||
 | 
					    let mut platform = imgui_winit_support::WinitPlatform::init(&mut imgui);
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        let glw = display.gl_window();
 | 
				
			||||||
 | 
					        let window = glw.window();
 | 
				
			||||||
 | 
					        platform.attach_window(imgui.io_mut(), window, imgui_winit_support::HiDpiMode::Locked(1.0));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    let mut renderer = imgui_glium_renderer::Renderer::init(&mut imgui, &display).unwrap();
 | 
				
			||||||
 | 
					    let mut last_frame = std::time::Instant::now();
 | 
				
			||||||
 | 
					    ev.run(move |e, _, flow| match e {
 | 
				
			||||||
 | 
					        glium::glutin::event::Event::NewEvents(_) => {
 | 
				
			||||||
 | 
					            let now = std::time::Instant::now();
 | 
				
			||||||
 | 
					            imgui.io_mut().update_delta_time(now - last_frame);
 | 
				
			||||||
 | 
					            last_frame = now;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        glium::glutin::event::Event::MainEventsCleared => {
 | 
				
			||||||
 | 
					            let glw = display.gl_window();
 | 
				
			||||||
 | 
					            platform.prepare_frame(imgui.io_mut(), glw.window()).unwrap();
 | 
				
			||||||
 | 
					            glw.window().request_redraw();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        glium::glutin::event::Event::RedrawRequested(_) => {
 | 
				
			||||||
 | 
					            let main_pos_size = {
 | 
				
			||||||
 | 
					                let v = imgui.main_viewport();
 | 
				
			||||||
 | 
					                (v.work_pos, v.work_size)
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					            let ui = imgui.new_frame();
 | 
				
			||||||
 | 
					            render(main_pos_size, &mut state, ui);
 | 
				
			||||||
 | 
					            let glw = display.gl_window();
 | 
				
			||||||
 | 
					            let mut target = display.draw();
 | 
				
			||||||
 | 
					            target.clear_color(0.0, 0.0, 0.0, 1.0);
 | 
				
			||||||
 | 
					            platform.prepare_render(ui, glw.window());
 | 
				
			||||||
 | 
					            renderer.render(&mut target, imgui.render()).unwrap();
 | 
				
			||||||
 | 
					            target.finish().unwrap();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        glium::glutin::event::Event::WindowEvent { event: glium::glutin::event::WindowEvent::CloseRequested, .. } => {
 | 
				
			||||||
 | 
					            *flow = glium::glutin::event_loop::ControlFlow::Exit
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        _ => {
 | 
				
			||||||
 | 
					            let glw = display.gl_window();
 | 
				
			||||||
 | 
					            platform.handle_event(imgui.io_mut(), glw.window(), &e);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										48
									
								
								src/parser.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								src/parser.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,48 @@
 | 
				
			|||||||
 | 
					use std::collections::HashMap;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
 | 
				
			||||||
 | 
					pub enum Node {
 | 
				
			||||||
 | 
					    Num(f64),
 | 
				
			||||||
 | 
					    Var(String),
 | 
				
			||||||
 | 
					    Neg(Box<Node>),
 | 
				
			||||||
 | 
					    Plus(Box<Node>, Box<Node>),
 | 
				
			||||||
 | 
					    Minus(Box<Node>, Box<Node>),
 | 
				
			||||||
 | 
					    Mul(Box<Node>, Box<Node>),
 | 
				
			||||||
 | 
					    Div(Box<Node>, Box<Node>)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn join_vec<T>(mut l: Vec<T>, mut r: Vec<T>) -> Vec<T> {
 | 
				
			||||||
 | 
					    l.append(&mut r);
 | 
				
			||||||
 | 
					    l
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Node {
 | 
				
			||||||
 | 
					    pub fn used_vars(&self) -> Vec<String> {
 | 
				
			||||||
 | 
					        match self {
 | 
				
			||||||
 | 
					            Node::Num(_) => vec![],
 | 
				
			||||||
 | 
					            Node::Var(v) => vec![v.clone()],
 | 
				
			||||||
 | 
					            Node::Neg(n) => n.used_vars(),
 | 
				
			||||||
 | 
					            Node::Plus(l, r) => join_vec(l.used_vars(), r.used_vars()),
 | 
				
			||||||
 | 
					            Node::Minus(l, r) => join_vec(l.used_vars(), r.used_vars()),
 | 
				
			||||||
 | 
					            Node::Mul(l, r) => join_vec(l.used_vars(), r.used_vars()),
 | 
				
			||||||
 | 
					            Node::Div(l, r) => join_vec(l.used_vars(), r.used_vars())
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn eval(&self, vars: &HashMap<String, Node>) -> Option<f64> {
 | 
				
			||||||
 | 
					        match self {
 | 
				
			||||||
 | 
					            Node::Num(v) => Some(v.clone()),
 | 
				
			||||||
 | 
					            Node::Var(v) => vars.get(v)?.eval(vars),
 | 
				
			||||||
 | 
					            Node::Neg(n) => n.eval(vars),
 | 
				
			||||||
 | 
					            Node::Plus(l, r) =>
 | 
				
			||||||
 | 
					                l.eval(vars).and_then(|l| r.eval(vars).and_then(|r| Some(l + r))),
 | 
				
			||||||
 | 
					            Node::Minus(l, r) =>
 | 
				
			||||||
 | 
					                l.eval(vars).and_then(|l| r.eval(vars).and_then(|r| Some(l - r))),
 | 
				
			||||||
 | 
					            Node::Mul(l, r) =>
 | 
				
			||||||
 | 
					                l.eval(vars).and_then(|l| r.eval(vars).and_then(|r| Some(l * r))),
 | 
				
			||||||
 | 
					            Node::Div(l, r) =>
 | 
				
			||||||
 | 
					                l.eval(vars).and_then(|l| r.eval(vars).and_then(|r| Some(l / r)))
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		Reference in New Issue
	
	Block a user