diff --git a/.rustfmt.toml b/.rustfmt.toml new file mode 100644 index 0000000..6d501dc --- /dev/null +++ b/.rustfmt.toml @@ -0,0 +1,3 @@ +max_width = 90 +comment_width = 90 +wrap_comments = true diff --git a/Cargo.lock b/Cargo.lock index d768acb..6afd181 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -776,6 +776,27 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.59.0", +] + [[package]] name = "dispatch" version = "0.2.0" @@ -980,6 +1001,7 @@ version = "0.1.0" dependencies = [ "assembler", "common", + "dirs", "eframe", "egui", "rfd", @@ -2163,6 +2185,12 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "orbclient" version = "0.3.48" @@ -2483,6 +2511,17 @@ dependencies = [ "bitflags 2.9.1", ] +[[package]] +name = "redox_users" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" +dependencies = [ + "getrandom 0.2.16", + "libredox", + "thiserror 2.0.12", +] + [[package]] name = "renderdoc-sys" version = "1.1.0" diff --git a/Cargo.toml b/Cargo.toml index a7fb44b..aaacca0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,8 @@ [workspace] members = ["emulator", "common", "assembler"] +resolver = "3" [workspace.package] version = "0.2.0" edition = "2024" -authors = ["zxq5", "nullndvoid"] \ No newline at end of file +authors = ["zxq5", "nullndvoid"] diff --git a/assembler/src/lib.rs b/assembler/src/lib.rs index 7470dd0..d233a24 100644 --- a/assembler/src/lib.rs +++ b/assembler/src/lib.rs @@ -11,12 +11,12 @@ pub fn assemble(src: &str) -> Vec { todo!() } -pub fn disassemble(binary: Vec) -> String { - // TODO: disassembling functionality - - // - we probably don't need to implement this for a while yet. - // - this method should recover symbols such as labels and variables from the human written assembly, recognising - // sequences that are expansions of pseduo-instructions and reversing this to produce near enough the original source code. +/// TODO: disassembling functionality +/// - We probably don't need to implement this for a while yet. +/// - This method should recover symbols such as labels and variables from the human +/// written assembly, recognising sequences that are expansions of pseudo-instructions +/// and reversing this to produce near enough the original source code. +pub fn disassemble(_: Vec) -> String { todo!() } diff --git a/emulator/Cargo.toml b/emulator/Cargo.toml index f353d11..4921fbb 100644 --- a/emulator/Cargo.toml +++ b/emulator/Cargo.toml @@ -12,4 +12,5 @@ common = { path = "../common" } assembler = { path = "../assembler" } eframe = "0.31.1" egui = "0.31.1" -rfd = "0.15.3" \ No newline at end of file +rfd = "0.15.3" +dirs = "6.0.0" diff --git a/emulator/src/emulator/system/emulator.rs b/emulator/src/emulator/system/emulator.rs index d5925ac..35baed4 100644 --- a/emulator/src/emulator/system/emulator.rs +++ b/emulator/src/emulator/system/emulator.rs @@ -1,8 +1,5 @@ use std::{ - sync::{ - Arc, Mutex, MutexGuard, - mpsc::{self, Receiver, Sender}, - }, + sync::mpsc::{self, Receiver, Sender}, thread, time::Duration, }; @@ -48,7 +45,7 @@ pub fn run_emulator( }; if let Some(cmd) = cmd { - println!("Received command: {:?}", cmd); + println!("Received command: {cmd:?}"); match cmd { Command::Start => { diff --git a/emulator/src/emulator/system/processor/tests.rs b/emulator/src/emulator/system/processor/tests.rs index 99a09ca..3a6eb74 100644 --- a/emulator/src/emulator/system/processor/tests.rs +++ b/emulator/src/emulator/system/processor/tests.rs @@ -1,10 +1,7 @@ use super::*; use crate::emulator::system::memory::*; -use common::instructions::{ - args::{ITypeArgs, RTypeArgs}, - *, -}; +use common::prelude::*; fn create_test_processor() -> Processor { let memory = Box::new(MainStore::new()); diff --git a/emulator/src/emulator/ui/editor.rs b/emulator/src/emulator/ui/editor.rs index 588bb09..fe766b1 100644 --- a/emulator/src/emulator/ui/editor.rs +++ b/emulator/src/emulator/ui/editor.rs @@ -60,7 +60,8 @@ impl Component for Editor { } impl Editor { - pub fn new(sender: Sender) -> Self { + #[must_use] + pub const fn new(sender: Sender) -> Self { Self { filename: String::new(), text: String::new(), @@ -75,7 +76,7 @@ impl Editor { } } - fn render_output(&mut self, _state: &mut State, ui: &mut Ui, _ctx: &Context) { + fn render_output(&self, _state: &mut State, ui: &mut Ui, _ctx: &Context) { // Output area with synchronized scrolling egui::ScrollArea::vertical() .id_salt("output_scroll") @@ -117,7 +118,7 @@ impl Editor { style.visuals.widgets.inactive.bg_fill = egui::Color32::from_gray(30); ui.label( - egui::RichText::new(format!("0x{:04X}", address)) + egui::RichText::new(format!("0x{address:04X}")) .font(egui::FontId::monospace(12.0)), ); }, @@ -126,26 +127,26 @@ impl Editor { // Individual bytes column let byte_str = chunk .iter() - .map(|b| format!("{:02X}", b)) + .map(|b| format!("{b:02X}")) .collect::>() .join(" "); ui.label( - egui::RichText::new(format!("{:<11}", byte_str)) + egui::RichText::new(format!("{byte_str:<11}")) .font(egui::FontId::monospace(12.0)) .color(egui::Color32::from_rgb(200, 200, 255)), ); // Hex column ui.label( - egui::RichText::new(format!("0x{:08X}", value)) + egui::RichText::new(format!("0x{value:08X}")) .font(egui::FontId::monospace(12.0)) .color(egui::Color32::from_rgb(255, 200, 200)), ); // Decimal column ui.label( - egui::RichText::new(format!("{:10}", value)) + egui::RichText::new(format!("{value:10}")) .font(egui::FontId::monospace(12.0)) .color(egui::Color32::from_rgb(200, 255, 200)), ); @@ -173,7 +174,8 @@ impl Editor { egui::Color32::from_gray(30); // Calculate line height to match text editor - let line_height = ui.text_style_height(&egui::TextStyle::Monospace); + let line_height = + ui.text_style_height(&egui::TextStyle::Monospace); for line_num in 1..=line_count { let line_response = ui.allocate_response( @@ -184,7 +186,7 @@ impl Editor { ui.painter().text( line_response.rect.left_center() + egui::vec2(5.0, 0.0), egui::Align2::LEFT_CENTER, - format!("{:3}", line_num), + format!("{line_num:3}"), egui::FontId::monospace(12.0), ui.style().visuals.text_color(), ); @@ -229,18 +231,24 @@ impl Editor { // error display ui.label( - egui::RichText::new(self.error.clone().unwrap_or("".to_string())) + egui::RichText::new(self.error.clone().unwrap_or_default()) .color(egui::Color32::RED), ); // number of lines in the file ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { let line_count = self.text.lines().count(); - ui.label(format!("Lines: {}", line_count)); + ui.label(format!("Lines: {line_count}")); }); }); ui.horizontal(|ui| { + let work_dir = std::env::current_dir().unwrap_or_else(|_| { + dirs::home_dir().expect( + "Couldn't get your current working directory or your home directory.", + ) + }); + ui.spacing_mut().button_padding = egui::vec2(8.0, 4.0); ui.spacing_mut().item_spacing.x = 6.0; @@ -249,7 +257,7 @@ impl Editor { if let Some(path) = FileDialog::new() .add_filter("dsafiles", &["dsa", "dsb", "dsc", "dsd"]) .add_filter("all", &["*"]) - .set_directory(std::env::current_dir().unwrap()) + .set_directory(&work_dir) .pick_file() { if let Ok(content) = std::fs::read_to_string(&path) { @@ -266,11 +274,11 @@ impl Editor { if let Some(path) = FileDialog::new() .add_filter("dsafiles", &["dsa", "dsb", "dsc", "dsd"]) .add_filter("all", &["*"]) - .set_directory(std::env::current_dir().unwrap()) + .set_directory(&work_dir) .save_file() { - if let Err(e) = std::fs::write(&path, &self.text) { - self.error = Some(format!("Failed to save file: {}", e)); + if let Err(why) = std::fs::write(&path, &self.text) { + self.error = Some(format!("Failed to save file: {why}")); } else { self.filename = path.display().to_string(); } @@ -279,6 +287,8 @@ impl Editor { // builds the current file if ui.button("Build").clicked() { + let _instructions = assembler::assemble(&self.text); + // TODO: uncomment this once assembler works!!! // let instructions = assembler::assemble(&self.text); // self.output = instructions @@ -292,12 +302,15 @@ impl Editor { // Loads the generated binary into the assembler at the provided offset if ui.button("Load").clicked() { if self.error.is_some() { - self.error = Some("Can't load program at invalid offset!".to_string()); + self.error = + Some("Can't load program at invalid offset!".to_string()); } self.sender .send(Command::Write(self.load_offset, self.output.clone())) - .unwrap_or_else(|_| self.error = Some("Failed to send command".to_string())); + .unwrap_or_else(|_| { + self.error = Some("Failed to send command".to_string()) + }); } // Entry widget to enter a load offset @@ -312,22 +325,27 @@ impl Editor { // Resets the emulator and all attached devices if ui.button("Reset Emulator").clicked() { - self.sender - .send(Command::Reset) - .unwrap_or_else(|_| self.error = Some("Failed to send command".to_string())); + self.sender.send(Command::Reset).unwrap_or_else(|_| { + self.error = Some("Failed to send command".to_string()) + }); } }); } } fn parse_address(address: &str) -> Option { - if address.starts_with("0x") { - u32::from_str_radix(&address[2..], 16).ok() - } else if address.starts_with("0b") { - u32::from_str_radix(&address[2..], 2).ok() - } else if address.starts_with("0o") { - u32::from_str_radix(&address[2..], 8).ok() - } else { - address.parse::().ok() - } + address.strip_prefix("0x").map_or_else( + || { + address.strip_prefix("0b").map_or_else( + || { + address.strip_prefix("0o").map_or_else( + || address.parse::().ok(), + |oct| u32::from_str_radix(oct, 8).ok(), + ) + }, + |bin| u32::from_str_radix(bin, 2).ok(), + ) + }, + |hex| u32::from_str_radix(hex, 16).ok(), + ) } diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..5f3e652 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,53 @@ +use std::{ + sync::{Arc, Mutex}, + thread, +}; + +use dsa_rs::emulator::{ + system::{emulator::run_emulator, memory::MainStore, processor::Processor}, + ui::{ + control_unit::ControlPanel, interface::EmulatorUI, memory_inspector::MemoryInspector, + stack_inspector::StackInspector, + }, +}; + +fn main() -> Result<(), eframe::Error> { + // Initialize Channels + let (cmd_sender, cmd_receiver) = std::sync::mpsc::channel(); + let (state_sender, state_receiver) = std::sync::mpsc::channel(); + + let mainstore = MainStore::new(); + let processor = Processor::new(Box::new(mainstore), vec![]); + + thread::spawn(move || { + run_emulator(&cmd_receiver, &state_sender, processor); + }); + + // Create UI + let mut ui = EmulatorUI::new(cmd_sender.clone(), state_receiver); + + // Create UI modules + let control_unit = ControlPanel::new(cmd_sender.clone()); + ui.add_component(Box::new(control_unit)); + + let mem_inspector = MemoryInspector::new(cmd_sender.clone()); + ui.add_component(Box::new(mem_inspector)); + + let stack_inspector = StackInspector::new(); + ui.add_component(Box::new(stack_inspector)); + + // Run UI + let options = eframe::NativeOptions { + viewport: egui::ViewportBuilder::default().with_inner_size([800.0, 600.0]), + ..Default::default() + }; + + eframe::run_native( + "DSA Simulator (Damn Simple Architecture 🔥)", + options, + Box::new(move |cc| { + cc.egui_ctx.set_visuals(egui::Visuals::default()); + Ok(Box::new(ui)) + }), + ) +}