From 75246f5e73992c24dcbd1983af58d889ccfb1602 Mon Sep 17 00:00:00 2001 From: zxq5 Date: Mon, 16 Jun 2025 23:38:36 +0100 Subject: [PATCH] editor go brr? --- Cargo.lock | 17 +++ assembler/src/main.rs | 81 ++++++++++- common/src/instructions.rs | 10 +- dsa_editor | 1 + emulator/Cargo.toml | 2 + emulator/src/emulator/ui/editor.rs | 213 ++++++++++++++--------------- 6 files changed, 207 insertions(+), 117 deletions(-) create mode 160000 dsa_editor diff --git a/Cargo.lock b/Cargo.lock index 6afd181..e0273c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -653,6 +653,12 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "colorful" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb474a9c3219a8254ead020421ecf1b90427f29b55f6aae9a2471fa62c126ef" + [[package]] name = "combine" version = "4.6.7" @@ -866,6 +872,16 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" +[[package]] +name = "dsa_editor" +version = "0.1.0" +dependencies = [ + "colorful", + "eframe", + "egui", + "serde", +] + [[package]] name = "ecolor" version = "0.31.1" @@ -1002,6 +1018,7 @@ dependencies = [ "assembler", "common", "dirs", + "dsa_editor", "eframe", "egui", "rfd", diff --git a/assembler/src/main.rs b/assembler/src/main.rs index 8277af7..000d328 100644 --- a/assembler/src/main.rs +++ b/assembler/src/main.rs @@ -1,16 +1,85 @@ use std::fs; use assembler::{lexer, parser::Parser}; +use common::prelude::{ITypeArgs, Instruction, RTypeArgs, Register}; fn main() { - let program = fs::read_to_string("../resources/dsa/print.dsa").unwrap(); - let tokens = lexer::lexer(program).unwrap(); + // let program = fs::read_to_string("../resources/dsa/print.dsa").unwrap(); + // let tokens = lexer::lexer(program).unwrap(); - println!("{:?}", tokens); + // println!("{:?}", tokens); - let parser = Parser::new(tokens); + // let parser = Parser::new(tokens); - for node in parser { - println!("{:?}", node); + // for node in parser { + // println!("{:?}", node); + // } + + // generate some instructions + let ins = vec![ + // Initialize first two Fibonacci numbers + // F(0) = 0, F(1) = 1 + + // Load 0 into Rg0 (F(0)) + Instruction::LoadLowerImmediate(ITypeArgs::new(0, None, Some(Register::Rg0))), + // Load 1 into Rg1 (F(1)) + Instruction::LoadLowerImmediate(ITypeArgs::new(1, None, Some(Register::Rg1))), + // Load loop counter (how many more numbers to calculate) + // Let's calculate 8 Fibonacci numbers total (0,1,1,2,3,5,8,13) + Instruction::LoadLowerImmediate(ITypeArgs::new(6, None, Some(Register::Rg2))), // 6 more iterations + // Load 0 for comparison + Instruction::LoadLowerImmediate(ITypeArgs::new(0, None, Some(Register::Zero))), + // Fibonacci calculation loop starts here (address 4) + // Calculate next Fibonacci number: F(n) = F(n-1) + F(n-2) + Instruction::Add(RTypeArgs::new( + Some(Register::Rg0), // F(n-2) + Some(Register::Rg1), // F(n-1) + Some(Register::Rg3), // F(n) result + None, + )), + // Shift the sequence: Rg0 = Rg1, Rg1 = Rg3 + Instruction::Mov(RTypeArgs::new( + Some(Register::Rg1), + Some(Register::NoReg), + Some(Register::Rg0), + None, + )), + Instruction::Mov(RTypeArgs::new( + Some(Register::Rg3), + Some(Register::NoReg), + Some(Register::Rg1), + None, + )), + // Decrement counter + Instruction::Decrement(RTypeArgs::new( + Some(Register::Rg2), + Some(Register::NoReg), + Some(Register::Rg2), + None, + )), + // Compare counter with 0 + Instruction::Compare(RTypeArgs::new( + Some(Register::Rg2), + Some(Register::Zero), + Some(Register::NoReg), + None, + )), + // Jump back to loop if counter > 0 (address 4) + // JGT jumps if greater than flag is set + Instruction::JumpGt(ITypeArgs::new(4, None, None)), + // Program complete - the final Fibonacci number is in Rg1 + // Let's move it to the accumulator for easy access + Instruction::Mov(RTypeArgs::new( + Some(Register::Rg1), + Some(Register::NoReg), + Some(Register::Acc), + None, + )), + // Halt the processor + Instruction::Halt, + ]; + + for i in ins.iter() { + println!("0x{:08x}", i.encode()); } } diff --git a/common/src/instructions.rs b/common/src/instructions.rs index adca96e..801470a 100644 --- a/common/src/instructions.rs +++ b/common/src/instructions.rs @@ -344,7 +344,9 @@ impl std::fmt::Display for Instruction { write!(f, "{}", self.mnemonic())?; match self { - Self::Mov(args) | Self::MovSigned(args) => write!(f, " {}, {}", args.sr1, args.dr), + Self::Mov(args) | Self::MovSigned(args) => { + write!(f, " {}, {}", args.sr1, args.dr) + } Self::LoadByte(args) | Self::LoadByteSigned(args) | Self::LoadHalfword(args) @@ -365,9 +367,11 @@ impl std::fmt::Display for Instruction { write!(f, " ({:x}){}", args.immediate, args.r1) } Self::LoadLowerImmediate(args) | Self::LoadUpperImmediate(args) => { - write!(f, " {}, {}", args.r1, args.r2) + write!(f, " {}, {}, {}", args.immediate, args.r1, args.r2) + } + Self::Compare(args) | Self::Not(args) => { + write!(f, " {}, {}", args.sr1, args.sr2) } - Self::Compare(args) | Self::Not(args) => write!(f, " {}, {}", args.sr1, args.sr2), Self::Add(args) | Self::Sub(args) diff --git a/dsa_editor b/dsa_editor new file mode 160000 index 0000000..1566784 --- /dev/null +++ b/dsa_editor @@ -0,0 +1 @@ +Subproject commit 1566784e20e6ef5e6ebf4004f4972e164d240ebf diff --git a/emulator/Cargo.toml b/emulator/Cargo.toml index 4921fbb..031a929 100644 --- a/emulator/Cargo.toml +++ b/emulator/Cargo.toml @@ -10,7 +10,9 @@ path = "src/lib.rs" [dependencies] common = { path = "../common" } assembler = { path = "../assembler" } +dsa_editor = { path = "../dsa_editor" } eframe = "0.31.1" egui = "0.31.1" rfd = "0.15.3" dirs = "6.0.0" + diff --git a/emulator/src/emulator/ui/editor.rs b/emulator/src/emulator/ui/editor.rs index fe766b1..a9efea1 100644 --- a/emulator/src/emulator/ui/editor.rs +++ b/emulator/src/emulator/ui/editor.rs @@ -1,15 +1,19 @@ -use std::sync::mpsc::Sender; +use std::{ffi::OsStr, path::PathBuf, sync::mpsc::Sender}; -use egui::{Align, Context, Layout, Ui}; +use assembler::lexer::Symbol; +use common::prelude::Instruction; +use egui::{Align, Context, Key, Layout, Ui}; use rfd::FileDialog; +use dsa_editor::{CodeEditor, ColorTheme, Syntax}; + use crate::emulator::{ system::model::{Command, State}, ui::interface::Component, }; pub struct Editor { - filename: String, + path: PathBuf, text: String, output: Vec, sender: Sender, @@ -37,6 +41,11 @@ impl Component for Editor { fn render(&mut self, state: &mut State, ui: &mut Ui, ctx: &Context) { ui.vertical(|ui| { // Top bar + + if ui.input(|i| i.key_pressed(Key::S) && i.modifiers.ctrl) { + self.save(); + }; + self.render_toolbar(state, ui, ctx); ui.add_space(4.0); // Add some spacing instead of just a separator @@ -61,9 +70,9 @@ impl Component for Editor { impl Editor { #[must_use] - pub const fn new(sender: Sender) -> Self { + pub fn new(sender: Sender) -> Self { Self { - filename: String::new(), + path: PathBuf::new(), text: String::new(), output: Vec::new(), sender, @@ -76,11 +85,68 @@ impl Editor { } } + fn filename(&self) -> &str { + self.path + .file_name() + .unwrap_or(OsStr::new("Unnamed!")) + .to_str() + .unwrap() + } + + fn extension(&self) -> &str { + self.path + .extension() + .unwrap_or(OsStr::new("Unknown!")) + .to_str() + .unwrap() + } + + fn save(&mut self) { + 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.", + ) + }); + + if let Some(path) = FileDialog::new() + .add_filter("damn simple files", &["dsa", "dsb", "dsc", "dsd"]) + .add_filter("all", &["*"]) + .set_directory(&work_dir) + .save_file() + { + if let Err(why) = std::fs::write(&path, &self.text) { + self.error = Some(format!("Failed to save file: {why}")); + } else { + self.path = path; + } + } + } + + fn open(&mut self) { + 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.", + ) + }); + + if let Some(path) = FileDialog::new() + .add_filter("damn simple files", &["dsa", "dsb", "dsc", "dsd"]) + .add_filter("all", &["*"]) + .set_directory(&work_dir) + .pick_file() + { + if let Ok(contents) = std::fs::read_to_string(&path) { + self.path = path; + self.text = contents; + } + } + } + fn render_output(&self, _state: &mut State, ui: &mut Ui, _ctx: &Context) { // Output area with synchronized scrolling egui::ScrollArea::vertical() .id_salt("output_scroll") - .max_width(300.0) + .max_width(350.0) .show(ui, |ui| { if self.output.is_empty() { ui.label( @@ -92,8 +158,8 @@ impl Editor { } egui::Grid::new("output_grid") + .spacing([5.0, 2.0]) // Horizontal and vertical spacing .num_columns(4) - .spacing([20.0, 2.0]) // Horizontal and vertical spacing .striped(false) .show(ui, |ui| { // Process bytes in chunks of 4 @@ -107,7 +173,7 @@ impl Editor { bytes[i] = byte; } } - let value = u32::from_le_bytes(bytes); + let value = u32::from_be_bytes(bytes); // Address column ui.with_layout( @@ -144,9 +210,14 @@ impl Editor { .color(egui::Color32::from_rgb(255, 200, 200)), ); - // Decimal column + // Instruction column + let instruction = match Instruction::decode(value) { + Ok(instruction) => instruction.to_string(), + Err(_) => format!("{value:10}"), + }; + ui.label( - egui::RichText::new(format!("{value:10}")) + egui::RichText::new(instruction) .font(egui::FontId::monospace(12.0)) .color(egui::Color32::from_rgb(200, 255, 200)), ); @@ -159,75 +230,26 @@ impl Editor { fn render_editor(&mut self, _state: &mut State, ui: &mut Ui, _ctx: &Context) { let available_width = ui.available_width(); + let syntax = match self.extension() { + "dsa" => Syntax::dsa(), + _ => Syntax::dsa(), + }; - // Main editor area with synchronized scrolling - egui::ScrollArea::vertical() - .max_width(available_width - 400.0) - .id_salt("editor_scroll") - .show(ui, |ui| { - ui.horizontal(|ui| { - // Line numbers column - let line_count = self.text.lines().count(); - ui.vertical(|ui| { - ui.set_width(50.0); - ui.style_mut().visuals.widgets.inactive.bg_fill = - egui::Color32::from_gray(30); - - // Calculate line height to match text editor - let line_height = - ui.text_style_height(&egui::TextStyle::Monospace); - - for line_num in 1..=line_count { - let line_response = ui.allocate_response( - egui::vec2(50.0, line_height), - egui::Sense::hover(), - ); - - ui.painter().text( - line_response.rect.left_center() + egui::vec2(5.0, 0.0), - egui::Align2::LEFT_CENTER, - format!("{line_num:3}"), - egui::FontId::monospace(12.0), - ui.style().visuals.text_color(), - ); - } - }); - - ui.separator(); - - // Text editor area - ui.vertical(|ui| { - let available_size = ui.available_size(); - let response = ui.add_sized( - available_size, - egui::TextEdit::multiline(&mut self.text) - .font(egui::TextStyle::Monospace) - .margin(egui::vec2(5.0, 0.0)) - .code_editor(), - ); - - // Update cursor position when text changes - if response.changed() { - // Simple but functional cursor tracking - let lines = self.text.lines().collect::>(); - self.cursor_line = lines.len().max(1); - - // Get the length of the last line for column position - if let Some(last_line) = lines.last() { - self.cursor_col = last_line.chars().count() + 1; - } else { - self.cursor_col = 1; - } - } - }); - }); - }); + CodeEditor::default() + .id_source("editor") + .with_fontsize(12.0) + .with_rows(0) + .with_theme(ColorTheme::default()) + .with_syntax(syntax) + .with_numlines(true) + .desired_width(available_width - 450.0) + .show(ui, &mut self.text); } fn render_toolbar(&mut self, _state: &mut State, ui: &mut Ui, _ctx: &Context) { ui.horizontal(|ui| { - // current filename - ui.label(format!("File: {}", self.filename)); + ui.label(format!("File type: {}", self.extension())); + ui.label(format!("Filename: {}", self.filename())); // error display ui.label( @@ -243,52 +265,21 @@ impl Editor { }); 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; // Opens a file if ui.button("Open").clicked() { - if let Some(path) = FileDialog::new() - .add_filter("dsafiles", &["dsa", "dsb", "dsc", "dsd"]) - .add_filter("all", &["*"]) - .set_directory(&work_dir) - .pick_file() - { - if let Ok(content) = std::fs::read_to_string(&path) { - self.text = content; - self.filename = path.display().to_string(); - } - } - - self.output = Vec::new(); + self.open(); } // Saves the current file if ui.button("Save").clicked() { - if let Some(path) = FileDialog::new() - .add_filter("dsafiles", &["dsa", "dsb", "dsc", "dsd"]) - .add_filter("all", &["*"]) - .set_directory(&work_dir) - .save_file() - { - 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(); - } - } + self.save(); } // 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 @@ -296,7 +287,13 @@ impl Editor { // .flat_map(|i| i.encode().to_le_bytes().to_vec()) // .collect(); - self.output = vec![0x00; 256]; + self.output = vec![ + 0x2e, 0xe0, 0x00, 0x00, 0x2e, 0xe1, 0x00, 0x01, 0x2e, 0xe2, 0x00, + 0x06, 0x2e, 0xf6, 0x00, 0x00, 0x64, 0x01, 0x18, 0x00, 0x04, 0x37, + 0x00, 0x00, 0x04, 0x77, 0x08, 0x00, 0x58, 0x57, 0x10, 0x00, 0x50, + 0x56, 0xb8, 0x00, 0x42, 0xf7, 0x00, 0x04, 0x04, 0x37, 0x80, 0x00, + 0x92, 0xf7, 0xb8, 0x00, + ]; } // Loads the generated binary into the assembler at the provided offset