diff --git a/src/gui.rs b/src/gui.rs new file mode 100644 index 0000000..5fd2a9a --- /dev/null +++ b/src/gui.rs @@ -0,0 +1,63 @@ +use imgui::{TableFlags, Ui}; +use thousands::Separable; +use crate::state::{State, Variable}; + +pub fn draw_main_menu_bar(state: &mut State, ui: &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, _> = serde_json::from_str(&data); + match vars { + Ok(v) => { + *state = State::default(); + state.variables = v; + state.eval_vars(); + }, + 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(); + } +} + +pub fn draw_table(state: &mut State, ui: &Ui) { + 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)); + state.eval_vars(); + } + t.end(); + } +} diff --git a/src/main.rs b/src/main.rs index 69c31eb..f5c333f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,136 +1,16 @@ mod parser; +mod gui; +mod state; use glium::Surface; -use imgui::{Condition, Context, FocusedWidget, TableFlags, Ui, WindowFlags}; +use imgui::{Condition, Context, FocusedWidget, Ui, WindowFlags}; use lalrpop_util::lalrpop_mod; -use thousands::Separable; -use crate::parser::Node; +use crate::state::State; lalrpop_mod!(grammar); -#[derive(Debug, serde::Deserialize, serde::Serialize)] -struct Variable { - name: String, - expr: String, - - #[serde(skip)] - value: Option, - - parsed_expr: Node, - used: Vec -} - -#[derive(Debug, Default)] -struct State { - inputs: Vec, - input_buf: String, - error_msg: Option, - variables: Vec, - last_eval: Option -} - -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, &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) -> Result<(), Vec> { - 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, _> = 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(); - } + gui::draw_main_menu_bar(state, ui); 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") @@ -155,9 +35,7 @@ fn render(main_pos_size: ([f32; 2], [f32; 2]), state: &mut State, ui: &mut Ui) { 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(); - } + state.handle_input_buf(); ui.set_keyboard_focus_here_with_offset(FocusedWidget::Previous); } if let Some(last_eval) = &state.last_eval { @@ -170,33 +48,7 @@ fn render(main_pos_size: ([f32; 2], [f32; 2]), state: &mut State, ui: &mut Ui) { 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(); - } + gui::draw_table(state, ui); t.end(); } t.end(); diff --git a/src/state.rs b/src/state.rs new file mode 100644 index 0000000..49ca564 --- /dev/null +++ b/src/state.rs @@ -0,0 +1,105 @@ +use crate::parser::Node; + +#[derive(Debug, serde::Deserialize, serde::Serialize)] +pub struct Variable { + pub name: String, + pub expr: String, + + #[serde(skip)] + pub value: Option, + + pub parsed_expr: Node, + pub used: Vec +} + +#[derive(Debug, Default)] +pub struct State { + pub inputs: Vec, + pub input_buf: String, + pub error_msg: Option, + pub variables: Vec, + pub last_eval: Option +} + +impl State { + pub fn eval_vars(&mut self) { + let start = std::time::Instant::now(); + + let var_map = self.variables.iter() + .map(|v| (v.name.clone(), v.parsed_expr.clone())) + .collect(); + + for v in &mut self.variables { + let val = v.parsed_expr.eval(&var_map); + v.value = val; + } + + self.last_eval = Some(start.elapsed()); + } + + pub fn handle_input_buf(&mut self) { + let input = std::mem::take(&mut self.input_buf); + if !self.handle_input(&input) { + self.input_buf = input; + } + } + + pub fn handle_input(&mut self, input: &String) -> bool { + self.error_msg.take(); + + let node: Result<(String, Node), lalrpop_util::ParseError, &str>> + = crate::grammar::AssignParser::new().parse(&input); + match node { + Ok((name, expr)) => { + let used_vars = expr.used_vars(); + if used_vars.contains(&name) { + self.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| self.find_dependency_recursive(v, seen.clone()).err()).next() { + e.push(name.clone()); + e.reverse(); + e.push(name.clone()); + self.error_msg = Some(format!("Found recursive dependency:\n{}", e.join(" -> "))); + return false; + } + if let Some(v) = self.variables.iter_mut().filter(|v| v.name == name).next() { + v.parsed_expr = expr; + v.expr = input.clone(); + v.used = used_vars; + } else { + self.variables.push(Variable { + name, + expr: input.clone(), + value: None, + parsed_expr: expr, + used: used_vars + }); + } + } + Err(e) => { + self.error_msg = Some(e.to_string()); + return false; + } + } + + self.eval_vars(); + self.inputs.push(input.clone()); + true + } + + fn find_dependency_recursive(&self, name: &String, mut seen: Vec) -> Result<(), Vec> { + if let Some(var) = self.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| self.find_dependency_recursive(v, seen.clone()).err()).next() { + e.push(name.clone()); + return Err(e); + } + } + Ok(()) + } +}