Split up code into multiple files
This commit is contained in:
parent
1798c29c6b
commit
ceb3ad869d
63
src/gui.rs
Normal file
63
src/gui.rs
Normal 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();
|
||||
}
|
||||
}
|
162
src/main.rs
162
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<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();
|
||||
}
|
||||
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();
|
||||
|
105
src/state.rs
Normal file
105
src/state.rs
Normal 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(())
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user