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 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
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