Split up code into multiple files

This commit is contained in:
Mutzi 2023-05-01 14:21:18 +02:00
parent 1798c29c6b
commit ceb3ad869d
3 changed files with 175 additions and 155 deletions

63
src/gui.rs Normal file
View File

@ -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<Vec<Variable>, _> = 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();
}
}

View File

@ -1,136 +1,16 @@
mod parser; mod parser;
mod gui;
mod state;
use glium::Surface; use glium::Surface;
use imgui::{Condition, Context, FocusedWidget, TableFlags, Ui, WindowFlags}; use imgui::{Condition, Context, FocusedWidget, Ui, WindowFlags};
use lalrpop_util::lalrpop_mod; use lalrpop_util::lalrpop_mod;
use thousands::Separable; use crate::state::State;
use crate::parser::Node;
lalrpop_mod!(grammar); 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) { fn render(main_pos_size: ([f32; 2], [f32; 2]), state: &mut State, ui: &mut Ui) {
if let Some(t) = ui.begin_main_menu_bar() { gui::draw_main_menu_bar(state, ui);
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; 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") 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) if ui.input_text("##Input", &mut state.input_buf)
.enter_returns_true(true) .enter_returns_true(true)
.build() { .build() {
if handle_input(state.input_buf.clone(), state) { state.handle_input_buf();
state.input_buf.clear();
}
ui.set_keyboard_focus_here_with_offset(FocusedWidget::Previous); ui.set_keyboard_focus_here_with_offset(FocusedWidget::Previous);
} }
if let Some(last_eval) = &state.last_eval { 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(); ui.spacing();
if let Some(t) = ui.child_window("Variables").begin() { 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) { gui::draw_table(state, ui);
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();
} }
t.end(); t.end();

105
src/state.rs Normal file
View File

@ -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<f64>,
pub parsed_expr: Node,
pub used: Vec<String>
}
#[derive(Debug, Default)]
pub struct State {
pub inputs: Vec<String>,
pub input_buf: String,
pub error_msg: Option<String>,
pub variables: Vec<Variable>,
pub last_eval: Option<std::time::Duration>
}
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<usize, lalrpop_util::lexer::Token<'_>, &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<String>) -> Result<(), Vec<String>> {
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(())
}
}