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