diff --git a/Cargo.lock b/Cargo.lock index e77f9c8..700d79f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -659,6 +659,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" @@ -903,6 +909,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" diff --git a/assembler/src/lexer.rs b/assembler/src/lexer.rs index 0202a2c..5879317 100644 --- a/assembler/src/lexer.rs +++ b/assembler/src/lexer.rs @@ -1,20 +1,12 @@ use std::str::FromStr; -use crate::{AssembleError, parser::Opcode}; +use crate::{ + AssembleError, + model::{Module, Opcode, Symbol, Token}, +}; use common::prelude::Register; -pub type Symbol = String; - -#[derive(Debug, Clone)] -pub enum Token { - Symbol(Symbol), - Register(Register), - Immediate(u32), - StringLit(String), - Opcode(Opcode), -} - -pub fn lexer(mut program: String) -> Result, AssembleError> { +pub fn lexer(mut program: String, module: u64) -> Result, AssembleError> { let mut tokens = Vec::new(); program = program.replace(",", ""); @@ -30,7 +22,6 @@ pub fn lexer(mut program: String) -> Result, AssembleError> { if let Some(stripped) = token.strip_prefix('"') { literal.push_str(stripped); - println!("literal: {literal}"); } if !literal.is_empty() { @@ -61,9 +52,9 @@ pub fn lexer(mut program: String) -> Result, AssembleError> { tokens.push(token); } else if let Some(token) = parse_decimal(token)? { tokens.push(token); - } else if let Some(token) = parse_label(token)? { + } else if let Some(token) = parse_label(token, module)? { tokens.push(token); - } else if let Some(token) = parse_symbol(token)? { + } else if let Some(token) = parse_symbol(token, module)? { tokens.push(token); } else { return Err(AssembleError::Generic); @@ -126,18 +117,34 @@ pub fn parse_decimal(token: &str) -> Result, AssembleError> { } } -pub fn parse_label(token: &str) -> Result, AssembleError> { +pub fn parse_label(token: &str, module: u64) -> Result, AssembleError> { if !token.ends_with(":") { Ok(None) } else { - Ok(Some(Token::Symbol(token[0..token.len() - 1].to_string()))) + Ok(Some(Token::Symbol(Symbol { + name: token[0..token.len() - 1].to_string(), + module: Module::Resolved(module), + }))) } } -pub fn parse_symbol(token: &str) -> Result, AssembleError> { +pub fn parse_symbol(token: &str, module: u64) -> Result, AssembleError> { if token.chars().next().unwrap().is_numeric() { return Ok(None); } - Ok(Some(Token::Symbol(token.to_string()))) + let mut split = token.splitn(2, "::"); + let symbol1 = split.next().unwrap().to_string(); + + if let Some(symbol2) = split.next() { + Ok(Some(Token::Symbol(Symbol { + name: symbol2.to_string(), + module: Module::Unresolved(symbol1), + }))) + } else { + Ok(Some(Token::Symbol(Symbol { + name: symbol1, + module: Module::Resolved(module), + }))) + } } diff --git a/assembler/src/lib.rs b/assembler/src/lib.rs index 477f968..d7fdb55 100644 --- a/assembler/src/lib.rs +++ b/assembler/src/lib.rs @@ -1,14 +1,91 @@ use core::fmt; +use std::{ + collections::HashSet, + fs, + hash::{DefaultHasher, Hash, Hasher}, + path::PathBuf, +}; use common::prelude::Instruction; -use crate::{lexer::Token, parser::TokenType}; +use crate::{ + model::{Node, Token, TokenType}, + parser::{Parser, Program}, +}; pub mod lexer; +pub mod model; pub mod parser; -pub fn assemble(_src: &str) -> Vec { - todo!() +pub fn assemble(src: &PathBuf) -> Vec { + let mut modules = HashSet::::new(); + let mut program = Program::new(); + + let hash = quick_hash(src); + modules.insert(hash); + + match prepare_dependency(src.clone(), &mut modules, &mut program) { + Ok(_) => {} + Err(err) => println!("BIG ERROR {:?}", err), + } + + for node in program.nodes { + println!("{:?}", node); + } + + vec![] +} + +fn prepare_dependency( + path: PathBuf, + modules: &mut HashSet, + program: &mut Program, +) -> Result<(), AssembleError> { + let filename = path.file_name().unwrap().to_str().unwrap(); + if let Ok(path) = path.canonicalize() { + log(&format!( + "{:20} {:20} [{}]", + "Building", + filename, + path.display() + )); + } + + let src = fs::read_to_string(&path) + .map_err(|_| AssembleError::InvalidFile(path.clone()))?; + let file_hash = quick_hash(&path); + + log(&format!("{:20} {:20}", "Tokenising", filename)); + let tokens = lexer::lexer(src, file_hash)?; + + log(&format!("{:20} {:20}", "Parsing", filename)); + let mut parser = Parser::new(tokens); + + log(&format!("{:20} {:20}", "Resolving Deps", filename)); + let deps = parser + .parse_nodes()? + .resolve_dependencies()? + .get_dependencies()?; + program.add_module(parser.get()); + + for dep in deps { + log(&format!( + "{:20} {:20}", + "Including", + dep.file_name().unwrap().to_str().unwrap() + )); + + if !modules.contains(&quick_hash(&dep)) { + modules.insert(quick_hash(&dep)); + prepare_dependency(dep, modules, program)? + } + } + + Ok(()) +} + +fn build(src: Vec) -> Result, AssembleError> { + Ok(vec![]) } /// TODO: disassembling functionality @@ -23,6 +100,7 @@ pub fn disassemble(_: Vec) -> String { #[derive(Debug)] pub enum AssembleError { Generic, + InvalidFile(PathBuf), UnexpectedToken(Token, TokenType), } @@ -33,6 +111,17 @@ impl fmt::Display for AssembleError { AssembleError::UnexpectedToken(tok, expected) => { write!(f, "Unexpected token {tok:?}, expected {expected:?}") } + AssembleError::InvalidFile(path) => write!(f, "Invalid file {path:?}"), } } } + +fn quick_hash(value: &PathBuf) -> u64 { + let mut hasher = DefaultHasher::new(); + value.canonicalize().unwrap().to_str().hash(&mut hasher); + hasher.finish() +} + +fn log(message: &str) { + println!("\x1b[32mINFO:\x1b[0m {}", message); +} diff --git a/assembler/src/main.rs b/assembler/src/main.rs index efda738..7645a1a 100644 --- a/assembler/src/main.rs +++ b/assembler/src/main.rs @@ -1,16 +1,25 @@ -use std::fs; +use std::{fs, io::Write, path::PathBuf}; 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(); - - println!("{tokens:?}"); - - let parser = Parser::new(tokens); - - for node in parser { - println!("{node:?}"); + // parse args: + let args: Vec = std::env::args().collect(); + if args.len() != 5 || args[1] != "-i" || args[3] != "-o" { + eprintln!("Usage: binary_name -i input_path -o output_path"); + std::process::exit(1); } + let input_path = &args[2]; + let output_path = &args[4]; + + let src = PathBuf::from(input_path); + let mut output_file = fs::File::create(output_path).unwrap(); + + let res = assembler::assemble(&src) + .iter() + .map(|i| i.encode()) + .for_each(|i| { + output_file.write_all(&i.to_be_bytes()).unwrap(); + }); } diff --git a/assembler/src/model.rs b/assembler/src/model.rs new file mode 100644 index 0000000..dd5aeae --- /dev/null +++ b/assembler/src/model.rs @@ -0,0 +1,282 @@ +use std::{fmt, str::FromStr}; + +use common::prelude::Register; + +#[derive(Debug, Clone)] +#[expect(dead_code)] +pub struct Node(pub Option, pub Opcode, pub Vec); + +impl fmt::Display for Node { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let symbol = match &self.0 { + Some(symbol) => format!("{}", symbol), + None => "".to_string(), + }; + + write!(f, "Node: {} {} {:?}", symbol, self.1, self.2) + } +} + +impl fmt::Display for Symbol { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}::{}", self.module, self.name) + } +} + +impl fmt::Display for Module { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Module::Unresolved(name) => write!(f, "{}", name), + Module::Resolved(name) => write!(f, "{}", name), + } + } +} + +impl fmt::Display for Opcode { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self) + } +} + +#[derive(Debug, Clone)] +pub struct Symbol { + pub name: String, + pub module: Module, +} + +#[derive(Debug, Clone)] +pub enum Module { + Resolved(u64), + Unresolved(String), +} + +#[derive(Debug, Clone)] +pub enum Token { + Symbol(Symbol), + Register(Register), + Immediate(u32), + StringLit(String), + Opcode(Opcode), +} + +#[derive(Debug, PartialEq, Copy, Clone)] +pub enum TokenType { + Symbol, + Register, + Immediate, + StringLit, + Opcode, +} + +impl TokenType { + pub fn from_token(token: &Token) -> TokenType { + match token { + Token::Symbol(_) => TokenType::Symbol, + Token::Register(_) => TokenType::Register, + Token::Immediate(_) => TokenType::Immediate, + Token::StringLit(_) => TokenType::StringLit, + Token::Opcode(_) => TokenType::Opcode, + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub enum Opcode { + // Real instructions (0x00-0x26) + Nop, + Mov, + Movs, + Ldb, + Ldbs, + Ldh, + Ldhs, + Ldw, + Stb, + Sth, + Stw, + Lli, + Lui, + Jmp, + Jeq, + Jne, + Jgt, + Jge, + Jlt, + Jle, + Cmp, + Inc, + Dec, + Shl, + Shr, + Add, + Sub, + And, + Or, + Not, + Xor, + Nand, + Nor, + Xnor, + Int, + Irt, + Hlt, + Iadd, + Isub, + // Pseudo-instructions + Db, + Dh, + Dw, + Resb, + Resh, + Resw, + Push, + Pop, + Lwi, + Include, +} + +#[derive(Debug)] +pub enum OpcodeFromStrError { + InvalidRegister(&'static str), +} + +impl std::fmt::Display for OpcodeFromStrError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::InvalidRegister(reg) => write!(f, "register does not exist: {reg}"), + } + } +} + +impl std::error::Error for OpcodeFromStrError {} + +impl FromStr for Opcode { + type Err = OpcodeFromStrError; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "nop" => Ok(Self::Nop), + "mov" => Ok(Self::Mov), + "movs" => Ok(Self::Movs), + "ldb" => Ok(Self::Ldb), + "ldbs" => Ok(Self::Ldbs), + "ldh" => Ok(Self::Ldh), + "ldhs" => Ok(Self::Ldhs), + "ldw" => Ok(Self::Ldw), + "stb" => Ok(Self::Stb), + "sth" => Ok(Self::Sth), + "stw" => Ok(Self::Stw), + "lli" => Ok(Self::Lli), + "lui" => Ok(Self::Lui), + "jmp" => Ok(Self::Jmp), + "jeq" => Ok(Self::Jeq), + "jne" => Ok(Self::Jne), + "jgt" => Ok(Self::Jgt), + "jge" => Ok(Self::Jge), + "jlt" => Ok(Self::Jlt), + "jle" => Ok(Self::Jle), + "cmp" => Ok(Self::Cmp), + "inc" => Ok(Self::Inc), + "dec" => Ok(Self::Dec), + "shl" => Ok(Self::Shl), + "shr" => Ok(Self::Shr), + "add" => Ok(Self::Add), + "sub" => Ok(Self::Sub), + "and" => Ok(Self::And), + "or" => Ok(Self::Or), + "not" => Ok(Self::Not), + "xor" => Ok(Self::Xor), + "nand" => Ok(Self::Nand), + "nor" => Ok(Self::Nor), + "xnor" => Ok(Self::Xnor), + "int" => Ok(Self::Int), + "irt" => Ok(Self::Irt), + "hlt" => Ok(Self::Hlt), + "iadd" => Ok(Self::Iadd), + "isub" => Ok(Self::Isub), + "db" => Ok(Self::Db), + "dh" => Ok(Self::Dh), + "dw" => Ok(Self::Dw), + "resb" => Ok(Self::Resb), + "resh" => Ok(Self::Resh), + "resw" => Ok(Self::Resw), + "push" => Ok(Self::Push), + "pop" => Ok(Self::Pop), + "lwi" => Ok(Self::Lwi), + "include" => Ok(Self::Include), + _ => Err(OpcodeFromStrError::InvalidRegister("unknown opcode")), + } + } +} + +impl Opcode { + pub const OPCODES: &[&str] = &[ + // Real instructions (0x00-0x26) + "nop", "mov", "movs", "ldb", "ldbs", "ldh", "ldhs", "ldw", "stb", "sth", "stw", + "lli", "lui", "jmp", "jeq", "jne", "jgt", "jge", "jlt", "jle", "cmp", "inc", + "dec", "shl", "shr", "add", "sub", "and", "or", "not", "xor", "nand", "nor", + "xnor", "int", "irt", "hlt", "iadd", "isub", // Pseudo-instructions + "db", "dh", "dw", "resb", "resh", "resw", "push", "pop", "lwi", "include", + ]; + + pub fn to_opcode_value(&self) -> Option { + match self { + Self::Nop => Some(0x00), + Self::Mov => Some(0x01), + Self::Movs => Some(0x02), + Self::Ldb => Some(0x03), + Self::Ldbs => Some(0x04), + Self::Ldh => Some(0x05), + Self::Ldhs => Some(0x06), + Self::Ldw => Some(0x07), + Self::Stb => Some(0x08), + Self::Sth => Some(0x09), + Self::Stw => Some(0x0A), + Self::Lli => Some(0x0B), + Self::Lui => Some(0x0C), + Self::Jmp => Some(0x0D), + Self::Jeq => Some(0x0E), + Self::Jne => Some(0x0F), + Self::Jgt => Some(0x10), + Self::Jge => Some(0x11), + Self::Jlt => Some(0x12), + Self::Jle => Some(0x13), + Self::Cmp => Some(0x14), + Self::Inc => Some(0x15), + Self::Dec => Some(0x16), + Self::Shl => Some(0x17), + Self::Shr => Some(0x18), + Self::Add => Some(0x19), + Self::Sub => Some(0x1A), + Self::And => Some(0x1B), + Self::Or => Some(0x1C), + Self::Not => Some(0x1D), + Self::Xor => Some(0x1E), + Self::Nand => Some(0x1F), + Self::Nor => Some(0x20), + Self::Xnor => Some(0x21), + Self::Int => Some(0x22), + Self::Irt => Some(0x23), + Self::Hlt => Some(0x24), + Self::Iadd => Some(0x25), + Self::Isub => Some(0x26), + // Pseudo-instructions don't have opcode values + _ => None, + } + } + + pub fn is_pseudo_instruction(&self) -> bool { + matches!( + self, + Self::Db + | Self::Dh + | Self::Dw + | Self::Resb + | Self::Resh + | Self::Resw + | Self::Push + | Self::Pop + | Self::Lwi + ) + } +} diff --git a/assembler/src/parser.rs b/assembler/src/parser.rs index 6cd39d6..eb1457e 100644 --- a/assembler/src/parser.rs +++ b/assembler/src/parser.rs @@ -1,73 +1,35 @@ +use core::fmt; +use std::path::PathBuf; use std::str::FromStr; -use crate::AssembleError; -use crate::lexer::{Symbol, Token}; +use common::prelude::{Instruction, Register}; + +use crate::model::{Module, Node, Opcode, Symbol, Token, TokenType}; +use crate::{AssembleError, quick_hash}; pub struct Parser { tokens: Vec, + nodes: Vec, } -#[derive(Debug, PartialEq, Copy, Clone)] -pub enum TokenType { - Symbol, - Register, - Immediate, - StringLit, - Opcode, +pub struct Program { + pub nodes: Vec, } -impl TokenType { - fn from_token(token: &Token) -> TokenType { - match token { - Token::Symbol(_) => TokenType::Symbol, - Token::Register(_) => TokenType::Register, - Token::Immediate(_) => TokenType::Immediate, - Token::StringLit(_) => TokenType::StringLit, - Token::Opcode(_) => TokenType::Opcode, - } +impl Program { + pub fn new() -> Program { + Program { nodes: vec![] } } -} -// TODO: MAKE SURE I DO THE BIT SHIFT FOR LUI CODEGEN -// TODO: MAKE SURE I DO THE BIT SHIFT FOR LUI CODEGEN -// TODO: MAKE SURE I DO THE BIT SHIFT FOR LUI CODEGEN -// TODO: MAKE SURE I DO THE BIT SHIFT FOR LUI CODEGEN -// TODO: MAKE SURE I DO THE BIT SHIFT FOR LUI CODEGEN -// TODO: MAKE SURE I DO THE BIT SHIFT FOR LUI CODEGEN -// TODO: MAKE SURE I DO THE BIT SHIFT FOR LUI CODEGEN -// TODO: MAKE SURE I DO THE BIT SHIFT FOR LUI CODEGEN -// TODO: MAKE SURE I DO THE BIT SHIFT FOR LUI CODEGEN -// TODO: MAKE SURE I DO THE BIT SHIFT FOR LUI CODEGEN -// TODO: MAKE SURE I DO THE BIT SHIFT FOR LUI CODEGEN -// TODO: MAKE SURE I DO THE BIT SHIFT FOR LUI CODEGEN -// TODO: MAKE SURE I DO THE BIT SHIFT FOR LUI CODEGEN -// TODO: MAKE SURE I DO THE BIT SHIFT FOR LUI CODEGEN -// TODO: MAKE SURE I DO THE BIT SHIFT FOR LUI CODEGEN -// TODO: MAKE SURE I DO THE BIT SHIFT FOR LUI CODEGEN -// TODO: MAKE SURE I DO THE BIT SHIFT FOR LUI CODEGEN -// TODO: MAKE SURE I DO THE BIT SHIFT FOR LUI CODEGEN -// TODO: MAKE SURE I DO THE BIT SHIFT FOR LUI CODEGEN -// TODO: MAKE SURE I DO THE BIT SHIFT FOR LUI CODEGEN -// TODO: MAKE SURE I DO THE BIT SHIFT FOR LUI CODEGEN -// TODO: MAKE SURE I DO THE BIT SHIFT FOR LUI CODEGEN -// TODO: MAKE SURE I DO THE BIT SHIFT FOR LUI CODEGEN -// TODO: MAKE SURE I DO THE BIT SHIFT FOR LUI CODEGEN -// TODO: MAKE SURE I DO THE BIT SHIFT FOR LUI CODEGEN -// TODO: MAKE SURE I DO THE BIT SHIFT FOR LUI CODEGEN + pub fn add_module(&mut self, module: Vec) { + self.nodes.extend(module); + } -#[derive(Debug)] -#[expect(dead_code)] -pub struct Node(Option, Opcode, Vec); - -impl Iterator for Parser { - type Item = Result; - - fn next(&mut self) -> Option> { - if self.tokens.is_empty() { - return None; + pub fn parser(&mut self) -> Parser { + Parser { + tokens: vec![], + nodes: self.nodes.clone(), } - - Some(self.parse_instruction()) } } @@ -75,9 +37,128 @@ impl Parser { pub fn new(tokens: Vec) -> Parser { Parser { tokens: tokens.into_iter().rev().collect(), + nodes: vec![], } } + pub fn parse_nodes(&mut self) -> Result<&mut Self, AssembleError> { + while !self.tokens.is_empty() { + let ins = self.parse_instruction()?; + self.nodes.push(ins); + } + + Ok(self) + } + + pub fn get_dependencies(&mut self) -> Result, AssembleError> { + let mut dependencies = Vec::new(); + for node in &self.nodes { + if let Opcode::Include = node.1 { + if let Token::StringLit(path) = node.2.get(1).unwrap() { + dependencies.push(PathBuf::from(path)); + } + } + } + Ok(dependencies) + } + + pub fn resolve_dependencies(&mut self) -> Result<&mut Self, AssembleError> { + // first we get a list of imports + let mut dependencies = Vec::new(); + for node in &self.nodes { + if let Opcode::Include = node.1 { + // we want the path, and the name + let name = if let Token::Symbol(name) = node.2.get(0).unwrap() { + name.name.clone() + } else { + unreachable!() + }; //node.2.get(0).unwrap() + let path = if let Token::StringLit(path) = node.2.get(1).unwrap() { + path + } else { + unreachable!() + }; + let hash = quick_hash(&PathBuf::from(path).canonicalize().unwrap()); + + dependencies.push((name, hash)); + } + } + + let mut changes = Vec::<(u32, u32, Symbol)>::new(); + // now we resolve the symbols on all the nodes + // we need to check all operands for unresolved signals + for (i, node) in self.nodes.clone().iter().enumerate() { + let Node(_, _, operands) = node; + for (j, token) in operands.iter().enumerate() { + if let Token::Symbol(symbol) = token { + for d in &dependencies { + if let Module::Unresolved(name) = symbol.module.clone() { + if name != d.0 { + continue; + } + + let symbol = Symbol { + name: symbol.name.clone(), + module: Module::Resolved(d.1), + }; + changes.push((i as u32, j as u32, symbol)); + } + } + } + } + } + + for (i, j, symbol) in changes { + self.nodes[i as usize].2[j as usize] = Token::Symbol(symbol); + } + + Ok(self) + } + + pub fn get(&self) -> Vec { + self.nodes.clone() + } + + pub fn expand_pseudo_ops(&mut self) -> Result<&mut Self, AssembleError> { + for node in self.nodes.iter_mut() { + match node.1 { + Opcode::Db | Opcode::Dh | Opcode::Dw => todo!(), + Opcode::Resb | Opcode::Resh | Opcode::Resw => todo!(), + + Opcode::Push => { + // inc SPR + // STW reg, SPR + let label = node.0.clone(); + let reg = node.2.get(0).unwrap(); + + vec![ + Node( + label.clone(), + Opcode::Inc, + vec![Token::Register(Register::Spr)], + ), + Node( + label.clone(), + Opcode::Stw, + vec![reg.clone(), Token::Register(Register::Spr)], + ), + ]; + } + _ => continue, + } + } + + Ok(self) + } + + pub fn resolve_symbols(&mut self) -> Result<&mut Self, AssembleError> { + Ok(self) + } + + pub fn instructions(&mut self) -> Vec { + vec![] + } + fn parse_instruction(&mut self) -> Result { if self.tokens.is_empty() { unreachable!(); @@ -170,6 +251,12 @@ impl Parser { args = vec![reg]; } + Opcode::Include => { + let mod_name = self.expect(TokenType::Symbol)?; + let path = self.expect(TokenType::StringLit)?; + args = vec![mod_name, path]; + } + // J-type instructions Opcode::Jmp | Opcode::Jeq @@ -312,202 +399,3 @@ impl Parser { } } } - -#[derive(Debug, Clone, PartialEq)] -pub enum Opcode { - // Real instructions (0x00-0x26) - Nop, - Mov, - Movs, - Ldb, - Ldbs, - Ldh, - Ldhs, - Ldw, - Stb, - Sth, - Stw, - Lli, - Lui, - Jmp, - Jeq, - Jne, - Jgt, - Jge, - Jlt, - Jle, - Cmp, - Inc, - Dec, - Shl, - Shr, - Add, - Sub, - And, - Or, - Not, - Xor, - Nand, - Nor, - Xnor, - Int, - Irt, - Hlt, - Iadd, - Isub, - // Pseudo-instructions - Db, - Dh, - Dw, - Resb, - Resh, - Resw, - Push, - Pop, - Lwi, -} - -#[derive(Debug)] -pub enum OpcodeFromStrError { - InvalidRegister(&'static str), -} - -impl std::fmt::Display for OpcodeFromStrError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::InvalidRegister(reg) => write!(f, "register does not exist: {reg}"), - } - } -} - -impl std::error::Error for OpcodeFromStrError {} - -impl FromStr for Opcode { - type Err = OpcodeFromStrError; - - fn from_str(s: &str) -> Result { - match s.to_lowercase().as_str() { - "nop" => Ok(Self::Nop), - "mov" => Ok(Self::Mov), - "movs" => Ok(Self::Movs), - "ldb" => Ok(Self::Ldb), - "ldbs" => Ok(Self::Ldbs), - "ldh" => Ok(Self::Ldh), - "ldhs" => Ok(Self::Ldhs), - "ldw" => Ok(Self::Ldw), - "stb" => Ok(Self::Stb), - "sth" => Ok(Self::Sth), - "stw" => Ok(Self::Stw), - "lli" => Ok(Self::Lli), - "lui" => Ok(Self::Lui), - "jmp" => Ok(Self::Jmp), - "jeq" => Ok(Self::Jeq), - "jne" => Ok(Self::Jne), - "jgt" => Ok(Self::Jgt), - "jge" => Ok(Self::Jge), - "jlt" => Ok(Self::Jlt), - "jle" => Ok(Self::Jle), - "cmp" => Ok(Self::Cmp), - "inc" => Ok(Self::Inc), - "dec" => Ok(Self::Dec), - "shl" => Ok(Self::Shl), - "shr" => Ok(Self::Shr), - "add" => Ok(Self::Add), - "sub" => Ok(Self::Sub), - "and" => Ok(Self::And), - "or" => Ok(Self::Or), - "not" => Ok(Self::Not), - "xor" => Ok(Self::Xor), - "nand" => Ok(Self::Nand), - "nor" => Ok(Self::Nor), - "xnor" => Ok(Self::Xnor), - "int" => Ok(Self::Int), - "irt" => Ok(Self::Irt), - "hlt" => Ok(Self::Hlt), - "iadd" => Ok(Self::Iadd), - "isub" => Ok(Self::Isub), - "db" => Ok(Self::Db), - "dh" => Ok(Self::Dh), - "dw" => Ok(Self::Dw), - "resb" => Ok(Self::Resb), - "resh" => Ok(Self::Resh), - "resw" => Ok(Self::Resw), - "push" => Ok(Self::Push), - "pop" => Ok(Self::Pop), - "lwi" => Ok(Self::Lwi), - _ => Err(OpcodeFromStrError::InvalidRegister("unknown opcode")), - } - } -} - -impl Opcode { - pub const OPCODES: &[&str] = &[ - // Real instructions (0x00-0x26) - "nop", "mov", "movs", "ldb", "ldbs", "ldh", "ldhs", "ldw", "stb", "sth", "stw", - "lli", "lui", "jmp", "jeq", "jne", "jgt", "jge", "jlt", "jle", "cmp", "inc", - "dec", "shl", "shr", "add", "sub", "and", "or", "not", "xor", "nand", "nor", - "xnor", "int", "irt", "hlt", "iadd", "isub", // Pseudo-instructions - "db", "dh", "dw", "resb", "resh", "resw", "push", "pop", "lwi", - ]; - - pub fn to_opcode_value(&self) -> Option { - match self { - Self::Nop => Some(0x00), - Self::Mov => Some(0x01), - Self::Movs => Some(0x02), - Self::Ldb => Some(0x03), - Self::Ldbs => Some(0x04), - Self::Ldh => Some(0x05), - Self::Ldhs => Some(0x06), - Self::Ldw => Some(0x07), - Self::Stb => Some(0x08), - Self::Sth => Some(0x09), - Self::Stw => Some(0x0A), - Self::Lli => Some(0x0B), - Self::Lui => Some(0x0C), - Self::Jmp => Some(0x0D), - Self::Jeq => Some(0x0E), - Self::Jne => Some(0x0F), - Self::Jgt => Some(0x10), - Self::Jge => Some(0x11), - Self::Jlt => Some(0x12), - Self::Jle => Some(0x13), - Self::Cmp => Some(0x14), - Self::Inc => Some(0x15), - Self::Dec => Some(0x16), - Self::Shl => Some(0x17), - Self::Shr => Some(0x18), - Self::Add => Some(0x19), - Self::Sub => Some(0x1A), - Self::And => Some(0x1B), - Self::Or => Some(0x1C), - Self::Not => Some(0x1D), - Self::Xor => Some(0x1E), - Self::Nand => Some(0x1F), - Self::Nor => Some(0x20), - Self::Xnor => Some(0x21), - Self::Int => Some(0x22), - Self::Irt => Some(0x23), - Self::Hlt => Some(0x24), - Self::Iadd => Some(0x25), - Self::Isub => Some(0x26), - // Pseudo-instructions don't have opcode values - _ => None, - } - } - - pub fn is_pseudo_instruction(&self) -> bool { - matches!( - self, - Self::Db - | Self::Dh - | Self::Dw - | Self::Resb - | Self::Resh - | Self::Resw - | Self::Push - | Self::Pop - | Self::Lwi - ) - } -} 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/emulator/Cargo.toml b/emulator/Cargo.toml index e79fe90..b8bd3e0 100644 --- a/emulator/Cargo.toml +++ b/emulator/Cargo.toml @@ -10,6 +10,7 @@ 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" diff --git a/emulator/src/emulator/system/memory.rs b/emulator/src/emulator/system/memory.rs index ff620d0..aa655e7 100644 --- a/emulator/src/emulator/system/memory.rs +++ b/emulator/src/emulator/system/memory.rs @@ -86,6 +86,9 @@ impl MemoryUnit for MainStore { for i in 0..size { data.push(self.read_byte(addr + i)); } + + // println!("reading {data:?} from {addr:x?}"); + data } @@ -105,8 +108,10 @@ impl MemoryUnit for MainStore { } fn write_range(&mut self, addr: u32, value: Vec) { - for byte in value { - let (block_addr, offset) = Self::segment_addr(addr); + // println!("writing {value:?} to {addr:x?}"); + + for (i, byte) in value.into_iter().enumerate() { + let (block_addr, offset) = Self::segment_addr(addr + i as u32); let block = self.mut_block(block_addr); block.data[offset as usize] = byte; } diff --git a/emulator/src/emulator/ui/editor.rs b/emulator/src/emulator/ui/editor.rs index a4d009e..70343c7 100644 --- a/emulator/src/emulator/ui/editor.rs +++ b/emulator/src/emulator/ui/editor.rs @@ -1,15 +1,18 @@ -use std::sync::mpsc::Sender; +use std::{ffi::OsStr, path::PathBuf, sync::mpsc::Sender}; -use egui::{Align, Context, Layout, Ui}; +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 +40,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 +69,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 +84,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 +157,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 +172,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 +209,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 +229,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 +264,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 +286,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 diff --git a/resources/dsa/fib.dsa b/resources/dsa/fib.dsa new file mode 100644 index 0000000..cd52942 --- /dev/null +++ b/resources/dsa/fib.dsa @@ -0,0 +1,34 @@ +include print "../resources/dsa/print.dsa" +// Fibonacci sequence calculator in DSA assembly +// Calculates the first 8 Fibonacci numbers: 0, 1, 1, 2, 3, 5, 8, 13 +dw fib_count: 6 // How many more numbers to calculate after F(0) and F(1) + +init: + // Initialize the first two Fibonacci numbers + lli 0, rg0 // F(0) = 0 + lli 1, rg1 // F(1) = 1 + + // Load loop counter + ldw fib_count, zero, rg2 // Load number of iterations remaining + +fibonacci_loop: + // Calculate next Fibonacci number: F(n) = F(n-1) + F(n-2) + add rg0, rg1, rg4 // rg4 = rg0 + rg1 (new Fibonacci number) + + // Shift the sequence forward + mov rg1, rg0 // rg0 = previous rg1 (F(n-2) = F(n-1)) + mov rg4, rg1 // rg1 = rg4 (F(n-1) = F(n)) + + // Decrement loop counter + dec rg2 // rg2 = rg2 - 1 + + // Check if we should continue looping + cmp rg2, zero // Compare counter with 0 + jgt fibonacci_loop // Jump back if counter > 0 + +finish: + mov rg1, acc // Final Fibonacci number is in acc + hlt + + jmp print::run + diff --git a/resources/dsb/fib.dsb b/resources/dsb/fib.dsb new file mode 100644 index 0000000..e69de29