From 2582ad10fa402e1d92bded52241b46c2d8656496 Mon Sep 17 00:00:00 2001 From: zxq5 Date: Mon, 30 Jun 2025 20:44:39 +0100 Subject: [PATCH 01/53] started work on compiler --- Cargo.lock | 4 + Cargo.toml | 2 +- compiler/Cargo.toml | 7 + compiler/src/lexer.rs | 342 ++++++++++++++++++++++++++++++++++++++ compiler/src/main.rs | 25 +++ compiler/src/parser.rs | 146 ++++++++++++++++ resources/dsc/example.dsc | 8 + 7 files changed, 533 insertions(+), 1 deletion(-) create mode 100644 compiler/Cargo.toml create mode 100644 compiler/src/lexer.rs create mode 100644 compiler/src/main.rs create mode 100644 compiler/src/parser.rs create mode 100644 resources/dsc/example.dsc diff --git a/Cargo.lock b/Cargo.lock index a84889a..51d0e25 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -642,6 +642,10 @@ dependencies = [ name = "common" version = "0.2.0" +[[package]] +name = "compiler" +version = "0.2.0" + [[package]] name = "concurrent-queue" version = "2.5.0" diff --git a/Cargo.toml b/Cargo.toml index d5039da..9ae7c73 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ cargo-features = ["codegen-backend"] [workspace] -members = ["emulator", "common", "assembler", "dsa_editor"] +members = ["emulator", "common", "assembler", "dsa_editor", "compiler"] resolver = "3" [workspace.package] diff --git a/compiler/Cargo.toml b/compiler/Cargo.toml new file mode 100644 index 0000000..15e1473 --- /dev/null +++ b/compiler/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "compiler" +version.workspace = true +edition.workspace = true +authors.workspace = true + +[dependencies] diff --git a/compiler/src/lexer.rs b/compiler/src/lexer.rs new file mode 100644 index 0000000..e616b93 --- /dev/null +++ b/compiler/src/lexer.rs @@ -0,0 +1,342 @@ +use std::iter::Peekable; +use std::str::Chars; + +#[derive(Debug, PartialEq, Clone)] +pub enum Token { + // Keywords + If, + Else, + Loop, + Break, + Return, + Continue, + + // Identifiers and literals + Identifier(String), + String(String), + Number(i64), + + // Symbols + LeftParen, // ( + RightParen, // ) + LeftBrace, // { + RightBrace, // } + Semicolon, // ; + Colon, // : + Comma, // , + Pipe, // | + + // Operators + Plus, // + + Minus, // - + Star, // * + Slash, // / + Assign, // = + EqualEqual, // == + Bang, // ! + BangEqual, // != + Less, // < + LessEqual, // <= + Greater, // > + GreaterEqual, // >= + + // Special + Eof, +} + +impl Token { + pub fn tt(&self) -> &str { + match self { + Token::If => "If", + Token::Else => "Else", + Token::Loop => "Loop", + Token::Break => "Break", + Token::Return => "Return", + Token::Continue => "Continue", + Token::Identifier(_) => "Identifier", + Token::String(_) => "String", + Token::Number(_) => "Number", + Token::LeftParen => "LeftParen", + Token::RightParen => "RightParen", + Token::LeftBrace => "LeftBrace", + Token::RightBrace => "RightBrace", + Token::Semicolon => "Semicolon", + Token::Colon => "Colon", + Token::Comma => "Comma", + Token::Pipe => "Pipe", + Token::Plus => "Plus", + Token::Minus => "Minus", + Token::Star => "Star", + Token::Slash => "Slash", + Token::Assign => "Assign", + Token::EqualEqual => "EqualEqual", + Token::Bang => "Bang", + Token::BangEqual => "BangEqual", + Token::Less => "Less", + Token::LessEqual => "LessEqual", + Token::Greater => "Greater", + Token::GreaterEqual => "GreaterEqual", + Token::Eof => "Eof", + } + } +} + +#[derive(Debug)] +pub struct Lexer<'a> { + chars: Peekable>, + current: Option, + line: usize, +} + +impl<'a> Lexer<'a> { + pub fn new(input: &'a str) -> Self { + let mut chars = input.chars().peekable(); + let current = chars.next(); + + Lexer { + chars, + current, + line: 1, + } + } + + fn advance(&mut self) -> Option { + self.current = self.chars.next(); + self.current + } + + fn peek(&mut self) -> Option<&char> { + self.chars.peek() + } + + fn skip_whitespace(&mut self) { + while let Some(c) = self.current { + if !c.is_whitespace() { + break; + } + if c == '\n' { + self.line += 1; + } + self.advance(); + } + } + + fn read_identifier(&mut self) -> String { + let mut ident = String::new(); + while let Some(&c) = self.peek() { + if c.is_alphanumeric() || c == '_' { + ident.push(c); + self.advance(); + } else { + break; + } + } + ident + } + + fn read_number(&mut self) -> i64 { + let mut num_str = String::new(); + while let Some(&c) = self.peek() { + if c.is_ascii_digit() { + num_str.push(c); + self.advance(); + } else { + break; + } + } + num_str.parse().unwrap_or(0) + } + + fn match_next(&mut self, expected: char) -> bool { + match self.peek() { + Some(&c) if c == expected => { + self.advance(); + true + } + _ => false, + } + } + + pub fn next_token(&mut self) -> Token { + self.skip_whitespace(); + + let token = match self.current { + Some('(') => Token::LeftParen, + Some(')') => Token::RightParen, + Some('{') => Token::LeftBrace, + Some('}') => Token::RightBrace, + Some(';') => Token::Semicolon, + Some(':') => Token::Colon, + Some(',') => Token::Comma, + Some('|') => Token::Pipe, + Some('+') => Token::Plus, + Some('-') => Token::Minus, + Some('*') => Token::Star, + Some('/') => Token::Slash, + Some('!') => { + if self.match_next('=') { + Token::BangEqual + } else { + Token::Bang + } + } + Some('=') => { + if self.match_next('=') { + Token::EqualEqual + } else { + Token::Assign + } + } + Some('<') => { + if self.match_next('=') { + Token::LessEqual + } else { + Token::Less + } + } + Some('>') => { + if self.match_next('=') { + Token::GreaterEqual + } else { + Token::Greater + } + } + Some('"') => { + self.advance(); // Skip the opening quote + let mut s = String::new(); + while let Some(c) = self.current { + if c == '"' { + break; + } + s.push(c); + self.advance(); + } + Token::String(s) + } + Some(c) => { + if c.is_alphabetic() || c == '_' { + let mut ident = c.to_string(); + ident.push_str(&self.read_identifier()); + match ident.as_str() { + "if" => Token::If, + "else" => Token::Else, + "loop" => Token::Loop, + "break" => Token::Break, + "return" => Token::Return, + "continue" => Token::Continue, + _ => Token::Identifier(ident), + } + } else if c.is_ascii_digit() { + Token::Number(self.read_number()) + } else { + // Skip unknown characters for now + self.advance(); + return self.next_token(); + } + } + None => Token::Eof, + }; + + if token != Token::Eof { + self.advance(); + } + + token + } +} + +impl<'a> Iterator for Lexer<'a> { + type Item = Token; + + fn next(&mut self) -> Option { + match self.next_token() { + Token::Eof => None, + token => Some(token), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_keywords() { + let input = "if else loop break return continue"; + let mut lexer = Lexer::new(input); + + assert_eq!(lexer.next_token(), Token::If); + assert_eq!(lexer.next_token(), Token::Else); + assert_eq!(lexer.next_token(), Token::Loop); + assert_eq!(lexer.next_token(), Token::Break); + assert_eq!(lexer.next_token(), Token::Return); + assert_eq!(lexer.next_token(), Token::Continue); + assert_eq!(lexer.next_token(), Token::Eof); + } + + #[test] + fn test_identifiers_and_numbers() { + let input = "x y42 _test 123 45"; + let mut lexer = Lexer::new(input); + + assert_eq!(lexer.next_token(), Token::Identifier("x".to_string())); + assert_eq!(lexer.next_token(), Token::Identifier("y42".to_string())); + assert_eq!(lexer.next_token(), Token::Identifier("_test".to_string())); + assert_eq!(lexer.next_token(), Token::Number(123)); + assert_eq!(lexer.next_token(), Token::Number(45)); + assert_eq!(lexer.next_token(), Token::Eof); + } + + #[test] + fn test_operators() { + let input = "= == ! != < <= > >="; + let mut lexer = Lexer::new(input); + + assert_eq!(lexer.next_token(), Token::Assign); + assert_eq!(lexer.next_token(), Token::EqualEqual); + assert_eq!(lexer.next_token(), Token::Bang); + assert_eq!(lexer.next_token(), Token::BangEqual); + assert_eq!(lexer.next_token(), Token::Less); + assert_eq!(lexer.next_token(), Token::LessEqual); + assert_eq!(lexer.next_token(), Token::Greater); + assert_eq!(lexer.next_token(), Token::GreaterEqual); + assert_eq!(lexer.next_token(), Token::Eof); + } + + #[test] + fn test_example_syntax() { + let input = r#" + main: Func = | x: U32, y: U32 | { + res = add(x, y); + print(res); + + if res > 10 { + print("res is greater than 10"); + } + } + "#; + + let mut lexer = Lexer::new(input); + + // Skip whitespace and newlines + while let Some(c) = lexer.current { + if !c.is_whitespace() { + break; + } + lexer.advance(); + } + + // Test the first few tokens + assert_eq!(lexer.next_token(), Token::Identifier("main".to_string())); + assert_eq!(lexer.next_token(), Token::Colon); + assert_eq!(lexer.next_token(), Token::Identifier("Func".to_string())); + assert_eq!(lexer.next_token(), Token::Assign); + assert_eq!(lexer.next_token(), Token::Pipe); + assert_eq!(lexer.next_token(), Token::Identifier("x".to_string())); + assert_eq!(lexer.next_token(), Token::Colon); + assert_eq!(lexer.next_token(), Token::Identifier("U32".to_string())); + assert_eq!(lexer.next_token(), Token::Comma); + + // The rest of the tokens would be tested similarly + } +} diff --git a/compiler/src/main.rs b/compiler/src/main.rs new file mode 100644 index 0000000..031ecc8 --- /dev/null +++ b/compiler/src/main.rs @@ -0,0 +1,25 @@ +use std::{fs, path::Path}; + +pub mod lexer; +pub mod parser; + +fn main() { + println!("Hello, world!"); + + let path = Path::new("../resources/dsc/example.dsc"); + let contents = fs::read_to_string(path).expect("Failed to read file"); + + let lexer = lexer::Lexer::new(&contents); + let tokens = lexer.collect::>(); + println!("{tokens:?}"); + + let mut parser = parser::Parser::new(tokens); + let ast = match parser.parse() { + Ok(ast) => ast, + Err(e) => { + eprintln!("Error: {e:?}"); + return; + } + }; + println!("{ast:?}"); +} diff --git a/compiler/src/parser.rs b/compiler/src/parser.rs new file mode 100644 index 0000000..e21e049 --- /dev/null +++ b/compiler/src/parser.rs @@ -0,0 +1,146 @@ +use crate::expect_type; +use crate::lexer::Token; + +pub struct Parser { + ast: Node, + idx: usize, + tokens: Vec, +} + +impl Parser { + pub fn new(tokens: Vec) -> Self { + Self { + ast: Node::Scope { + children: Vec::new(), + }, + idx: 0, + tokens, + } + } + + pub fn parse(&mut self) -> Result { + let mut statements = Vec::new(); + + while let Some(_) = self.peek_next() { + statements.push(self.parse_statement()?); + } + + Ok(Node::Scope { + children: statements, + }) + } + + fn parse_statement(&mut self) -> Result { + // first token in a statement is always an identifier + let left = if let Ok(typed_var) = self.parse_typed_var() { + Box::new(typed_var) + } else { + let tok = expect_type!(self.next()?, Identifier)?; + Box::new(Node::Terminal { value: tok }) + }; + + let _ = expect_type!(self.next()?, Assign)?; + + let right = Box::new(self.parse_expression()?); + + Ok(Node::Statement { left, right }) + } + + fn parse_typed_var(&mut self) -> Result { + let name = expect_type!(self.next()?, Identifier)?; + let _ = expect_type!(self.next()?, Colon)?; + let type_ = expect_type!(self.next()?, Identifier)?; + + Ok(Node::TypedVar { name, type_ }) + } + + fn parse_expression(&mut self) -> Result { + Err(CompileError::Generic) + } + + fn next(&mut self) -> Result { + if self.idx >= self.tokens.len() { + return Err(CompileError::UnexpectedEOF); + } + + let token = self.tokens[self.idx].clone(); + self.idx += 1; + + Ok(token) + } + + fn peek_next(&mut self) -> Option { + if self.idx >= self.tokens.len() { + return None; + } + + Some(self.tokens[self.idx].clone()) + } +} + +#[derive(Debug, Clone, PartialEq)] +pub enum Node { + Scope { + children: Vec, + }, + Terminal { + value: Token, + }, + UnaryOp { + op: Token, + right: Box, + }, + BinaryOp { + left: Box, + op: Token, + right: Box, + }, + Statement { + left: Box, + right: Box, + }, + If { + condition: Box, + then_branch: Box, + else_branch: Option>, + }, + FunctionDef { + params: Vec, + body: Box, + }, + TypedVar { + name: Token, + type_: Token, + }, + TypeDef { + name: Token, + fields: Vec, + }, +} + +#[derive(Debug)] +pub enum CompileError { + Generic, + ExpectedToken { expected: String, found: Token }, + UnexpectedEOF, +} + +#[macro_export] +macro_rules! expect_type { + ($token:expr, $($variant:ident),+) => {{ + match $token.tt() { + $( + stringify!($variant) => Ok($token.clone()), + )+ + _ => { + // return an expected token error + let expected = format!("[{}]", vec![$(stringify!($variant)),+].join(" | ")); + + Err(CompileError::ExpectedToken { + expected, + found: $token.clone(), + }) + } + } + }}; +} diff --git a/resources/dsc/example.dsc b/resources/dsc/example.dsc new file mode 100644 index 0000000..550a707 --- /dev/null +++ b/resources/dsc/example.dsc @@ -0,0 +1,8 @@ +main: Func = | x: U32, y: U32 | { + res = add(x, y); + print(res); + + if res > 10 { + print("res is greater than 10"); + } +} -- 2.47.3 From b33fdbfeec5691786efc763908c6df9144b0ced8 Mon Sep 17 00:00:00 2001 From: zxq5 Date: Tue, 1 Jul 2025 01:08:37 +0100 Subject: [PATCH 02/53] rust macros make me want to unalive. --- compiler/src/parser.rs | 246 ++++++++++++++++++++++++++++++++++------- 1 file changed, 203 insertions(+), 43 deletions(-) diff --git a/compiler/src/parser.rs b/compiler/src/parser.rs index e21e049..7784e09 100644 --- a/compiler/src/parser.rs +++ b/compiler/src/parser.rs @@ -1,4 +1,4 @@ -use crate::expect_type; +use crate::expect_tt; use crate::lexer::Token; pub struct Parser { @@ -10,7 +10,7 @@ pub struct Parser { impl Parser { pub fn new(tokens: Vec) -> Self { Self { - ast: Node::Scope { + ast: Node::Block { children: Vec::new(), }, idx: 0, @@ -19,13 +19,17 @@ impl Parser { } pub fn parse(&mut self) -> Result { + self.parse_block() + } + + fn parse_block(&mut self) -> Result { let mut statements = Vec::new(); - while let Some(_) = self.peek_next() { + while self.peek_next().is_ok() { statements.push(self.parse_statement()?); } - Ok(Node::Scope { + Ok(Node::Block { children: statements, }) } @@ -35,11 +39,11 @@ impl Parser { let left = if let Ok(typed_var) = self.parse_typed_var() { Box::new(typed_var) } else { - let tok = expect_type!(self.next()?, Identifier)?; + let tok = expect_tt!(self.next()?, Identifier)?; Box::new(Node::Terminal { value: tok }) }; - let _ = expect_type!(self.next()?, Assign)?; + let _ = expect_tt!(self.next()?, Assign)?; let right = Box::new(self.parse_expression()?); @@ -47,17 +51,144 @@ impl Parser { } fn parse_typed_var(&mut self) -> Result { - let name = expect_type!(self.next()?, Identifier)?; - let _ = expect_type!(self.next()?, Colon)?; - let type_ = expect_type!(self.next()?, Identifier)?; + let name = expect_tt!(self.next()?, Identifier)?; + let _ = expect_tt!(self.next()?, Colon)?; + let type_ = expect_tt!(self.next()?, Identifier)?; Ok(Node::TypedVar { name, type_ }) } fn parse_expression(&mut self) -> Result { + if expect_tt!(self.peek_next()?, Pipe).is_ok() { + return self.parse_func(); + } + + if expect_tt!(self.peek_next()?, If).is_ok() { + return self.parse_if(); + } + + if expect_tt!(self.peek_next()?, Loop).is_ok() { + return self.parse_loop(); + } + + if expect_tt!(self.peek_next()?, Identifier, String, Number).is_ok() { + let left = Node::Terminal { + value: self.next()?, + }; + + if expect_tt!( + self.next()?, + Plus, + Minus, + Star, + Slash, + EqualEqual, + BangEqual, + Less, + LessEqual, + Greater, + GreaterEqual + ) + .is_err() + { + return Ok(left); + } + + let operator = self.next()?; + let right = Box::new(self.parse_expression()?); + + return Ok(Node::BinaryOp { + left: Box::new(left), + op: operator, + right, + }); + } + Err(CompileError::Generic) } + fn parse_func(&mut self) -> Result { + // left arg delimiter + let _ = expect_tt!(self.next()?, Pipe)?; + + // parse args + let mut args = Vec::new(); + + while expect_tt!(self.peek_next()?, Identifier).is_ok() { + // add a typed var + let arg = self.parse_typed_var()?; + args.push(arg); + } + + // right arg delimiter + let _ = expect_tt!(self.next()?, Pipe)?; + + // ensure we have an open brace + let _ = expect_tt!(self.next()?, LeftBrace)?; + + // parse the body + let body = Box::new(self.parse_block()?); + + // ensure we have a close brace + let _ = expect_tt!(self.next()?, RightBrace)?; + + Ok(Node::FunctionDef { params: args, body }) + } + + fn parse_loop(&mut self) -> Result { + let _ = expect_tt!(self.next()?, Loop)?; + + // ensure we have an open brace + let _ = expect_tt!(self.next()?, LeftBrace)?; + + // parse the body + let body = Box::new(self.parse_block()?); + + // ensure we have a close brace + let _ = expect_tt!(self.next()?, RightBrace)?; + + Ok(Node::Loop { body }) + } + + fn parse_if(&mut self) -> Result { + let _ = expect_tt!(self.next()?, If)?; + + // parse condition (expr) + let condition = Box::new(self.parse_expression()?); + + // ensure we have an open brace + let _ = expect_tt!(self.next()?, LeftBrace)?; + + // parse the "then" branch (expr/statement) + let then_branch = Box::new(self.parse_expression()?); + + // ensure we have a close brace + let _ = expect_tt!(self.next()?, RightBrace)?; + + // if there is an else branch, we include it in the statement + let else_branch = self.parse_else()?.map(Box::new); + + Ok(Node::If { + condition, + then_branch, + else_branch, + }) + } + + fn parse_else(&mut self) -> Result, CompileError> { + // if there is no else branch, return None. + if expect_tt!(self.peek_next()?, Else).is_err() { + return Ok(None); + } + let _ = self.next()?; + + if expect_tt!(self.peek_next()?, If).is_ok() { + Ok(Some(self.parse_if()?)) + } else { + Ok(Some(self.parse_expression()?)) + } + } + fn next(&mut self) -> Result { if self.idx >= self.tokens.len() { return Err(CompileError::UnexpectedEOF); @@ -69,53 +200,54 @@ impl Parser { Ok(token) } - fn peek_next(&mut self) -> Option { + fn peek_next(&mut self) -> Result { if self.idx >= self.tokens.len() { - return None; + return Err(CompileError::UnexpectedEOF); } - Some(self.tokens[self.idx].clone()) + Ok(self.tokens[self.idx].clone()) } } - #[derive(Debug, Clone, PartialEq)] pub enum Node { - Scope { - children: Vec, - }, - Terminal { - value: Token, - }, - UnaryOp { - op: Token, - right: Box, - }, + /// A scope, which is a list of child nodes that are evaluated in order. + Block { children: Vec }, + + /// A leaf node with a value. + Terminal { value: Token }, + + /// A unary operator with a right operand. + UnaryOp { op: Token, right: Box }, + + /// A binary operator with a left and right operand. BinaryOp { left: Box, op: Token, right: Box, }, - Statement { - left: Box, - right: Box, - }, + + /// A statement, consisting of a value to assign to, and an expression. + Statement { left: Box, right: Box }, + + /// An if expression, which evaluates to either the then branch or the else branch. If { condition: Box, then_branch: Box, else_branch: Option>, }, - FunctionDef { - params: Vec, - body: Box, - }, - TypedVar { - name: Token, - type_: Token, - }, - TypeDef { - name: Token, - fields: Vec, - }, + + /// A loop expression, which evaluates to the last value of the loop. + /// a loop can be exited with the break keyword. + Loop { body: Box }, + + /// A function definition. ``` | param: type .. | -> ret_type { body }``` + FunctionDef { params: Vec, body: Box }, + + /// A typed variable definition: ```val: Type``` + TypedVar { name: Token, type_: Token }, + + /// A type definition, which is a list of fields. ```type MyType { field: Type }``` + TypeDef { name: Token, fields: Vec }, } #[derive(Debug)] @@ -126,16 +258,44 @@ pub enum CompileError { } #[macro_export] -macro_rules! expect_type { +macro_rules! expect_tt { ($token:expr, $($variant:ident),+) => {{ - match $token.tt() { + let tt = $token.tt().to_string(); + + println!("CASE"); + println!("TOK {:?}", $token); + println!("TT {}", tt); + + let mut vs = String::new(); + $( + let s = stringify!($variant); + vs.push_str(s); + vs.push_str("|"); + )+ + + match tt.as_str() { $( stringify!($variant) => Ok($token.clone()), )+ _ => { - // return an expected token error + println!("EXPECTED!! {} [{}]", tt, vs); let expected = format!("[{}]", vec![$(stringify!($variant)),+].join(" | ")); - + Err(CompileError::ExpectedToken { + expected, + found: $token.clone(), + }) + } + } + }}; +} + +#[macro_export] +macro_rules! expect_value { + ($token:expr, $variant:expr) => {{ + match $token { + $variant(x) => Ok(x), + _ => { + let expected = format!("[{}]") Err(CompileError::ExpectedToken { expected, found: $token.clone(), -- 2.47.3 From fd5b305576cc5574414006c95e49ce22f781813c Mon Sep 17 00:00:00 2001 From: zxq5 Date: Fri, 14 Nov 2025 23:36:51 +0000 Subject: [PATCH 03/53] started work on c compiler --- Cargo.lock | 4 + Cargo.toml | 2 +- assembler/src/assembler/mod.rs | 5 + c_compiler/Cargo.toml | 7 + c_compiler/code.c | 19 + c_compiler/compiler.py | 926 +++++++++++++++++++++++++++++++++ c_compiler/src/codegen.rs | 13 + c_compiler/src/lexer.rs | 265 ++++++++++ c_compiler/src/main.rs | 72 +++ c_compiler/src/parser.rs | 558 ++++++++++++++++++++ 10 files changed, 1870 insertions(+), 1 deletion(-) create mode 100644 c_compiler/Cargo.toml create mode 100644 c_compiler/code.c create mode 100644 c_compiler/compiler.py create mode 100644 c_compiler/src/codegen.rs create mode 100644 c_compiler/src/lexer.rs create mode 100644 c_compiler/src/main.rs create mode 100644 c_compiler/src/parser.rs diff --git a/Cargo.lock b/Cargo.lock index 51d0e25..4881937 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -539,6 +539,10 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +[[package]] +name = "c_compiler" +version = "0.2.0" + [[package]] name = "calloop" version = "0.13.0" diff --git a/Cargo.toml b/Cargo.toml index 9ae7c73..b317ec7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ cargo-features = ["codegen-backend"] [workspace] -members = ["emulator", "common", "assembler", "dsa_editor", "compiler"] +members = ["emulator", "common", "assembler", "dsa_editor", "compiler", "c_compiler"] resolver = "3" [workspace.package] diff --git a/assembler/src/assembler/mod.rs b/assembler/src/assembler/mod.rs index 26fd801..966febb 100644 --- a/assembler/src/assembler/mod.rs +++ b/assembler/src/assembler/mod.rs @@ -138,6 +138,11 @@ fn assemble(src: &Path) -> Result, AssembleError> { create_sections(&mut nodes)?; resolve_symbols(&mut nodes)?; + println!("Generating assembly output..."); + for n in &nodes { + println!("{n}"); + } + let instructions = codegen(nodes)?; Ok(instructions) } diff --git a/c_compiler/Cargo.toml b/c_compiler/Cargo.toml new file mode 100644 index 0000000..787ae6a --- /dev/null +++ b/c_compiler/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "c_compiler" +version.workspace = true +edition.workspace = true +authors.workspace = true + +[dependencies] diff --git a/c_compiler/code.c b/c_compiler/code.c new file mode 100644 index 0000000..cc43221 --- /dev/null +++ b/c_compiler/code.c @@ -0,0 +1,19 @@ +int factorial(int n) { + if (n <= 1) { + return 1; + } + return n * factorial(n - 1); +} + +int add(int a, int b) { return a + b; } + +int main() { + int x; + x = 5; + int x = 5; + int result; + int result = 5; + result = x + factorial(5); + print(result); + return 0; +} diff --git a/c_compiler/compiler.py b/c_compiler/compiler.py new file mode 100644 index 0000000..627278c --- /dev/null +++ b/c_compiler/compiler.py @@ -0,0 +1,926 @@ +#!/usr/bin/env python3 +""" +Simple C to DSA Assembly Compiler +Supports a subset of C including: +- int variables and functions +- Arithmetic operations (+, -, *, /) +- Comparisons (==, !=, <, >, <=, >=) +- If/else statements +- While loops +- Function calls +- Return statements +""" + +import re +import sys +from typing import List, Dict, Optional, Tuple +from dataclasses import dataclass +from enum import Enum +from pprint import pprint +import json + + +class TokenType(Enum): + # Keywords + INT = "int" + IF = "if" + ELSE = "else" + WHILE = "while" + RETURN = "return" + + # Identifiers and literals + IDENTIFIER = "IDENTIFIER" + NUMBER = "NUMBER" + + # Operators + PLUS = "+" + MINUS = "-" + STAR = "*" + SLASH = "/" + ASSIGN = "=" + EQ = "==" + NE = "!=" + LT = "<" + GT = ">" + LE = "<=" + GE = ">=" + + # Delimiters + LPAREN = "(" + RPAREN = ")" + LBRACE = "{" + RBRACE = "}" + SEMICOLON = ";" + COMMA = "," + + EOF = "EOF" + + +@dataclass +class Token: + type: TokenType + value: str + line: int + col: int + + +class Lexer: + def __init__(self, source: str): + self.source = source + self.pos = 0 + self.line = 1 + self.col = 1 + self.tokens = [] + + def error(self, msg: str): + raise SyntaxError(f"Lexer error at line {self.line}, col {self.col}: {msg}") + + def peek(self, offset: int = 0) -> Optional[str]: + pos = self.pos + offset + return self.source[pos] if pos < len(self.source) else None + + def advance(self) -> Optional[str]: + if self.pos >= len(self.source): + return None + char = self.source[self.pos] + self.pos += 1 + if char == "\n": + self.line += 1 + self.col = 1 + else: + self.col += 1 + return char + + def skip_whitespace(self): + while self.peek() and self.peek() in " \t\n\r": + self.advance() + + def skip_comment(self): + if self.peek() == "/" and self.peek(1) == "/": + while self.peek() and self.peek() != "\n": + self.advance() + self.advance() # skip newline + + def read_number(self) -> str: + num = "" + while self.peek() and self.peek().isdigit(): + num += self.advance() + return num + + def read_identifier(self) -> str: + ident = "" + while self.peek() and (self.peek().isalnum() or self.peek() == "_"): + ident += self.advance() + return ident + + def tokenize(self) -> List[Token]: + keywords = { + "int": TokenType.INT, + "if": TokenType.IF, + "else": TokenType.ELSE, + "while": TokenType.WHILE, + "return": TokenType.RETURN, + } + + while self.pos < len(self.source): + self.skip_whitespace() + self.skip_comment() + + if self.pos >= len(self.source): + break + + line, col = self.line, self.col + char = self.peek() + + # Numbers + if char.isdigit(): + num = self.read_number() + self.tokens.append(Token(TokenType.NUMBER, num, line, col)) + + # Identifiers and keywords + elif char.isalpha() or char == "_": + ident = self.read_identifier() + token_type = keywords.get(ident, TokenType.IDENTIFIER) + self.tokens.append(Token(token_type, ident, line, col)) + + # Two-character operators + elif char == "=" and self.peek(1) == "=": + self.advance() + self.advance() + self.tokens.append(Token(TokenType.EQ, "==", line, col)) + elif char == "!" and self.peek(1) == "=": + self.advance() + self.advance() + self.tokens.append(Token(TokenType.NE, "!=", line, col)) + elif char == "<" and self.peek(1) == "=": + self.advance() + self.advance() + self.tokens.append(Token(TokenType.LE, "<=", line, col)) + elif char == ">" and self.peek(1) == "=": + self.advance() + self.advance() + self.tokens.append(Token(TokenType.GE, ">=", line, col)) + + # Single-character operators + elif char == "+": + self.advance() + self.tokens.append(Token(TokenType.PLUS, "+", line, col)) + elif char == "-": + self.advance() + self.tokens.append(Token(TokenType.MINUS, "-", line, col)) + elif char == "*": + self.advance() + self.tokens.append(Token(TokenType.STAR, "*", line, col)) + elif char == "/": + self.advance() + self.tokens.append(Token(TokenType.SLASH, "/", line, col)) + elif char == "=": + self.advance() + self.tokens.append(Token(TokenType.ASSIGN, "=", line, col)) + elif char == "<": + self.advance() + self.tokens.append(Token(TokenType.LT, "<", line, col)) + elif char == ">": + self.advance() + self.tokens.append(Token(TokenType.GT, ">", line, col)) + elif char == "(": + self.advance() + self.tokens.append(Token(TokenType.LPAREN, "(", line, col)) + elif char == ")": + self.advance() + self.tokens.append(Token(TokenType.RPAREN, ")", line, col)) + elif char == "{": + self.advance() + self.tokens.append(Token(TokenType.LBRACE, "{", line, col)) + elif char == "}": + self.advance() + self.tokens.append(Token(TokenType.RBRACE, "}", line, col)) + elif char == ";": + self.advance() + self.tokens.append(Token(TokenType.SEMICOLON, ";", line, col)) + elif char == ",": + self.advance() + self.tokens.append(Token(TokenType.COMMA, ",", line, col)) + else: + self.error(f"Unexpected character: {char}") + + self.tokens.append(Token(TokenType.EOF, "", self.line, self.col)) + return self.tokens + + +# AST Node classes +@dataclass +class ASTNode: + pass + + +@dataclass +class Program(ASTNode): + declarations: List["Declaration"] + + +@dataclass +class Declaration(ASTNode): + pass + + +@dataclass +class FunctionDecl(Declaration): + name: str + params: List[str] + body: "CompoundStmt" + + +@dataclass +class VarDecl(Declaration): + name: str + init: Optional["Expression"] = None + + +@dataclass +class Statement(ASTNode): + pass + + +@dataclass +class CompoundStmt(Statement): + statements: List[Statement] + + +@dataclass +class ExprStmt(Statement): + expr: Optional["Expression"] + + +@dataclass +class IfStmt(Statement): + condition: "Expression" + then_stmt: Statement + else_stmt: Optional[Statement] = None + + +@dataclass +class WhileStmt(Statement): + condition: "Expression" + body: Statement + + +@dataclass +class ReturnStmt(Statement): + expr: Optional["Expression"] + + +@dataclass +class Expression(ASTNode): + pass + + +@dataclass +class BinaryOp(Expression): + op: str + left: Expression + right: Expression + + +@dataclass +class UnaryOp(Expression): + op: str + operand: Expression + + +@dataclass +class AssignExpr(Expression): + name: str + value: Expression + + +@dataclass +class VarExpr(Expression): + name: str + + +@dataclass +class NumberExpr(Expression): + value: int + + +@dataclass +class CallExpr(Expression): + name: str + args: List[Expression] + + +class Parser: + def __init__(self, tokens: List[Token]): + self.tokens = tokens + self.pos = 0 + + def error(self, msg: str): + token = self.current() + raise SyntaxError(f"Parser error at line {token.line}, col {token.col}: {msg}") + + def current(self) -> Token: + return self.tokens[self.pos] if self.pos < len(self.tokens) else self.tokens[-1] + + def peek(self, offset: int = 0) -> Token: + pos = self.pos + offset + return self.tokens[pos] if pos < len(self.tokens) else self.tokens[-1] + + def advance(self) -> Token: + token = self.current() + if self.pos < len(self.tokens) - 1: + self.pos += 1 + return token + + def expect(self, token_type: TokenType) -> Token: + token = self.current() + if token.type != token_type: + self.error(f"Expected {token_type.value}, got {token.type.value}") + return self.advance() + + def parse(self) -> Program: + declarations = [] + while self.current().type != TokenType.EOF: + declarations.append(self.parse_declaration()) + return Program(declarations) + + def parse_declaration(self) -> Declaration: + self.expect(TokenType.INT) + name = self.expect(TokenType.IDENTIFIER).value + + if self.current().type == TokenType.LPAREN: + # Function declaration + self.advance() + params = [] + + if self.current().type != TokenType.RPAREN: + self.expect(TokenType.INT) + params.append(self.expect(TokenType.IDENTIFIER).value) + + while self.current().type == TokenType.COMMA: + self.advance() + self.expect(TokenType.INT) + params.append(self.expect(TokenType.IDENTIFIER).value) + + self.expect(TokenType.RPAREN) + body = self.parse_compound_stmt() + return FunctionDecl(name, params, body) + else: + # Variable declaration + init = None + if self.current().type == TokenType.ASSIGN: + self.advance() + init = self.parse_expression() + self.expect(TokenType.SEMICOLON) + return VarDecl(name, init) + + def parse_compound_stmt(self) -> CompoundStmt: + self.expect(TokenType.LBRACE) + statements = [] + + while self.current().type != TokenType.RBRACE: + statements.append(self.parse_statement()) + + self.expect(TokenType.RBRACE) + return CompoundStmt(statements) + + def parse_statement(self) -> Statement: + token = self.current() + + if token.type == TokenType.LBRACE: + return self.parse_compound_stmt() + elif token.type == TokenType.IF: + return self.parse_if_stmt() + elif token.type == TokenType.WHILE: + return self.parse_while_stmt() + elif token.type == TokenType.RETURN: + return self.parse_return_stmt() + elif token.type == TokenType.INT: + # Local variable declaration + self.advance() + name = self.expect(TokenType.IDENTIFIER).value + init = None + if self.current().type == TokenType.ASSIGN: + self.advance() + init = self.parse_expression() + self.expect(TokenType.SEMICOLON) + return ExprStmt(AssignExpr(name, init) if init else None) + else: + expr = ( + self.parse_expression() + if self.current().type != TokenType.SEMICOLON + else None + ) + self.expect(TokenType.SEMICOLON) + return ExprStmt(expr) + + def parse_if_stmt(self) -> IfStmt: + self.expect(TokenType.IF) + self.expect(TokenType.LPAREN) + condition = self.parse_expression() + self.expect(TokenType.RPAREN) + then_stmt = self.parse_statement() + + else_stmt = None + if self.current().type == TokenType.ELSE: + self.advance() + else_stmt = self.parse_statement() + + return IfStmt(condition, then_stmt, else_stmt) + + def parse_while_stmt(self) -> WhileStmt: + self.expect(TokenType.WHILE) + self.expect(TokenType.LPAREN) + condition = self.parse_expression() + self.expect(TokenType.RPAREN) + body = self.parse_statement() + return WhileStmt(condition, body) + + def parse_return_stmt(self) -> ReturnStmt: + self.expect(TokenType.RETURN) + expr = None + if self.current().type != TokenType.SEMICOLON: + expr = self.parse_expression() + self.expect(TokenType.SEMICOLON) + return ReturnStmt(expr) + + def parse_expression(self) -> Expression: + return self.parse_assignment() + + def parse_assignment(self) -> Expression: + expr = self.parse_comparison() + + if self.current().type == TokenType.ASSIGN: + if not isinstance(expr, VarExpr): + self.error("Invalid assignment target") + self.advance() + value = self.parse_assignment() + return AssignExpr(expr.name, value) + + return expr + + def parse_comparison(self) -> Expression: + expr = self.parse_additive() + + while self.current().type in [ + TokenType.EQ, + TokenType.NE, + TokenType.LT, + TokenType.GT, + TokenType.LE, + TokenType.GE, + ]: + op = self.advance().value + right = self.parse_additive() + expr = BinaryOp(op, expr, right) + + return expr + + def parse_additive(self) -> Expression: + expr = self.parse_multiplicative() + + while self.current().type in [TokenType.PLUS, TokenType.MINUS]: + op = self.advance().value + right = self.parse_multiplicative() + expr = BinaryOp(op, expr, right) + + return expr + + def parse_multiplicative(self) -> Expression: + expr = self.parse_unary() + + while self.current().type in [TokenType.STAR, TokenType.SLASH]: + op = self.advance().value + right = self.parse_unary() + expr = BinaryOp(op, expr, right) + + return expr + + def parse_unary(self) -> Expression: + if self.current().type in [TokenType.PLUS, TokenType.MINUS]: + op = self.advance().value + operand = self.parse_unary() + return UnaryOp(op, operand) + + return self.parse_primary() + + def parse_primary(self) -> Expression: + token = self.current() + + if token.type == TokenType.NUMBER: + self.advance() + return NumberExpr(int(token.value)) + + elif token.type == TokenType.IDENTIFIER: + name = self.advance().value + + if self.current().type == TokenType.LPAREN: + # Function call + self.advance() + args = [] + + if self.current().type != TokenType.RPAREN: + args.append(self.parse_expression()) + while self.current().type == TokenType.COMMA: + self.advance() + args.append(self.parse_expression()) + + self.expect(TokenType.RPAREN) + return CallExpr(name, args) + else: + return VarExpr(name) + + elif token.type == TokenType.LPAREN: + self.advance() + expr = self.parse_expression() + self.expect(TokenType.RPAREN) + return expr + + else: + self.error(f"Unexpected token: {token.type.value}") + + +class CodeGenerator: + def __init__(self): + self.output = [] + self.label_counter = 0 + self.string_counter = 0 + self.functions = {} + self.current_function = None + self.local_vars = {} + self.global_vars = {} + self.register_pool = [f"rg{i:x}" for i in range(16)] + self.used_registers = set() + + def new_label(self, prefix: str = "L") -> str: + label = f"{prefix}{self.label_counter}" + self.label_counter += 1 + return label + + def allocate_register(self) -> str: + for reg in self.register_pool: + if reg not in self.used_registers: + self.used_registers.add(reg) + return reg + raise RuntimeError("Out of registers") + + def free_register(self, reg: str): + self.used_registers.discard(reg) + + def emit(self, code: str): + self.output.append(code) + + def generate(self, program: Program) -> str: + # Emit data section + self.emit("// Global variables") + for decl in program.declarations: + if isinstance(decl, VarDecl): + self.global_vars[decl.name] = f"var_{decl.name}" + if decl.init: + if isinstance(decl.init, NumberExpr): + self.emit(f"dw var_{decl.name}: {decl.init.value}") + else: + self.emit(f"dw var_{decl.name}: 0") + else: + self.emit(f"dw var_{decl.name}: 0") + + self.emit("") + self.emit("// Entry point") + self.emit("dw stack_bottom: 0x10000") + self.emit("") + self.emit("init:") + self.emit(" ldw stack_bottom, spr") + self.emit(" mov spr, bpr") + + self.emit(" push zero") + self.emit(" call main") + self.emit(" pop rg0") + self.emit(" hlt") + self.emit("") + + # Emit functions + for decl in program.declarations: + if isinstance(decl, FunctionDecl): + self.generate_function(decl) + + return "\n".join(self.output) + + def generate_function(self, func: FunctionDecl): + self.current_function = func.name + self.functions[func.name] = func + self.local_vars = {} + + # Map parameters to stack offsets + # Parameters start at bpr+8 (after return addr at bpr+4) + for i, param in enumerate(func.params): + self.local_vars[param] = 8 + (i * 4) + + self.emit(f"{func.name}:") + self.emit(" push bpr") + self.emit(" mov spr, bpr") + self.emit("") + + # Generate function body + self.generate_compound_stmt(func.body) + + # Default return if no explicit return + self.emit("// default return") + self.emit(f"{func.name}_end:") + self.emit(" mov bpr, spr") + self.emit(" pop bpr") + self.emit(" return") + self.emit("") + + def generate_compound_stmt(self, stmt: CompoundStmt): + for s in stmt.statements: + self.generate_statement(s) + + def generate_statement(self, stmt: Statement): + if isinstance(stmt, CompoundStmt): + self.generate_compound_stmt(stmt) + elif isinstance(stmt, ExprStmt): + if stmt.expr: + reg = self.generate_expression(stmt.expr) + self.free_register(reg) + elif isinstance(stmt, IfStmt): + self.generate_if_stmt(stmt) + elif isinstance(stmt, WhileStmt): + self.generate_while_stmt(stmt) + elif isinstance(stmt, ReturnStmt): + self.generate_return_stmt(stmt) + + def generate_if_stmt(self, stmt: IfStmt): + else_label = self.new_label("else") + end_label = self.new_label("endif") + + # Evaluate condition + cond_reg = self.generate_expression(stmt.condition) + self.emit(f" cmp {cond_reg}, zero") + self.free_register(cond_reg) + + if stmt.else_stmt: + self.emit(f" jeq {else_label}") + else: + self.emit(f" jeq {end_label}") + + # Then branch + self.generate_statement(stmt.then_stmt) + + if stmt.else_stmt: + self.emit(f" jmp {end_label}") + self.emit(f"{else_label}:") + self.generate_statement(stmt.else_stmt) + + self.emit(f"{end_label}:") + + def generate_while_stmt(self, stmt: WhileStmt): + start_label = self.new_label("while_start") + end_label = self.new_label("while_end") + + self.emit(f"{start_label}:") + + # Evaluate condition + cond_reg = self.generate_expression(stmt.condition) + self.emit(f" cmp {cond_reg}, zero") + self.free_register(cond_reg) + self.emit(f" jeq {end_label}") + + # Loop body + self.generate_statement(stmt.body) + self.emit(f" jmp {start_label}") + + self.emit(f"{end_label}:") + + def generate_return_stmt(self, stmt: ReturnStmt): + if stmt.expr: + reg = self.generate_expression(stmt.expr) + # Store return value at spr+8 according to calling convention + self.emit(f" stw {reg}, spr, 8") + self.free_register(reg) + self.emit(f" jmp {self.current_function}_end") + + def generate_expression(self, expr: Expression) -> str: + if isinstance(expr, NumberExpr): + reg = self.allocate_register() + if expr.value <= 0xFFFF and expr.value >= 0: + self.emit(f" lli {expr.value}, {reg}") + if expr.value > 0xFF: + self.emit(f" lui {expr.value >> 16}, {reg}") + else: + self.emit(f" lli {expr.value & 0xFFFF}, {reg}") + self.emit(f" lui {(expr.value >> 16) & 0xFFFF}, {reg}") + return reg + + elif isinstance(expr, VarExpr): + reg = self.allocate_register() + if expr.name in self.local_vars: + offset = self.local_vars[expr.name] + self.emit(f" ldw bpr, {reg}, {offset}") + elif expr.name in self.global_vars: + label = self.global_vars[expr.name] + self.emit(f" ldw {label}, {reg}") + else: + raise RuntimeError(f"Undefined variable: {expr.name}") + return reg + + elif isinstance(expr, AssignExpr): + value_reg = self.generate_expression(expr.value) + + if expr.name in self.local_vars: + offset = self.local_vars[expr.name] + self.emit(f" stw {value_reg}, bpr, {offset}") + elif expr.name in self.global_vars: + label = self.global_vars[expr.name] + self.emit(f" stw {value_reg}, {label}") + else: + # New local variable - allocate after params and return value space + # Start local variables at offset -4 from bpr (growing downward) + offset = -(len([v for v in self.local_vars.values() if v < 0]) + 1) * 4 + self.local_vars[expr.name] = offset + self.emit(f" stw {value_reg}, bpr, {offset}") + + return value_reg + + elif isinstance(expr, BinaryOp): + return self.generate_binary_op(expr) + + elif isinstance(expr, UnaryOp): + operand_reg = self.generate_expression(expr.operand) + result_reg = self.allocate_register() + + if expr.op == "-": + self.emit(f" lwi 0, {result_reg}") + self.emit(f" sub {result_reg}, {operand_reg}, {result_reg}") + else: # + + self.emit(f" mov {operand_reg}, {result_reg}") + + self.free_register(operand_reg) + return result_reg + + elif isinstance(expr, CallExpr): + # First, make space for return value (must be pushed BEFORE arguments) + temp_reg = self.allocate_register() + + # Then push arguments in reverse order + arg_regs = [] + for arg in reversed(expr.args): + reg = self.generate_expression(arg) + self.emit(f" push {reg}") + arg_regs.append(reg) + + # Call function + self.emit(f" call {expr.name}") + + # Get return value (it's now on top of stack) + self.emit(f" pop {temp_reg}") + + # Clean up remaining args + for i in range(len(arg_regs) - 1): + self.emit(f" pop zero") + + # Free the arg registers + for reg in arg_regs: + self.free_register(reg) + + return temp_reg + + else: + raise RuntimeError(f"Unknown expression type: {type(expr)}") + + def generate_binary_op(self, expr: BinaryOp) -> str: + # For operations that might contain function calls, we need to be careful + # about register allocation. Evaluate left, save it, evaluate right. + left_reg = self.generate_expression(expr.left) + + # If right side contains a function call, we need to save left_reg + # For now, always save to be safe + saved_reg = self.allocate_register() + self.emit(f" mov {left_reg}, {saved_reg}") + self.free_register(left_reg) + + right_reg = self.generate_expression(expr.right) + result_reg = self.allocate_register() + + if expr.op == "+": + self.emit(f" add {left_reg}, {right_reg}, {result_reg}") + elif expr.op == "-": + self.emit(f" sub {left_reg}, {right_reg}, {result_reg}") + elif expr.op == "*": + # Simple multiplication using loop + temp_label = self.new_label("mult") + end_label = self.new_label("mult_end") + self.emit(f" lli 0, {result_reg}") + self.emit(f"{temp_label}:") + self.emit(f" cmp {right_reg}, zero") + self.emit(f" jeq {end_label}") + self.emit(f" add {result_reg}, {left_reg}, {result_reg}") + self.emit(f" dec {right_reg}") + self.emit(f" jmp {temp_label}") + self.emit(f"{end_label}:") + elif expr.op == "/": + # Simple division using loop + temp_label = self.new_label("div") + end_label = self.new_label("div_end") + self.emit(f" lli 0, {result_reg}") + self.emit(f"{temp_label}:") + self.emit(f" cmp {left_reg}, {right_reg}") + self.emit(f" jlt {end_label}") + self.emit(f" sub {left_reg}, {right_reg}, {left_reg}") + self.emit(f" inc {result_reg}") + self.emit(f" jmp {temp_label}") + self.emit(f"{end_label}:") + elif expr.op in ["==", "!=", "<", ">", "<=", ">="]: + self.emit(f" cmp {left_reg}, {right_reg}") + + # Result is 1 if condition true, 0 otherwise + self.emit(f" lli 0, {result_reg}") + true_label = self.new_label("cmp_true") + end_label = self.new_label("cmp_end") + + if expr.op == "==": + self.emit(f" jeq {true_label}") + elif expr.op == "!=": + self.emit(f" jne {true_label}") + elif expr.op == "<": + self.emit(f" jlt {true_label}") + elif expr.op == ">": + self.emit(f" jgt {true_label}") + elif expr.op == "<=": + self.emit(f" jle {true_label}") + elif expr.op == ">=": + self.emit(f" jge {true_label}") + + self.emit(f" jmp {end_label}") + self.emit(f"{true_label}:") + self.emit(f" lli 1, {result_reg}") + self.emit(f"{end_label}:") + + self.free_register(left_reg) + self.free_register(right_reg) + return result_reg + + +def compile_c_to_asm(source: str) -> str: + """Compile C source code to DSA assembly.""" + lexer = Lexer(source) + tokens = lexer.tokenize() + + parser = Parser(tokens) + ast = parser.parse() + + codegen = CodeGenerator() + assembly = codegen.generate(ast) + + return assembly + + +def main(): + if len(sys.argv) < 2: + print("Usage: python compiler.py [output.dsa]") + sys.exit(1) + + input_file = sys.argv[1] + output_file = sys.argv[2] if len(sys.argv) > 2 else input_file.replace(".c", ".dsa") + + with open(input_file, "r") as f: + source = f.read() + + try: + assembly = compile_c_to_asm(source) + + with open(output_file, "w") as f: + f.write(assembly) + + print(f"Successfully compiled {input_file} to {output_file}") + except (SyntaxError, RuntimeError) as e: + print(f"Compilation error: {e}") + sys.exit(1) + + +if __name__ == "__main__": + main() + # # Example usage + # if len(sys.argv) > 1: + # example_c = sys.argv[1] + + # else: + # example_c = """ + # int factorial(int n) { + # if (n <= 1) { + # return 1; + # } + # return n * factorial(n - 1); + # } + + # int main() { + # int result; + # result = factorial(5); + # return result; + # } + # """ + + # print("Example C program:") + # print(example_c) + # print("\n" + "="*60 + "\n") + # print("Generated DSA assembly:") + # print(compile_c_to_asm(example_c)) diff --git a/c_compiler/src/codegen.rs b/c_compiler/src/codegen.rs new file mode 100644 index 0000000..06e030d --- /dev/null +++ b/c_compiler/src/codegen.rs @@ -0,0 +1,13 @@ +use crate::parser::Program; + +pub struct CodeGenerator; + +impl CodeGenerator { + pub fn new(ast: Program) -> Self { + CodeGenerator {} + } + + pub fn run(&mut self) -> Result { + Ok(String::new()) + } +} diff --git a/c_compiler/src/lexer.rs b/c_compiler/src/lexer.rs new file mode 100644 index 0000000..f6ccc74 --- /dev/null +++ b/c_compiler/src/lexer.rs @@ -0,0 +1,265 @@ +// ============================================================================ +// Token Types +// ============================================================================ + +#[derive(Debug, Clone, PartialEq)] +pub enum TokenType { + // Keywords + Int, + If, + Else, + While, + Return, + + // Identifiers and literals + Identifier(String), + Number(i32), + + // Operators + Plus, + Minus, + Star, + Slash, + Assign, + Eq, + Ne, + Lt, + Gt, + Le, + Ge, + + // Delimiters + LParen, + RParen, + LBrace, + RBrace, + Semicolon, + Comma, + + Eof, +} + +#[derive(Debug, Clone)] +pub struct Token { + pub token_type: TokenType, + pub line: usize, + pub col: usize, +} + +impl Token { + pub fn new(token_type: TokenType, line: usize, col: usize) -> Self { + Self { + token_type, + line, + col, + } + } +} + +// ============================================================================ +// Lexer +// ============================================================================ + +pub struct Lexer { + source: Vec, + pos: usize, + line: usize, + col: usize, +} + +impl Lexer { + pub fn new(source: &str) -> Self { + Self { + source: source.chars().collect(), + pos: 0, + line: 1, + col: 1, + } + } + + fn error(&self, msg: &str) -> String { + format!( + "Lexer error at line {}, col {}: {}", + self.line, self.col, msg + ) + } + + fn peek(&self, offset: usize) -> Option { + self.source.get(self.pos + offset).copied() + } + + fn advance(&mut self) -> Option { + if self.pos >= self.source.len() { + return None; + } + let ch = self.source[self.pos]; + self.pos += 1; + if ch == '\n' { + self.line += 1; + self.col = 1; + } else { + self.col += 1; + } + Some(ch) + } + + fn skip_whitespace(&mut self) { + while let Some(ch) = self.peek(0) { + if ch.is_whitespace() { + self.advance(); + } else { + break; + } + } + } + + fn skip_comment(&mut self) { + if self.peek(0) == Some('/') && self.peek(1) == Some('/') { + while let Some(ch) = self.peek(0) { + if ch == '\n' { + break; + } + self.advance(); + } + } + } + + fn read_number(&mut self) -> i32 { + let mut num_str = String::new(); + while let Some(ch) = self.peek(0) { + if ch.is_ascii_digit() { + num_str.push(ch); + self.advance(); + } else { + break; + } + } + num_str.parse().unwrap_or(0) + } + + fn read_identifier(&mut self) -> String { + let mut ident = String::new(); + while let Some(ch) = self.peek(0) { + if ch.is_alphanumeric() || ch == '_' { + ident.push(ch); + self.advance(); + } else { + break; + } + } + ident + } + + pub fn tokenize(&mut self) -> Result, String> { + let mut tokens = Vec::new(); + + loop { + self.skip_whitespace(); + self.skip_comment(); + + if self.pos >= self.source.len() { + break; + } + + let line = self.line; + let col = self.col; + let ch = self.peek(0).unwrap(); + + let token_type = if ch.is_ascii_digit() { + let num = self.read_number(); + TokenType::Number(num) + } else if ch.is_alphabetic() || ch == '_' { + let ident = self.read_identifier(); + match ident.as_str() { + "int" => TokenType::Int, + "if" => TokenType::If, + "else" => TokenType::Else, + "while" => TokenType::While, + "return" => TokenType::Return, + _ => TokenType::Identifier(ident), + } + } else { + match ch { + '=' if self.peek(1) == Some('=') => { + self.advance(); + self.advance(); + TokenType::Eq + } + '!' if self.peek(1) == Some('=') => { + self.advance(); + self.advance(); + TokenType::Ne + } + '<' if self.peek(1) == Some('=') => { + self.advance(); + self.advance(); + TokenType::Le + } + '>' if self.peek(1) == Some('=') => { + self.advance(); + self.advance(); + TokenType::Ge + } + '+' => { + self.advance(); + TokenType::Plus + } + '-' => { + self.advance(); + TokenType::Minus + } + '*' => { + self.advance(); + TokenType::Star + } + '/' => { + self.advance(); + TokenType::Slash + } + '=' => { + self.advance(); + TokenType::Assign + } + '<' => { + self.advance(); + TokenType::Lt + } + '>' => { + self.advance(); + TokenType::Gt + } + '(' => { + self.advance(); + TokenType::LParen + } + ')' => { + self.advance(); + TokenType::RParen + } + '{' => { + self.advance(); + TokenType::LBrace + } + '}' => { + self.advance(); + TokenType::RBrace + } + ';' => { + self.advance(); + TokenType::Semicolon + } + ',' => { + self.advance(); + TokenType::Comma + } + _ => return Err(self.error(&format!("Unexpected character: {}", ch))), + } + }; + + tokens.push(Token::new(token_type, line, col)); + } + + tokens.push(Token::new(TokenType::Eof, self.line, self.col)); + Ok(tokens) + } +} diff --git a/c_compiler/src/main.rs b/c_compiler/src/main.rs new file mode 100644 index 0000000..cd52c21 --- /dev/null +++ b/c_compiler/src/main.rs @@ -0,0 +1,72 @@ +use std::fmt; + +use crate::{codegen::CodeGenerator, lexer::Lexer, parser::Parser}; + +pub mod codegen; +pub mod lexer; +pub mod parser; + +// ============================================================================ +// Main & Tests +// ============================================================================ + +fn main() { + // read from input file: syntax "c_compiler [output.dsa]" + let args: Vec = std::env::args().collect(); + if args.len() < 2 { + eprintln!("Usage: c_compiler [output.dsa]"); + return; + } + + let input_file = &args[1]; + let output_file = if args.len() > 2 { + &args[2] + } else { + "output.dsa" + }; + + // read input + let input = std::fs::read_to_string(input_file).expect("Failed to read input file"); + + // Lexing + let mut lexer = Lexer::new(&input); + let tokens = match lexer.tokenize() { + Ok(tokens) => tokens, + Err(e) => { + eprintln!("Lexing error: {}", e); + return; + } + }; + + println!("Tokens:"); + for token in &tokens { + println!(" {:?}", token.token_type); + } + println!(); + + // Parsing + let mut parser = Parser::new(tokens); + let ast = match parser.parse() { + Ok(ast) => ast, + Err(e) => { + eprintln!("Parsing error: {}", e); + return; + } + }; + + println!("AST:"); + println!("{:#?}", ast); + + // Code Gen + let mut generator = CodeGenerator::new(ast); + let result = match generator.run() { + Ok(code) => code, + Err(e) => { + eprintln!("Parsing error: {}", e); + return; + } + }; + + println!("CODE:"); + println!("{:#?}", result); +} diff --git a/c_compiler/src/parser.rs b/c_compiler/src/parser.rs new file mode 100644 index 0000000..1ae587a --- /dev/null +++ b/c_compiler/src/parser.rs @@ -0,0 +1,558 @@ +// ============================================================================ +// AST Node Types +// ============================================================================ + +use std::fmt; + +use crate::lexer::{Token, TokenType}; + +#[derive(Debug, Clone)] +pub struct Program { + pub declarations: Vec, +} + +#[derive(Debug, Clone)] +pub enum Declaration { + Function { + name: String, + return_type: Type, + params: Vec, + body: Statement, + }, + Variable { + name: String, + init: Option, + }, +} + +#[derive(Debug, Clone)] +pub struct Parameter { + pub name: String, + pub param_type: Type, +} + +#[derive(Debug, Clone)] +pub enum Type { + Int, + Long, + Float, + Double, + Char, + Void, + Ptr(Box), + Array(Box, usize), + Struct(String), +} + +#[derive(Debug, Clone)] +pub enum Statement { + Compound { + statements: Vec, + }, + Assign { + // left side + name: String, + declare_type: Option, + + // right side + value: Option>, + }, + Expression { + expr: Expression, + }, + If { + condition: Expression, + then_stmt: Box, + else_stmt: Option>, + }, + While { + condition: Expression, + body: Box, + }, + Return { + expr: Option, + }, +} + +#[derive(Debug, Clone)] +pub enum Expression { + Empty, + Binary { + op: BinaryOperator, + left: Box, + right: Box, + }, + Unary { + op: UnaryOperator, + operand: Box, + }, + Variable { + name: String, + expr_type: Option, + }, + Number { + value: i32, + }, + Call { + name: String, + args: Vec, + }, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum BinaryOperator { + Add, + Sub, + Mul, + Div, + Eq, + Ne, + Lt, + Gt, + Le, + Ge, +} + +impl fmt::Display for BinaryOperator { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + BinaryOperator::Add => write!(f, "+"), + BinaryOperator::Sub => write!(f, "-"), + BinaryOperator::Mul => write!(f, "*"), + BinaryOperator::Div => write!(f, "/"), + BinaryOperator::Eq => write!(f, "=="), + BinaryOperator::Ne => write!(f, "!="), + BinaryOperator::Lt => write!(f, "<"), + BinaryOperator::Gt => write!(f, ">"), + BinaryOperator::Le => write!(f, "<="), + BinaryOperator::Ge => write!(f, ">="), + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub enum UnaryOperator { + Plus, + Minus, +} + +impl fmt::Display for UnaryOperator { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + UnaryOperator::Plus => write!(f, "+"), + UnaryOperator::Minus => write!(f, "-"), + } + } +} + +// ============================================================================ +// Parser +// ============================================================================ + +pub struct Parser { + tokens: Vec, + pos: usize, +} + +impl Parser { + pub fn new(tokens: Vec) -> Self { + Self { tokens, pos: 0 } + } + + fn error(&self, msg: &str) -> String { + let token = self.current(); + format!( + "Parser error at line {}, col {}: {}", + token.line, token.col, msg + ) + } + + fn current(&self) -> &Token { + self.tokens + .get(self.pos) + .unwrap_or_else(|| self.tokens.last().unwrap()) + } + + fn peek(&self, offset: usize) -> &Token { + self.tokens + .get(self.pos + offset) + .unwrap_or_else(|| self.tokens.last().unwrap()) + } + + fn advance(&mut self) -> &Token { + if self.pos < self.tokens.len() - 1 { + self.pos += 1; + } + self.current() + } + + fn expect(&mut self, expected: TokenType) -> Result { + let token = self.current().clone(); + if std::mem::discriminant(&token.token_type) != std::mem::discriminant(&expected) + { + return Err(self.error(&format!( + "Expected {:?}, got {:?}", + expected, token.token_type + ))); + } + self.advance(); + Ok(token) + } + + pub fn parse(&mut self) -> Result { + let mut declarations = Vec::new(); + + while !matches!(self.current().token_type, TokenType::Eof) { + declarations.push(self.parse_declaration()?); + } + + Ok(Program { declarations }) + } + + fn parse_declaration(&mut self) -> Result { + self.expect(TokenType::Int)?; + + let name = match &self.current().token_type { + TokenType::Identifier(s) => s.clone(), + _ => return Err(self.error("Expected identifier")), + }; + self.advance(); + + match &self.current().token_type { + TokenType::LParen => { + // Function declaration + self.advance(); + let mut params = Vec::::new(); + + if !matches!(self.current().token_type, TokenType::RParen) { + self.expect(TokenType::Int)?; + + match &self.current().token_type { + TokenType::Identifier(s) => { + params.push(Parameter { + name: s.clone(), + param_type: Type::Int, + }); + self.advance(); + } + _ => return Err(self.error("Expected parameter name")), + } + + while matches!(self.current().token_type, TokenType::Comma) { + self.advance(); + self.expect(TokenType::Int)?; + + match &self.current().token_type { + TokenType::Identifier(s) => { + params.push(Parameter { + name: s.clone(), + param_type: Type::Int, + }); + self.advance(); + } + _ => return Err(self.error("Expected parameter name")), + } + } + } + + self.expect(TokenType::RParen)?; + let body = self.parse_compound_stmt()?; + + Ok(Declaration::Function { + name, + params, + body, + return_type: Type::Int, + }) + } + _ => { + // Variable declaration + let init = if matches!(self.current().token_type, TokenType::Assign) { + self.advance(); + Some(self.parse_expression()?) + } else { + None + }; + + self.expect(TokenType::Semicolon)?; + Ok(Declaration::Variable { name, init }) + } + } + } + + fn parse_compound_stmt(&mut self) -> Result { + self.expect(TokenType::LBrace)?; + let mut statements = Vec::new(); + + while !matches!(self.current().token_type, TokenType::RBrace) { + statements.push(self.parse_statement()?); + } + + self.expect(TokenType::RBrace)?; + Ok(Statement::Compound { statements }) + } + + fn parse_statement(&mut self) -> Result { + match &self.current().token_type { + TokenType::LBrace => Ok(self.parse_compound_stmt()?), + TokenType::If => self.parse_if_stmt(), + TokenType::While => self.parse_while_stmt(), + TokenType::Return => self.parse_return_stmt(), + TokenType::Identifier(name) => { + let name = name.clone(); + + // peek ahead for open paren (func call expr) + if matches!(self.peek(1).token_type, TokenType::LParen) { + let expr = self.parse_expression()?; // a function call expr + self.expect(TokenType::Semicolon)?; + return Ok(Statement::Expression { expr }); + } + + self.advance(); // advance past identifier + + // assignment expression + if matches!(self.current().token_type, TokenType::Assign) { + self.advance(); + let expr = self.parse_expression()?; + + self.expect(TokenType::Semicolon)?; + Ok(Statement::Assign { + name, + value: Some(Box::new(expr)), + declare_type: None, + }) + } + // var expression + else { + self.expect(TokenType::Semicolon)?; + Ok(Statement::Expression { + expr: Expression::Variable { + name, + expr_type: None, + }, + }) + } + } + TokenType::Int => { + // Local variable declaration + self.advance(); + let name = match &self.current().token_type { + TokenType::Identifier(s) => s.clone(), + _ => return Err(self.error("Expected variable name")), + }; + self.advance(); + + let init = if matches!(self.current().token_type, TokenType::Assign) { + self.advance(); + Some(self.parse_expression()?) + } else { + None + }; + + self.expect(TokenType::Semicolon)?; + + // Convert to assignment expression statement + let expr = if let Some(init_expr) = init { + Statement::Assign { + name, + value: Some(Box::new(init_expr)), + declare_type: Some(Type::Int), + } + } else { + Statement::Assign { + name, + value: None, + declare_type: Some(Type::Int), + } + }; + + Ok(expr) + } + _ => { + let expr = if matches!(self.current().token_type, TokenType::Semicolon) { + Expression::Empty + } else { + self.parse_expression()? + }; + + self.expect(TokenType::Semicolon)?; + Ok(Statement::Expression { expr }) + } + } + } + + fn parse_if_stmt(&mut self) -> Result { + self.expect(TokenType::If)?; + self.expect(TokenType::LParen)?; + let condition = self.parse_expression()?; + self.expect(TokenType::RParen)?; + let then_stmt = Box::new(self.parse_statement()?); + + let else_stmt = if matches!(self.current().token_type, TokenType::Else) { + self.advance(); + Some(Box::new(self.parse_statement()?)) + } else { + None + }; + + Ok(Statement::If { + condition, + then_stmt, + else_stmt, + }) + } + + fn parse_while_stmt(&mut self) -> Result { + self.expect(TokenType::While)?; + self.expect(TokenType::LParen)?; + let condition = self.parse_expression()?; + self.expect(TokenType::RParen)?; + let body = Box::new(self.parse_statement()?); + + Ok(Statement::While { condition, body }) + } + + fn parse_return_stmt(&mut self) -> Result { + self.expect(TokenType::Return)?; + + let expr = if matches!(self.current().token_type, TokenType::Semicolon) { + None + } else { + Some(self.parse_expression()?) + }; + + self.expect(TokenType::Semicolon)?; + Ok(Statement::Return { expr }) + } + + fn parse_expression(&mut self) -> Result { + self.parse_comparison() + } + + fn parse_comparison(&mut self) -> Result { + let mut expr = self.parse_additive()?; + + while let Some(op) = match &self.current().token_type { + TokenType::Eq => Some(BinaryOperator::Eq), + TokenType::Ne => Some(BinaryOperator::Ne), + TokenType::Lt => Some(BinaryOperator::Lt), + TokenType::Gt => Some(BinaryOperator::Gt), + TokenType::Le => Some(BinaryOperator::Le), + TokenType::Ge => Some(BinaryOperator::Ge), + _ => None, + } { + self.advance(); + let right = Box::new(self.parse_additive()?); + expr = Expression::Binary { + op, + left: Box::new(expr), + right, + }; + } + + Ok(expr) + } + + fn parse_additive(&mut self) -> Result { + let mut expr = self.parse_multiplicative()?; + + while let Some(op) = match &self.current().token_type { + TokenType::Plus => Some(BinaryOperator::Add), + TokenType::Minus => Some(BinaryOperator::Sub), + _ => None, + } { + self.advance(); + let right = Box::new(self.parse_multiplicative()?); + expr = Expression::Binary { + op, + left: Box::new(expr), + right, + }; + } + + Ok(expr) + } + + fn parse_multiplicative(&mut self) -> Result { + let mut expr = self.parse_unary()?; + + while let Some(op) = match &self.current().token_type { + TokenType::Star => Some(BinaryOperator::Mul), + TokenType::Slash => Some(BinaryOperator::Div), + _ => None, + } { + self.advance(); + let right = Box::new(self.parse_unary()?); + expr = Expression::Binary { + op, + left: Box::new(expr), + right, + }; + } + + Ok(expr) + } + + fn parse_unary(&mut self) -> Result { + let op = match &self.current().token_type { + TokenType::Plus => Some(UnaryOperator::Plus), + TokenType::Minus => Some(UnaryOperator::Minus), + _ => None, + }; + + if let Some(op) = op { + self.advance(); + let operand = Box::new(self.parse_unary()?); + return Ok(Expression::Unary { op, operand }); + } + + self.parse_primary() + } + + fn parse_primary(&mut self) -> Result { + match &self.current().token_type.clone() { + TokenType::Number(n) => { + let value = *n; + self.advance(); + Ok(Expression::Number { value }) + } + TokenType::Identifier(name) => { + let name = name.clone(); + self.advance(); + + if matches!(self.current().token_type, TokenType::LParen) { + // Function call + self.advance(); + let mut args = Vec::new(); + + if !matches!(self.current().token_type, TokenType::RParen) { + args.push(self.parse_expression()?); + + while matches!(self.current().token_type, TokenType::Comma) { + self.advance(); + args.push(self.parse_expression()?); + } + } + + self.expect(TokenType::RParen)?; + Ok(Expression::Call { name, args }) + } else { + Ok(Expression::Variable { + name, + expr_type: None, + }) + } + } + TokenType::LParen => { + self.advance(); + let expr = self.parse_expression()?; + self.expect(TokenType::RParen)?; + Ok(expr) + } + _ => Err(self.error(&format!( + "Unexpected token: {:?}", + self.current().token_type + ))), + } + } +} -- 2.47.3 From b9f98bff7bd0932cedca7ce17303670271e3ac68 Mon Sep 17 00:00:00 2001 From: zxq5 Date: Sat, 15 Nov 2025 02:58:36 +0000 Subject: [PATCH 04/53] started on codegen, scaffolding basically done --- c_compiler/Cargo.toml | 1 + c_compiler/code.c | 6 +- c_compiler/output.dsa | 5 + c_compiler/src/codegen.rs | 190 +++++++++++++++++++++++++++++++++++++- c_compiler/src/main.rs | 6 +- c_compiler/src/parser.rs | 26 +++++- 6 files changed, 222 insertions(+), 12 deletions(-) create mode 100644 c_compiler/output.dsa diff --git a/c_compiler/Cargo.toml b/c_compiler/Cargo.toml index 787ae6a..ef733ea 100644 --- a/c_compiler/Cargo.toml +++ b/c_compiler/Cargo.toml @@ -5,3 +5,4 @@ edition.workspace = true authors.workspace = true [dependencies] +chrono = "0.4.42" diff --git a/c_compiler/code.c b/c_compiler/code.c index cc43221..5840a39 100644 --- a/c_compiler/code.c +++ b/c_compiler/code.c @@ -1,3 +1,7 @@ +int x = 5; + +int add(int a, int b) { return a + b; } + int factorial(int n) { if (n <= 1) { return 1; @@ -5,8 +9,6 @@ int factorial(int n) { return n * factorial(n - 1); } -int add(int a, int b) { return a + b; } - int main() { int x; x = 5; diff --git a/c_compiler/output.dsa b/c_compiler/output.dsa new file mode 100644 index 0000000..54e84c0 --- /dev/null +++ b/c_compiler/output.dsa @@ -0,0 +1,5 @@ +// Imports +include maths: "./lib/maths/core.dsa" + +// Reserved Memory + diff --git a/c_compiler/src/codegen.rs b/c_compiler/src/codegen.rs index 06e030d..201ad29 100644 --- a/c_compiler/src/codegen.rs +++ b/c_compiler/src/codegen.rs @@ -1,13 +1,193 @@ -use crate::parser::Program; +use std::time::SystemTime; +use std::{collections::HashMap, path::PathBuf}; -pub struct CodeGenerator; +use chrono::{DateTime, Local}; + +use crate::{block, cmd, comment, dsa}; + +use crate::parser::{ConstExpr, Declaration, Program}; + +pub struct CodeGenerator { + ast: Program, + imports: HashMap, + globals: Vec, + functions: Vec, +} + +fn import(name: &str, path: &str) -> String { + format!("include {name}: \"{}\"", path) +} impl CodeGenerator { pub fn new(ast: Program) -> Self { - CodeGenerator {} + CodeGenerator { + ast, + imports: HashMap::new(), + globals: Vec::new(), + functions: Vec::new(), + } } - pub fn run(&mut self) -> Result { - Ok(String::new()) + pub fn include(&mut self, name: &str, path: &str) { + self.imports.insert(name.to_string(), path.to_string()); + } + + pub fn generate(&mut self) -> Result { + // always include the print library for debugging! + self.include("print", "./lib/io/print.dsa"); + + for block in self.ast.clone().declarations { + self.generate_block(block.clone()); + } + + self.generate_layout() + } + + fn generate_block(&mut self, block: Declaration) { + match block { + Declaration::Variable { name, init } => self.globals.push(format!( + "dw {}: {}", + name, + init.unwrap_or(ConstExpr::Number(0)) + )), + Declaration::Function { + name, + return_type, + params, + body, + } => { + let function_start = format!( + "{name}: \n\t\ + push bpr \n\t\ + mov spr, bpr" + ); + + let function_end = format!( + "\n\t\ + mov bpr, spr \n\t\ + pop bpr \n\t\ + return\n" + ); + + self.functions + .push(format!("{function_start}\n{function_end}")); + } + } + } + + fn generate_layout(&mut self) -> Result { + let datetime: DateTime = SystemTime::now().into(); + Ok(dsa![ + "", + comment!("GENERATED BY DSA-C COMPILER"), + comment!(format!( + "Generated at {}", + datetime.format("%Y-%m-%d %H:%M:%S") + )), + "", + // imports + comment!("Imports"), + self.imports + .iter() + .map(|(k, v)| import(k, v)) + .collect::>() + .join("\n"), + "", + // reserved memory + comment!("Globals & Reserved Memory"), + self.globals.join("\n"), + "", + // entry point + comment!("Entry Point"), + "dw stack: 0x10000", + "db message: \"Process Exited with code:\"", + block! [ "_init" + dsa![ldw stack, bpr], + dsa![mov bpr, spr], + dsa![push zero], + dsa![call main], + dsa![lwi message, rg0], + dsa![push rg0], + dsa![call print::print], + dsa![pop zero], + dsa![call print::print_hex_word], + dsa![pop zero], + dsa![hlt] + ], + block! [ "main" + dsa![push bpr], + dsa![mov spr, bpr], + dsa![lwi 67, rg1], + dsa![stw rg1, spr, 8], + dsa![mov bpr, spr], + dsa![pop bpr], + dsa![return] + ], + "", + self.functions.join("\n"), + ]) } } + +/// Build a single string from any number of arguments. +/// Each argument must implement `Display` or be convertible to a string. +#[macro_export] +macro_rules! dsa { + ($($arg:expr),* $(,)?) => {{ + // Start with an empty String – we’ll grow it as we go. + use std::fmt::Write; + let mut s = ::std::string::String::new(); + $( + // `write!` is cheaper than `format!` for each element + // because it re‑uses the same buffer. + + write!(s, "{}\n", $arg).expect("write to String failed"); + )* + s + }}; +} + +// ──────────────────────── dsa! ──────────────────────── +// A tiny helper that just turns its token‑stream into a string. +// The trailing comma is kept – it’s part of the syntax you want. +#[macro_export] +macro_rules! cmd { + ($($tokens:tt)*) => {{ + // We’ll just stringify the tokens and return a String. + format!("{}", concat!(stringify!($tokens), "\n")) + }}; +} + +// ──────────────────────── block! ──────────────────────── +// Usage: +// +// let asm = block![ "name" +// dsa![mov rg0, rg1], +// dsa![add rg1, rg1] +// ]; +// +// `asm` is a `&'static str` containing: +// +// name: +// mov rg0, rg1 +// add rg1, rg1 +// +#[macro_export] +macro_rules! block { + // The first token must be a string literal – that’s the label. + ($label:literal $(dsa![$($ins:tt)*]),* ) => {{ + // Build a single string at compile time. + const CODE: &str = concat!( + $label, ":\n", + // Each `dsa!` call yields a string like `"mov rg0, rg1"`. + // We add a newline after each one to get the desired layout. + $(concat!("\t", stringify!($($ins)*), "\n")),* + ); + CODE + }}; +} + +#[macro_export] +macro_rules! comment { + ($text:expr) => {{ format!("// {}", $text) }}; +} diff --git a/c_compiler/src/main.rs b/c_compiler/src/main.rs index cd52c21..812d1ba 100644 --- a/c_compiler/src/main.rs +++ b/c_compiler/src/main.rs @@ -59,7 +59,7 @@ fn main() { // Code Gen let mut generator = CodeGenerator::new(ast); - let result = match generator.run() { + let result = match generator.generate() { Ok(code) => code, Err(e) => { eprintln!("Parsing error: {}", e); @@ -67,6 +67,6 @@ fn main() { } }; - println!("CODE:"); - println!("{:#?}", result); + std::fs::write(output_file, &result).expect("Failed to write output"); + println!("Result written to {}", output_file); } diff --git a/c_compiler/src/parser.rs b/c_compiler/src/parser.rs index 1ae587a..b89aaa1 100644 --- a/c_compiler/src/parser.rs +++ b/c_compiler/src/parser.rs @@ -21,7 +21,7 @@ pub enum Declaration { }, Variable { name: String, - init: Option, + init: Option, }, } @@ -74,6 +74,21 @@ pub enum Statement { }, } +#[derive(Debug, Clone)] +pub enum ConstExpr { + Number(i32), + String(String), +} + +impl fmt::Display for ConstExpr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + ConstExpr::Number(n) => write!(f, "{}", n), + ConstExpr::String(s) => write!(f, "\"{}\"", s), + } + } +} + #[derive(Debug, Clone)] pub enum Expression { Empty, @@ -269,7 +284,14 @@ impl Parser { // Variable declaration let init = if matches!(self.current().token_type, TokenType::Assign) { self.advance(); - Some(self.parse_expression()?) + + if let TokenType::Number(n) = self.current().token_type { + self.advance(); + Some(ConstExpr::Number(n)) + } else { + return Err(self + .error("Expected constant in global variable declaration")); + } } else { None }; -- 2.47.3 From 259746558f256fea80fb2652272ebf8af219e9a2 Mon Sep 17 00:00:00 2001 From: zxq5 Date: Thu, 29 Jan 2026 19:29:48 +0000 Subject: [PATCH 05/53] codegen progress --- .gitignore | 4 +- c_compiler/code.c | 11 +- c_compiler/src/codegen.rs | 465 ++++++++++++++++++++++++++++++++---- c_compiler/src/main.rs | 1 + c_compiler/src/parser.rs | 30 +-- c_compiler/src/registers.rs | 324 +++++++++++++++++++++++++ 6 files changed, 767 insertions(+), 68 deletions(-) create mode 100644 c_compiler/src/registers.rs diff --git a/.gitignore b/.gitignore index a851572..b05f24c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ /target -**/*.env \ No newline at end of file +**/*.env +Cargo.lock +*Cargo.lock diff --git a/c_compiler/code.c b/c_compiler/code.c index 5840a39..d069fa2 100644 --- a/c_compiler/code.c +++ b/c_compiler/code.c @@ -1,6 +1,4 @@ -int x = 5; - -int add(int a, int b) { return a + b; } +int var_x = 5; int factorial(int n) { if (n <= 1) { @@ -10,12 +8,7 @@ int factorial(int n) { } int main() { - int x; - x = 5; - int x = 5; - int result; - int result = 5; - result = x + factorial(5); + int result = var_x + factorial(5); print(result); return 0; } diff --git a/c_compiler/src/codegen.rs b/c_compiler/src/codegen.rs index 201ad29..308fe43 100644 --- a/c_compiler/src/codegen.rs +++ b/c_compiler/src/codegen.rs @@ -1,17 +1,25 @@ +use std::hash::Hash; +use std::sync::atomic::AtomicU32; use std::time::SystemTime; use std::{collections::HashMap, path::PathBuf}; use chrono::{DateTime, Local}; +use crate::registers::RegisterAllocator; use crate::{block, cmd, comment, dsa}; -use crate::parser::{ConstExpr, Declaration, Program}; +use crate::parser::{ + BinaryOperator, ConstExpr, Declaration, Expression, Parameter, Program, Statement, + UnaryOperator, +}; pub struct CodeGenerator { ast: Program, imports: HashMap, globals: Vec, functions: Vec, + allocator: RegisterAllocator, + call_stack: Vec, } fn import(name: &str, path: &str) -> String { @@ -25,6 +33,8 @@ impl CodeGenerator { imports: HashMap::new(), globals: Vec::new(), functions: Vec::new(), + allocator: RegisterAllocator::new(), + call_stack: Vec::new(), } } @@ -37,44 +47,16 @@ impl CodeGenerator { self.include("print", "./lib/io/print.dsa"); for block in self.ast.clone().declarations { - self.generate_block(block.clone()); + self.generate_block(block.clone())?; + } + + for func in &self.functions { + println!("{func}"); } self.generate_layout() } - fn generate_block(&mut self, block: Declaration) { - match block { - Declaration::Variable { name, init } => self.globals.push(format!( - "dw {}: {}", - name, - init.unwrap_or(ConstExpr::Number(0)) - )), - Declaration::Function { - name, - return_type, - params, - body, - } => { - let function_start = format!( - "{name}: \n\t\ - push bpr \n\t\ - mov spr, bpr" - ); - - let function_end = format!( - "\n\t\ - mov bpr, spr \n\t\ - pop bpr \n\t\ - return\n" - ); - - self.functions - .push(format!("{function_start}\n{function_end}")); - } - } - } - fn generate_layout(&mut self) -> Result { let datetime: DateTime = SystemTime::now().into(); Ok(dsa![ @@ -114,19 +96,416 @@ impl CodeGenerator { dsa![pop zero], dsa![hlt] ], - block! [ "main" - dsa![push bpr], - dsa![mov spr, bpr], - dsa![lwi 67, rg1], - dsa![stw rg1, spr, 8], - dsa![mov bpr, spr], - dsa![pop bpr], - dsa![return] - ], + // block! [ "main" + // dsa![push bpr], + // dsa![mov spr, bpr], + // dsa![lwi 67, rg1], + // dsa![stw rg1, spr, 8], + // dsa![mov bpr, spr], + // dsa![pop bpr], + // dsa![return] + // ], "", self.functions.join("\n"), ]) } + + fn generate_global(&mut self, name: &str, init: Option) { + self.globals.push(format!( + "dw {}: {}", + name, + init.unwrap_or(ConstExpr::Number(0)) + )) + } + + fn generate_block(&mut self, block: Declaration) -> Result<(), String> { + match block { + Declaration::Variable { name, init } => self.generate_global(&name, init), + Declaration::Function { + name, + return_type, + params, + body, + } => { + let func = self.generate_function(&name, ¶ms, &body).join("\n"); + + self.functions.push(format!("{func}\n")); + } + }; + + Ok(()) + } + + // Example: Generate code for a function + fn generate_function( + &mut self, + name: &str, + params: &[Parameter], + body: &[Statement], + ) -> Vec { + self.call_stack.push(name.to_string()); + + let mut code = Vec::new(); + + // Reset allocator for new function + self.allocator.reset(); + + // Function prologue + code.push(format!("{}:", name)); + code.push("\tpush bpr".to_string()); + code.push("\tmov spr, bpr".to_string()); + + // Allocate parameters to registers or stack locations + for (i, param) in params.iter().enumerate() { + let offset = 8 + (i as i32 * 4); // Parameters start at bpr+8 + // Track that this parameter is at a stack location + let (reg, mut load_code) = self.allocator.alloc_var(¶m.name).unwrap(); + code.extend(load_code); + code.push(format!("\tldw bpr, {}, {}", reg, offset)); + } + + // Generate code for function body + for stmt in body { + let stmt_code = self.generate_statement(stmt).unwrap(); + code.extend(stmt_code); + } + + // Function epilogue + code.push(format!("_ret_{name}:")); + code.push("\tmov bpr, spr".to_string()); + code.push("\tpop bpr".to_string()); + code.push("\treturn".to_string()); + + self.call_stack.pop(); + code + } + + // Example: Generate code for a statement + fn generate_statement(&mut self, stmt: &Statement) -> Result, String> { + let mut code = Vec::new(); + + match stmt { + Statement::Assign { + name, + declare_type, + value, + } => { + if let Some(expr) = value { + // Evaluate expression + let (result_reg, expr_code) = self.generate_expression(expr)?; + code.extend(expr_code); + + // Store result in variable + let store_code = self.allocator.store_var(name, &result_reg); + code.extend(store_code); + + // Free temporary register + self.allocator.free_temp(&result_reg); + } else { + // Just declaring variable without initialization + self.allocator.alloc_var(name)?; + } + } + + Statement::Return { expr } => { + if let Some(e) = expr { + let (result_reg, expr_code) = self.generate_expression(e)?; + code.extend(expr_code); + code.push(format!("\tstw {}, bpr, 8", result_reg)); + code.push(format!("\tjmp _ret_{}", self.call_stack.last().unwrap())); + self.allocator.free_temp(&result_reg); + } + } + + Statement::If { + condition, + then_stmt, + else_stmt, + } => { + // Generate condition + let (cond_reg, cond_code) = self.generate_expression(condition)?; + code.extend(cond_code); + + // Compare with zero + code.push(format!("\tcmp {}, zero", cond_reg)); + self.allocator.free_temp(&cond_reg); + + // Generate unique labels + let then_label = format!("_then_{}", self.get_unique_label()); + let else_label = format!("_else_{}", self.get_unique_label()); + let end_label = format!("_end_{}", self.get_unique_label()); + + // Jump to else if condition is false (equal to zero) + code.push(format!("\tjeq {}", else_label)); + + // Then block + code.push(format!("{}:", then_label)); + for s in then_stmt { + code.extend(self.generate_statement(s)?); + } + + if then_stmt.len() == 0 { + code.push("\tnop".to_string()); + } + + code.push(format!("\tjmp {}", end_label)); + + // Else block + code.push(format!("{}:", else_label)); + for s in else_stmt { + code.extend(self.generate_statement(s)?); + } + + if else_stmt.len() == 0 { + code.push("\tnop".to_string()); + } + + code.push(format!("{}:", end_label)); + } + + Statement::While { condition, body } => { + let loop_start = format!("_while_start_{}", self.get_unique_label()); + let loop_end = format!("_while_end_{}", self.get_unique_label()); + + code.push(format!("{}:", loop_start)); + + // Generate condition + let (cond_reg, cond_code) = self.generate_expression(condition)?; + code.extend(cond_code); + + code.push(format!("\tcmp {}, zero", cond_reg)); + self.allocator.free_temp(&cond_reg); + + code.push(format!("\tjeq {}", loop_end)); + + // Loop body + for s in body { + code.extend(self.generate_statement(s)?); + } + + code.push(format!("\tjmp {}", loop_start)); + code.push(format!("{}:", loop_end)); + } + + Statement::Expression { expr } => { + let (result_reg, expr_code) = self.generate_expression(expr)?; + code.extend(expr_code); + self.allocator.free_temp(&result_reg); + } + + Statement::Block(statements) => { + for s in statements { + code.extend(self.generate_statement(s)?); + } + } + } + + Ok(code) + } + + // Example: Generate code for an expression + // Returns (register containing result, assembly code) + fn generate_expression( + &mut self, + expr: &Expression, + ) -> Result<(String, Vec), String> { + let mut code = Vec::new(); + + match expr { + Expression::Number { value } => { + let (reg, alloc_code) = self.allocator.alloc_temp()?; + code.extend(alloc_code); + + // Load immediate value + code.push(format!("\tlli {}, {}", value & 0xFFFF, reg)); + if *value > 0xFFFF || *value < 0 { + code.push(format!("\tlui {}, {}", (value >> 16) & 0xFFFF, reg)); + } + + Ok((reg, code)) + } + + Expression::Variable { name, .. } => { + let (reg, load_code) = self.allocator.load_var(name)?; + code.extend(load_code); + Ok((reg, code)) + } + + Expression::Binary { op, left, right } => { + // Evaluate left operand + let (left_reg, left_code) = self.generate_expression(left)?; + code.extend(left_code); + + // Evaluate right operand + let (right_reg, right_code) = self.generate_expression(right)?; + code.extend(right_code); + + // Allocate result register + let (result_reg, result_alloc) = self.allocator.alloc_temp()?; + code.extend(result_alloc); + + // Generate operation + match op { + BinaryOperator::Add => { + code.push(format!( + "\tadd {}, {}, {}", + left_reg, right_reg, result_reg + )); + } + BinaryOperator::Sub => { + code.push(format!( + "\tsub {}, {}, {}", + left_reg, right_reg, result_reg + )); + } + BinaryOperator::Mul => { + self.include("maths", "./lib/maths/core.dsa"); + // Call multiply function + code.push(format!("\tpush {}", right_reg)); + code.push(format!("\tpush {}", left_reg)); + code.push("\tcall maths::multiply".to_string()); + code.push(format!("\tpop {}", result_reg)); + code.push("\tpop zero".to_string()); + } + // Comparison operators - return 1 (true) or 0 (false) + BinaryOperator::Eq => { + code.push(format!("\tcmp {}, {}", left_reg, right_reg)); + code.push(format!("\tlli 0, {}", result_reg)); + let end_label = format!("_cmp_end_{}", self.get_unique_label()); + code.push(format!("\tjne {}", end_label)); // If not equal, skip setting to 1 + code.push(format!("\tlli 1, {}", result_reg)); + code.push(format!("{}:", end_label)); + } + BinaryOperator::Ne => { + code.push(format!("\tcmp {}, {}", left_reg, right_reg)); + code.push(format!("\tlli 0, {}", result_reg)); + let end_label = format!("_cmp_end_{}", self.get_unique_label()); + code.push(format!("\tjeq {}", end_label)); // If equal, skip setting to 1 + code.push(format!("\tlli 1, {}", result_reg)); + code.push(format!("{}:", end_label)); + } + BinaryOperator::Lt => { + code.push(format!("\tcmp {}, {}", left_reg, right_reg)); + code.push(format!("\tlli 0, {}", result_reg)); + let end_label = format!("_cmp_end_{}", self.get_unique_label()); + code.push(format!("\tjge {}", end_label)); // If greater or equal, skip setting to 1 + code.push(format!("\tlli 1, {}", result_reg)); + code.push(format!("{}:", end_label)); + } + BinaryOperator::Le => { + code.push(format!("\tcmp {}, {}", left_reg, right_reg)); + code.push(format!("\tlli 0, {}", result_reg)); + let end_label = format!("_cmp_end_{}", self.get_unique_label()); + code.push(format!("\tjgt {}", end_label)); // If greater than, skip setting to 1 + code.push(format!("\tlli 1, {}", result_reg)); + code.push(format!("{}:", end_label)); + } + BinaryOperator::Gt => { + code.push(format!("\tcmp {}, {}", left_reg, right_reg)); + code.push(format!("\tlli 0, {}", result_reg)); + let end_label = format!("_cmp_end_{}", self.get_unique_label()); + code.push(format!("\tjle {}", end_label)); // If less or equal, skip setting to 1 + code.push(format!("\tlli 1, {}", result_reg)); + code.push(format!("{}:", end_label)); + } + BinaryOperator::Ge => { + code.push(format!("\tcmp {}, {}", left_reg, right_reg)); + code.push(format!("\tlli 0, {}", result_reg)); + let end_label = format!("_cmp_end_{}", self.get_unique_label()); + code.push(format!("\tjlt {}", end_label)); // If less than, skip setting to 1 + code.push(format!("\tlli 1, {}", result_reg)); + code.push(format!("{}:", end_label)); + } + _ => return Err(format!("Unsupported binary operator: {:?}", op)), + } + + // Free operand registers + self.allocator.free_temp(&left_reg); + self.allocator.free_temp(&right_reg); + + Ok((result_reg, code)) + } + + Expression::Call { name, args } => { + // Save caller-saved registers + let save_code = self.allocator.save_caller_saved(); + code.extend(save_code); + + // Evaluate and push arguments in reverse order + let mut arg_regs = Vec::new(); + for arg in args.iter().rev() { + let (arg_reg, arg_code) = self.generate_expression(arg)?; + code.extend(arg_code); + code.push(format!("\tpush {}", arg_reg)); + arg_regs.push(arg_reg); + } + + if self.functions.contains_key(name) { + // Call local function + code.push(format!("\tcall {}", name)); + } + + if self.imports + + + + // Clean up arguments + for _ in 0..args.len() { + code.push("\tpop zero".to_string()); + } + + // Free argument registers + for reg in arg_regs { + self.allocator.free_temp(®); + } + + // Result is in rg0, allocate a register and move it + let (result_reg, result_alloc) = self.allocator.alloc_temp()?; + code.extend(result_alloc); + + if result_reg != "rg0" { + code.push(format!("\tmov rg0, {}", result_reg)); + } + + // Restore caller-saved registers (simplified - you'd track which ones) + + Ok((result_reg, code)) + } + + Expression::Unary { op, operand } => { + let (operand_reg, operand_code) = self.generate_expression(operand)?; + code.extend(operand_code); + + let (result_reg, result_alloc) = self.allocator.alloc_temp()?; + code.extend(result_alloc); + + match op { + UnaryOperator::Minus => { + // Negate: result = 0 - operand + code.push(format!("\tsub zero, {}, {}", operand_reg, result_reg)); + } + UnaryOperator::Plus => { + // Just move + code.push(format!("\tmov {}, {}", operand_reg, result_reg)); + } + } + + self.allocator.free_temp(&operand_reg); + Ok((result_reg, code)) + } + + Expression::Empty => Ok(("zero".to_string(), code)), + } + } + + // Helper for generating unique labels + fn get_unique_label(&mut self) -> String { + // You'd implement a counter here + static COUNTER: AtomicU32 = AtomicU32::new(0); + + let val = COUNTER.fetch_add(1, std::sync::atomic::Ordering::SeqCst); + (val + 1).to_string() + } } /// Build a single string from any number of arguments. diff --git a/c_compiler/src/main.rs b/c_compiler/src/main.rs index 812d1ba..06758c8 100644 --- a/c_compiler/src/main.rs +++ b/c_compiler/src/main.rs @@ -5,6 +5,7 @@ use crate::{codegen::CodeGenerator, lexer::Lexer, parser::Parser}; pub mod codegen; pub mod lexer; pub mod parser; +mod registers; // ============================================================================ // Main & Tests diff --git a/c_compiler/src/parser.rs b/c_compiler/src/parser.rs index b89aaa1..734cb04 100644 --- a/c_compiler/src/parser.rs +++ b/c_compiler/src/parser.rs @@ -17,7 +17,7 @@ pub enum Declaration { name: String, return_type: Type, params: Vec, - body: Statement, + body: Block, }, Variable { name: String, @@ -44,11 +44,11 @@ pub enum Type { Struct(String), } +pub type Block = Vec; + #[derive(Debug, Clone)] pub enum Statement { - Compound { - statements: Vec, - }, + Block(Block), Assign { // left side name: String, @@ -62,12 +62,12 @@ pub enum Statement { }, If { condition: Expression, - then_stmt: Box, - else_stmt: Option>, + then_stmt: Block, + else_stmt: Block, }, While { condition: Expression, - body: Box, + body: Vec, }, Return { expr: Option, @@ -271,7 +271,7 @@ impl Parser { } self.expect(TokenType::RParen)?; - let body = self.parse_compound_stmt()?; + let body = self.parse_block()?; Ok(Declaration::Function { name, @@ -302,7 +302,7 @@ impl Parser { } } - fn parse_compound_stmt(&mut self) -> Result { + fn parse_block(&mut self) -> Result { self.expect(TokenType::LBrace)?; let mut statements = Vec::new(); @@ -311,12 +311,12 @@ impl Parser { } self.expect(TokenType::RBrace)?; - Ok(Statement::Compound { statements }) + Ok(statements) } fn parse_statement(&mut self) -> Result { match &self.current().token_type { - TokenType::LBrace => Ok(self.parse_compound_stmt()?), + TokenType::LBrace => Ok(Statement::Block(self.parse_block()?)), TokenType::If => self.parse_if_stmt(), TokenType::While => self.parse_while_stmt(), TokenType::Return => self.parse_return_stmt(), @@ -408,13 +408,13 @@ impl Parser { self.expect(TokenType::LParen)?; let condition = self.parse_expression()?; self.expect(TokenType::RParen)?; - let then_stmt = Box::new(self.parse_statement()?); + let then_stmt = self.parse_block()?; let else_stmt = if matches!(self.current().token_type, TokenType::Else) { self.advance(); - Some(Box::new(self.parse_statement()?)) + self.parse_block()? } else { - None + Vec::new() }; Ok(Statement::If { @@ -429,7 +429,7 @@ impl Parser { self.expect(TokenType::LParen)?; let condition = self.parse_expression()?; self.expect(TokenType::RParen)?; - let body = Box::new(self.parse_statement()?); + let body = self.parse_block()?; Ok(Statement::While { condition, body }) } diff --git a/c_compiler/src/registers.rs b/c_compiler/src/registers.rs new file mode 100644 index 0000000..d13babd --- /dev/null +++ b/c_compiler/src/registers.rs @@ -0,0 +1,324 @@ +use std::collections::HashMap; + +/// Register allocator for DSA assembly generation +/// Manages general-purpose registers (rg0-rgf) and handles stack spilling +pub struct RegisterAllocator { + /// Available general-purpose registers + available_registers: Vec, + + /// Maps variable names to their current location (register or stack offset) + variable_locations: HashMap, + + /// Maps registers to the variables they currently hold + register_contents: HashMap, + + /// Current stack offset for local variables (relative to bpr) + /// Starts at -4 (going downward from base pointer) + stack_offset: i32, + + /// Track which registers are currently in use + in_use: HashMap, +} + +#[derive(Debug, Clone)] +pub enum Location { + Register(String), + Stack(i32), // offset from bpr +} + +impl RegisterAllocator { + pub fn new() -> Self { + // Initialize with available GP registers (rg0-rgf = 16 registers) + let registers = vec![ + "rg0", "rg1", "rg2", "rg3", "rg4", "rg5", "rg6", "rg7", "rg8", "rg9", "rga", + "rgb", "rgc", "rgd", "rge", "rgf", + ] + .into_iter() + .map(String::from) + .collect(); + + RegisterAllocator { + available_registers: registers, + variable_locations: HashMap::new(), + register_contents: HashMap::new(), + stack_offset: -4, // Start at -4 (first local below saved bpr) + in_use: HashMap::new(), + } + } + + /// Allocate a temporary register for expression evaluation + /// Returns the register name and optionally assembly code to save it + pub fn alloc_temp(&mut self) -> Result<(String, Vec), String> { + let mut code = Vec::new(); + + // Try to find an unused register + for reg in &self.available_registers { + if !self.in_use.get(reg).unwrap_or(&false) { + self.in_use.insert(reg.clone(), true); + return Ok((reg.clone(), code)); + } + } + + // All registers in use - need to spill one + // Choose the first register with a variable we can spill + // Find a register to spill + let reg_to_spill = self + .available_registers + .iter() + .find(|reg| self.register_contents.contains_key(*reg)) + .cloned(); + + if let Some(reg) = reg_to_spill { + // Spill this variable to stack + let spill_code = self.spill_register(®)?; + code.extend(spill_code); + + self.in_use.insert(reg.clone(), true); + return Ok((reg, code)); + } + + Err("No registers available and nothing to spill".to_string()) + } + + /// Free a temporary register after use + pub fn free_temp(&mut self, reg: &str) { + self.in_use.insert(reg.to_string(), false); + } + + /// Allocate a register for a named variable + /// Returns the register and any necessary assembly code + pub fn alloc_var(&mut self, var_name: &str) -> Result<(String, Vec), String> { + // Check if variable already has a location + if let Some(location) = self.variable_locations.get(var_name).cloned() { + match location { + Location::Register(reg) => { + return Ok((reg.clone(), Vec::new())); + } + Location::Stack(offset) => { + // Variable is on stack, load it into a register + let (reg, mut code) = self.alloc_temp()?; + code.push(format!("\tldw bpr, {}, {}", reg, offset)); + + // Update location to register + self.variable_locations + .insert(var_name.to_string(), Location::Register(reg.clone())); + self.register_contents + .insert(reg.clone(), var_name.to_string()); + + return Ok((reg, code)); + } + } + } + + // Variable doesn't have a location yet, allocate a new register + let (reg, code) = self.alloc_temp()?; + self.variable_locations + .insert(var_name.to_string(), Location::Register(reg.clone())); + self.register_contents + .insert(reg.clone(), var_name.to_string()); + + Ok((reg, code)) + } + + /// Get the current location of a variable + pub fn get_var_location(&self, var_name: &str) -> Option<&Location> { + self.variable_locations.get(var_name) + } + + /// Load a variable into a register (allocating if necessary) + /// Returns the register and assembly code to load it + pub fn load_var(&mut self, var_name: &str) -> Result<(String, Vec), String> { + self.alloc_var(var_name) + } + + /// Store a value from a register into a variable + /// Updates tracking and returns any necessary assembly code + pub fn store_var(&mut self, var_name: &str, source_reg: &str) -> Vec { + let mut code = Vec::new(); + + // Check if variable already has a location + if let Some(location) = self.variable_locations.get(var_name) { + match location { + Location::Register(dest_reg) => { + if dest_reg != source_reg { + code.push(format!("\tmov {}, {}", source_reg, dest_reg)); + } + } + Location::Stack(offset) => { + code.push(format!("\tstw {}, bpr, {}", source_reg, offset)); + } + } + } else { + // Variable doesn't exist yet - try to allocate a register + if let Some(free_reg) = self.find_free_register() { + if &free_reg != source_reg { + code.push(format!("\tmov {}, {}", source_reg, free_reg)); + } + self.variable_locations + .insert(var_name.to_string(), Location::Register(free_reg.clone())); + self.register_contents + .insert(free_reg.clone(), var_name.to_string()); + self.in_use.insert(free_reg, true); + } else { + // No free registers - allocate on stack + code.push(format!("\tstw {}, bpr, {}", source_reg, self.stack_offset)); + self.variable_locations + .insert(var_name.to_string(), Location::Stack(self.stack_offset)); + self.stack_offset -= 4; // Move to next stack slot + } + } + + code + } + + /// Spill a register to the stack + /// Returns assembly code to perform the spill + fn spill_register(&mut self, reg: &str) -> Result, String> { + let mut code = Vec::new(); + + if let Some(var_name) = self.register_contents.get(reg).cloned() { + // Store register content to stack + code.push(format!("\tstw {}, bpr, {}", reg, self.stack_offset)); + + // Update variable location + self.variable_locations + .insert(var_name.clone(), Location::Stack(self.stack_offset)); + + // Remove from register tracking + self.register_contents.remove(reg); + + // Move to next stack slot + self.stack_offset -= 4; + } + + Ok(code) + } + + /// Find a free register (not currently in use) + fn find_free_register(&self) -> Option { + for reg in &self.available_registers { + if !self.in_use.get(reg).unwrap_or(&false) { + return Some(reg.clone()); + } + } + None + } + + /// Spill all registers to stack (useful before function calls) + pub fn spill_all(&mut self) -> Vec { + let mut code = Vec::new(); + + let regs_to_spill: Vec = self.register_contents.keys().cloned().collect(); + + for reg in regs_to_spill { + if let Ok(spill_code) = self.spill_register(®) { + code.extend(spill_code); + } + } + + code + } + + /// Get the total stack space needed for local variables + pub fn get_stack_size(&self) -> i32 { + -self.stack_offset // Convert negative offset to positive size + } + + /// Reset allocator for a new function + pub fn reset(&mut self) { + self.variable_locations.clear(); + self.register_contents.clear(); + self.stack_offset = -4; + self.in_use.clear(); + } + + /// Mark a variable as dead (no longer needed) + /// Frees its register if it's in one + pub fn free_var(&mut self, var_name: &str) { + if let Some(Location::Register(reg)) = self.variable_locations.get(var_name) { + let reg = reg.clone(); + self.register_contents.remove(®); + self.in_use.insert(reg, false); + } + self.variable_locations.remove(var_name); + } + + /// Save caller-saved registers before a function call + /// Returns assembly code to save them + pub fn save_caller_saved(&mut self) -> Vec { + let mut code = Vec::new(); + + // For simplicity, save all currently used registers + // In a more sophisticated compiler, you'd only save registers that are live + for (reg, var_name) in self.register_contents.clone() { + if *self.in_use.get(®).unwrap_or(&false) { + code.push(format!("\tpush {}", reg)); + } + } + + code + } + + /// Restore caller-saved registers after a function call + /// Returns assembly code to restore them + pub fn restore_caller_saved(&mut self, saved_regs: &[String]) -> Vec { + let mut code = Vec::new(); + + // Restore in reverse order (LIFO) + for reg in saved_regs.iter().rev() { + code.push(format!("\tpop {}", reg)); + } + + code + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_basic_allocation() { + let mut allocator = RegisterAllocator::new(); + + let (reg1, code1) = allocator.alloc_temp().unwrap(); + assert_eq!(code1.len(), 0); // No spill needed + assert_eq!(reg1, "rg0"); + + let (reg2, code2) = allocator.alloc_temp().unwrap(); + assert_eq!(code2.len(), 0); + assert_eq!(reg2, "rg1"); + + allocator.free_temp(®1); + + let (reg3, code3) = allocator.alloc_temp().unwrap(); + assert_eq!(code3.len(), 0); + assert_eq!(reg3, "rg0"); // Reuses freed register + } + + #[test] + fn test_variable_allocation() { + let mut allocator = RegisterAllocator::new(); + + let (reg, _) = allocator.alloc_var("x").unwrap(); + assert_eq!(reg, "rg0"); + + // Requesting same variable again should return same register + let (reg2, _) = allocator.alloc_var("x").unwrap(); + assert_eq!(reg2, "rg0"); + } + + #[test] + fn test_stack_allocation() { + let mut allocator = RegisterAllocator::new(); + + // Allocate all 16 registers + for i in 0..16 { + allocator.alloc_var(&format!("var{}", i)).unwrap(); + } + + // Next allocation should spill to stack + let (reg, code) = allocator.alloc_var("var16").unwrap(); + assert!(code.len() > 0); // Should have spill code + } +} -- 2.47.3 From 782c842a42aa92d21630db503eb67ad03861344c Mon Sep 17 00:00:00 2001 From: zxq5 Date: Thu, 29 Jan 2026 19:33:30 +0000 Subject: [PATCH 06/53] updated gitignore --- .gitignore | 1 - Cargo.lock | 4270 ----------------------------------------- c_compiler/example.c | 12 + dsa_editor/Cargo.lock | 3985 -------------------------------------- 4 files changed, 12 insertions(+), 8256 deletions(-) delete mode 100644 Cargo.lock create mode 100644 c_compiler/example.c delete mode 100644 dsa_editor/Cargo.lock diff --git a/.gitignore b/.gitignore index b05f24c..c0c554f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ /target **/*.env Cargo.lock -*Cargo.lock diff --git a/Cargo.lock b/Cargo.lock deleted file mode 100644 index 4881937..0000000 --- a/Cargo.lock +++ /dev/null @@ -1,4270 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "ab_glyph" -version = "0.2.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec3672c180e71eeaaac3a541fbbc5f5ad4def8b747c595ad30d674e43049f7b0" -dependencies = [ - "ab_glyph_rasterizer", - "owned_ttf_parser", -] - -[[package]] -name = "ab_glyph_rasterizer" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" - -[[package]] -name = "accesskit" -version = "0.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3d3b8f9bae46a948369bc4a03e815d4ed6d616bd00de4051133a5019dc31c5a" - -[[package]] -name = "accesskit_atspi_common" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c5dd55e6e94949498698daf4d48fb5659e824d7abec0d394089656ceaf99d4f" -dependencies = [ - "accesskit", - "accesskit_consumer", - "atspi-common", - "serde", - "thiserror 1.0.69", - "zvariant", -] - -[[package]] -name = "accesskit_consumer" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f47983a1084940ba9a39c077a8c63e55c619388be5476ac04c804cfbd1e63459" -dependencies = [ - "accesskit", - "hashbrown", - "immutable-chunkmap", -] - -[[package]] -name = "accesskit_macos" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7329821f3bd1101e03a7d2e03bd339e3ac0dc64c70b4c9f9ae1949e3ba8dece1" -dependencies = [ - "accesskit", - "accesskit_consumer", - "hashbrown", - "objc2 0.5.2", - "objc2-app-kit 0.2.2", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "accesskit_unix" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcee751cc20d88678c33edaf9c07e8b693cd02819fe89053776f5313492273f5" -dependencies = [ - "accesskit", - "accesskit_atspi_common", - "async-channel", - "async-executor", - "async-task", - "atspi", - "futures-lite", - "futures-util", - "serde", - "zbus", -] - -[[package]] -name = "accesskit_windows" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24fcd5d23d70670992b823e735e859374d694a3d12bfd8dd32bd3bd8bedb5d81" -dependencies = [ - "accesskit", - "accesskit_consumer", - "hashbrown", - "paste", - "static_assertions", - "windows", - "windows-core", -] - -[[package]] -name = "accesskit_winit" -version = "0.23.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6a48dad5530b6deb9fc7a52cc6c3bf72cdd9eb8157ac9d32d69f2427a5e879" -dependencies = [ - "accesskit", - "accesskit_macos", - "accesskit_unix", - "accesskit_windows", - "raw-window-handle", - "winit", -] - -[[package]] -name = "adler2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" - -[[package]] -name = "ahash" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" -dependencies = [ - "cfg-if", - "getrandom 0.3.3", - "once_cell", - "version_check", - "zerocopy", -] - -[[package]] -name = "android-activity" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046" -dependencies = [ - "android-properties", - "bitflags 2.9.1", - "cc", - "cesu8", - "jni", - "jni-sys", - "libc", - "log", - "ndk", - "ndk-context", - "ndk-sys 0.6.0+11769913", - "num_enum", - "thiserror 1.0.69", -] - -[[package]] -name = "android-properties" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - -[[package]] -name = "arboard" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1df21f715862ede32a0c525ce2ca4d52626bb0007f8c18b87a384503ac33e70" -dependencies = [ - "clipboard-win", - "image", - "log", - "objc2 0.6.1", - "objc2-app-kit 0.3.1", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-foundation 0.3.1", - "parking_lot", - "percent-encoding", - "windows-sys 0.59.0", - "x11rb", -] - -[[package]] -name = "arrayref" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" - -[[package]] -name = "arrayvec" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" - -[[package]] -name = "as-raw-xcb-connection" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" - -[[package]] -name = "ash" -version = "0.38.0+1.3.281" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f" -dependencies = [ - "libloading", -] - -[[package]] -name = "assembler" -version = "0.2.0" -dependencies = [ - "common", - "num_cpus", - "threadpool", -] - -[[package]] -name = "async-broadcast" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" -dependencies = [ - "event-listener", - "event-listener-strategy", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-channel" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" -dependencies = [ - "concurrent-queue", - "event-listener-strategy", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-executor" -version = "1.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb812ffb58524bdd10860d7d974e2f01cc0950c2438a74ee5ec2e2280c6c4ffa" -dependencies = [ - "async-task", - "concurrent-queue", - "fastrand", - "futures-lite", - "pin-project-lite", - "slab", -] - -[[package]] -name = "async-fs" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebcd09b382f40fcd159c2d695175b2ae620ffa5f3bd6f664131efff4e8b9e04a" -dependencies = [ - "async-lock", - "blocking", - "futures-lite", -] - -[[package]] -name = "async-io" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1237c0ae75a0f3765f58910ff9cdd0a12eeb39ab2f4c7de23262f337f0aacbb3" -dependencies = [ - "async-lock", - "cfg-if", - "concurrent-queue", - "futures-io", - "futures-lite", - "parking", - "polling", - "rustix 1.0.7", - "slab", - "tracing", - "windows-sys 0.59.0", -] - -[[package]] -name = "async-lock" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" -dependencies = [ - "event-listener", - "event-listener-strategy", - "pin-project-lite", -] - -[[package]] -name = "async-process" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cde3f4e40e6021d7acffc90095cbd6dc54cb593903d1de5832f435eb274b85dc" -dependencies = [ - "async-channel", - "async-io", - "async-lock", - "async-signal", - "async-task", - "blocking", - "cfg-if", - "event-listener", - "futures-lite", - "rustix 1.0.7", - "tracing", -] - -[[package]] -name = "async-recursion" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "async-signal" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7605a4e50d4b06df3898d5a70bf5fde51ed9059b0434b73105193bc27acce0d" -dependencies = [ - "async-io", - "async-lock", - "atomic-waker", - "cfg-if", - "futures-core", - "futures-io", - "rustix 1.0.7", - "signal-hook-registry", - "slab", - "windows-sys 0.59.0", -] - -[[package]] -name = "async-task" -version = "4.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" - -[[package]] -name = "async-trait" -version = "0.1.88" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - -[[package]] -name = "atspi" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be534b16650e35237bb1ed189ba2aab86ce65e88cc84c66f4935ba38575cecbf" -dependencies = [ - "atspi-common", - "atspi-connection", - "atspi-proxies", -] - -[[package]] -name = "atspi-common" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1909ed2dc01d0a17505d89311d192518507e8a056a48148e3598fef5e7bb6ba7" -dependencies = [ - "enumflags2", - "serde", - "static_assertions", - "zbus", - "zbus-lockstep", - "zbus-lockstep-macros", - "zbus_names", - "zvariant", -] - -[[package]] -name = "atspi-connection" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "430c5960624a4baaa511c9c0fcc2218e3b58f5dbcc47e6190cafee344b873333" -dependencies = [ - "atspi-common", - "atspi-proxies", - "futures-lite", - "zbus", -] - -[[package]] -name = "atspi-proxies" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e6c5de3e524cf967569722446bcd458d5032348554d9a17d7d72b041ab7496" -dependencies = [ - "atspi-common", - "serde", - "zbus", - "zvariant", -] - -[[package]] -name = "autocfg" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" - -[[package]] -name = "bit-set" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" -dependencies = [ - "bit-vec", -] - -[[package]] -name = "bit-vec" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" -dependencies = [ - "serde", -] - -[[package]] -name = "block" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "block2" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" -dependencies = [ - "objc2 0.5.2", -] - -[[package]] -name = "blocking" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" -dependencies = [ - "async-channel", - "async-task", - "futures-io", - "futures-lite", - "piper", -] - -[[package]] -name = "bumpalo" -version = "3.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db76d6187cd04dff33004d8e6c9cc4e05cd330500379d2394209271b4aeee" - -[[package]] -name = "bytemuck" -version = "1.23.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422" -dependencies = [ - "bytemuck_derive", -] - -[[package]] -name = "bytemuck_derive" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ecc273b49b3205b83d648f0690daa588925572cc5063745bfe547fe7ec8e1a1" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "byteorder-lite" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" - -[[package]] -name = "bytes" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" - -[[package]] -name = "c_compiler" -version = "0.2.0" - -[[package]] -name = "calloop" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" -dependencies = [ - "bitflags 2.9.1", - "log", - "polling", - "rustix 0.38.44", - "slab", - "thiserror 1.0.69", -] - -[[package]] -name = "calloop-wayland-source" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" -dependencies = [ - "calloop", - "rustix 0.38.44", - "wayland-backend", - "wayland-client", -] - -[[package]] -name = "cc" -version = "1.2.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" -dependencies = [ - "jobserver", - "libc", - "shlex", -] - -[[package]] -name = "cesu8" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - -[[package]] -name = "cfg-if" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" - -[[package]] -name = "cfg_aliases" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" - -[[package]] -name = "cgl" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ced0551234e87afee12411d535648dd89d2e7f34c78b753395567aff3d447ff" -dependencies = [ - "libc", -] - -[[package]] -name = "clipboard-win" -version = "5.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15efe7a882b08f34e38556b14f2fb3daa98769d06c7f0c1b076dfd0d983bc892" -dependencies = [ - "error-code", -] - -[[package]] -name = "codespan-reporting" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" -dependencies = [ - "termcolor", - "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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" -dependencies = [ - "bytes", - "memchr", -] - -[[package]] -name = "common" -version = "0.2.0" - -[[package]] -name = "compiler" -version = "0.2.0" - -[[package]] -name = "concurrent-queue" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "core-foundation" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - -[[package]] -name = "core-graphics" -version = "0.23.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" -dependencies = [ - "bitflags 1.3.2", - "core-foundation 0.9.4", - "core-graphics-types", - "foreign-types", - "libc", -] - -[[package]] -name = "core-graphics-types" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" -dependencies = [ - "bitflags 1.3.2", - "core-foundation 0.9.4", - "libc", -] - -[[package]] -name = "cpufeatures" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" -dependencies = [ - "libc", -] - -[[package]] -name = "crc32fast" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crossbeam-channel" -version = "0.5.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" - -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "cursor-icon" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "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.60.2", -] - -[[package]] -name = "discord-presence" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91d7c2fc01ffdc327e2b66d65dd59b8bd3f31a17e88811ce0540412fa0b84c1" -dependencies = [ - "byteorder", - "bytes", - "cfg-if", - "crossbeam-channel", - "log", - "num-derive", - "num-traits", - "parking_lot", - "paste", - "quork", - "serde", - "serde_json", - "thiserror 2.0.12", - "uuid", -] - -[[package]] -name = "dispatch" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" - -[[package]] -name = "dispatch2" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" -dependencies = [ - "bitflags 2.9.1", - "objc2 0.6.1", -] - -[[package]] -name = "displaydoc" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "dlib" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" -dependencies = [ - "libloading", -] - -[[package]] -name = "document-features" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d" -dependencies = [ - "litrs", -] - -[[package]] -name = "downcast-rs" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" - -[[package]] -name = "dpi" -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 = "dyn-clone" -version = "1.0.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" - -[[package]] -name = "ecolor" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc4feb366740ded31a004a0e4452fbf84e80ef432ecf8314c485210229672fd1" -dependencies = [ - "bytemuck", - "emath", -] - -[[package]] -name = "eframe" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0dfe0859f3fb1bc6424c57d41e10e9093fe938f426b691e42272c2f336d915c" -dependencies = [ - "ahash", - "bytemuck", - "document-features", - "egui", - "egui-wgpu", - "egui-winit", - "egui_glow", - "glow", - "glutin", - "glutin-winit", - "image", - "js-sys", - "log", - "objc2 0.5.2", - "objc2-app-kit 0.2.2", - "objc2-foundation 0.2.2", - "parking_lot", - "percent-encoding", - "profiling", - "raw-window-handle", - "static_assertions", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "web-time", - "winapi", - "windows-sys 0.59.0", - "winit", -] - -[[package]] -name = "egui" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25dd34cec49ab55d85ebf70139cb1ccd29c977ef6b6ba4fe85489d6877ee9ef3" -dependencies = [ - "accesskit", - "ahash", - "bitflags 2.9.1", - "emath", - "epaint", - "log", - "nohash-hasher", - "profiling", -] - -[[package]] -name = "egui-wgpu" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d319dfef570f699b6e9114e235e862a2ddcf75f0d1a061de9e1328d92146d820" -dependencies = [ - "ahash", - "bytemuck", - "document-features", - "egui", - "epaint", - "log", - "profiling", - "thiserror 1.0.69", - "type-map", - "web-time", - "wgpu", - "winit", -] - -[[package]] -name = "egui-winit" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d9dfbb78fe4eb9c3a39ad528b90ee5915c252e77bbab9d4ebc576541ab67e13" -dependencies = [ - "accesskit_winit", - "ahash", - "arboard", - "bytemuck", - "egui", - "log", - "profiling", - "raw-window-handle", - "smithay-clipboard", - "web-time", - "webbrowser", - "winit", -] - -[[package]] -name = "egui_file" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e7289fecaa1af3f4944a7ac6e1c187d0700e32716c2a4c76d6bad7ffd255d72" -dependencies = [ - "dyn-clone", - "egui", -] - -[[package]] -name = "egui_glow" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "910906e3f042ea6d2378ec12a6fd07698e14ddae68aed2d819ffe944a73aab9e" -dependencies = [ - "ahash", - "bytemuck", - "egui", - "glow", - "log", - "memoffset", - "profiling", - "wasm-bindgen", - "web-sys", - "winit", -] - -[[package]] -name = "emath" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e4cadcff7a5353ba72b7fea76bf2122b5ebdbc68e8155aa56dfdea90083fe1b" -dependencies = [ - "bytemuck", -] - -[[package]] -name = "emulator" -version = "0.1.0" -dependencies = [ - "assembler", - "common", - "dirs", - "discord-presence", - "dsa_editor", - "eframe", - "egui", - "egui_file", - "serde", - "toml", - "winit", -] - -[[package]] -name = "endi" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" - -[[package]] -name = "enumflags2" -version = "0.7.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" -dependencies = [ - "enumflags2_derive", - "serde", -] - -[[package]] -name = "enumflags2_derive" -version = "0.7.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "epaint" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fcc0f5a7c613afd2dee5e4b30c3e6acafb8ad6f0edb06068811f708a67c562" -dependencies = [ - "ab_glyph", - "ahash", - "bytemuck", - "ecolor", - "emath", - "epaint_default_fonts", - "log", - "nohash-hasher", - "parking_lot", - "profiling", -] - -[[package]] -name = "epaint_default_fonts" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7e7a64c02cf7a5b51e745a9e45f60660a286f151c238b9d397b3e923f5082f" - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "errno" -version = "0.3.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" -dependencies = [ - "libc", - "windows-sys 0.60.2", -] - -[[package]] -name = "error-code" -version = "3.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" - -[[package]] -name = "event-listener" -version = "5.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] - -[[package]] -name = "event-listener-strategy" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" -dependencies = [ - "event-listener", - "pin-project-lite", -] - -[[package]] -name = "fastrand" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" - -[[package]] -name = "fdeflate" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" -dependencies = [ - "simd-adler32", -] - -[[package]] -name = "flate2" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" -dependencies = [ - "crc32fast", - "miniz_oxide", -] - -[[package]] -name = "foldhash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - -[[package]] -name = "foreign-types" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" -dependencies = [ - "foreign-types-macros", - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-macros" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "foreign-types-shared" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" - -[[package]] -name = "form_urlencoded" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "futures-core" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" - -[[package]] -name = "futures-io" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" - -[[package]] -name = "futures-lite" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" -dependencies = [ - "fastrand", - "futures-core", - "futures-io", - "parking", - "pin-project-lite", -] - -[[package]] -name = "futures-macro" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "futures-sink" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" - -[[package]] -name = "futures-task" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" - -[[package]] -name = "futures-util" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" -dependencies = [ - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "gethostname" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" -dependencies = [ - "libc", - "windows-targets 0.48.5", -] - -[[package]] -name = "getrandom" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.11.1+wasi-snapshot-preview1", -] - -[[package]] -name = "getrandom" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" -dependencies = [ - "cfg-if", - "libc", - "r-efi", - "wasi 0.14.2+wasi-0.2.4", -] - -[[package]] -name = "gl_generator" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" -dependencies = [ - "khronos_api", - "log", - "xml-rs", -] - -[[package]] -name = "glow" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5e5ea60d70410161c8bf5da3fdfeaa1c72ed2c15f8bbb9d19fe3a4fad085f08" -dependencies = [ - "js-sys", - "slotmap", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "glutin" -version = "0.32.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12124de845cacfebedff80e877bb37b5b75c34c5a4c89e47e1cdd67fb6041325" -dependencies = [ - "bitflags 2.9.1", - "cfg_aliases", - "cgl", - "dispatch2", - "glutin_egl_sys", - "glutin_glx_sys", - "glutin_wgl_sys", - "libloading", - "objc2 0.6.1", - "objc2-app-kit 0.3.1", - "objc2-core-foundation", - "objc2-foundation 0.3.1", - "once_cell", - "raw-window-handle", - "wayland-sys", - "windows-sys 0.52.0", - "x11-dl", -] - -[[package]] -name = "glutin-winit" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85edca7075f8fc728f28cb8fbb111a96c3b89e930574369e3e9c27eb75d3788f" -dependencies = [ - "cfg_aliases", - "glutin", - "raw-window-handle", - "winit", -] - -[[package]] -name = "glutin_egl_sys" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c4680ba6195f424febdc3ba46e7a42a0e58743f2edb115297b86d7f8ecc02d2" -dependencies = [ - "gl_generator", - "windows-sys 0.52.0", -] - -[[package]] -name = "glutin_glx_sys" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7bb2938045a88b612499fbcba375a77198e01306f52272e692f8c1f3751185" -dependencies = [ - "gl_generator", - "x11-dl", -] - -[[package]] -name = "glutin_wgl_sys" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c4ee00b289aba7a9e5306d57c2d05499b2e5dc427f84ac708bd2c090212cf3e" -dependencies = [ - "gl_generator", -] - -[[package]] -name = "gpu-alloc" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" -dependencies = [ - "bitflags 2.9.1", - "gpu-alloc-types", -] - -[[package]] -name = "gpu-alloc-types" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" -dependencies = [ - "bitflags 2.9.1", -] - -[[package]] -name = "gpu-descriptor" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca" -dependencies = [ - "bitflags 2.9.1", - "gpu-descriptor-types", - "hashbrown", -] - -[[package]] -name = "gpu-descriptor-types" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" -dependencies = [ - "bitflags 2.9.1", -] - -[[package]] -name = "hashbrown" -version = "0.15.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" -dependencies = [ - "foldhash", -] - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "hermit-abi" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "hexf-parse" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" - -[[package]] -name = "icu_collections" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" -dependencies = [ - "displaydoc", - "potential_utf", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locale_core" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_normalizer" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" - -[[package]] -name = "icu_properties" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_locale_core", - "icu_properties_data", - "icu_provider", - "potential_utf", - "zerotrie", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" - -[[package]] -name = "icu_provider" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" -dependencies = [ - "displaydoc", - "icu_locale_core", - "stable_deref_trait", - "tinystr", - "writeable", - "yoke", - "zerofrom", - "zerotrie", - "zerovec", -] - -[[package]] -name = "idna" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" -dependencies = [ - "icu_normalizer", - "icu_properties", -] - -[[package]] -name = "image" -version = "0.25.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db35664ce6b9810857a38a906215e75a9c879f0696556a39f59c62829710251a" -dependencies = [ - "bytemuck", - "byteorder-lite", - "num-traits", - "png", - "tiff", -] - -[[package]] -name = "immutable-chunkmap" -version = "2.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f97096f508d54f8f8ab8957862eee2ccd628847b6217af1a335e1c44dee578" -dependencies = [ - "arrayvec", -] - -[[package]] -name = "indexmap" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" -dependencies = [ - "equivalent", - "hashbrown", -] - -[[package]] -name = "itoa" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" - -[[package]] -name = "jni" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" -dependencies = [ - "cesu8", - "cfg-if", - "combine", - "jni-sys", - "log", - "thiserror 1.0.69", - "walkdir", - "windows-sys 0.45.0", -] - -[[package]] -name = "jni-sys" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" - -[[package]] -name = "jobserver" -version = "0.1.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" -dependencies = [ - "getrandom 0.3.3", - "libc", -] - -[[package]] -name = "jpeg-decoder" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00810f1d8b74be64b13dbf3db89ac67740615d6c891f0e7b6179326533011a07" - -[[package]] -name = "js-sys" -version = "0.3.77" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" -dependencies = [ - "once_cell", - "wasm-bindgen", -] - -[[package]] -name = "khronos-egl" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" -dependencies = [ - "libc", - "libloading", - "pkg-config", -] - -[[package]] -name = "khronos_api" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" - -[[package]] -name = "libc" -version = "0.2.174" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" - -[[package]] -name = "libloading" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" -dependencies = [ - "cfg-if", - "windows-targets 0.53.2", -] - -[[package]] -name = "libredox" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" -dependencies = [ - "bitflags 2.9.1", - "libc", - "redox_syscall 0.5.13", -] - -[[package]] -name = "linux-raw-sys" -version = "0.4.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" - -[[package]] -name = "linux-raw-sys" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" - -[[package]] -name = "litemap" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" - -[[package]] -name = "litrs" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" - -[[package]] -name = "lock_api" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" -dependencies = [ - "autocfg", - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" - -[[package]] -name = "malloc_buf" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" -dependencies = [ - "libc", -] - -[[package]] -name = "memchr" -version = "2.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" - -[[package]] -name = "memmap2" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" -dependencies = [ - "libc", -] - -[[package]] -name = "memoffset" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" -dependencies = [ - "autocfg", -] - -[[package]] -name = "metal" -version = "0.31.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f569fb946490b5743ad69813cb19629130ce9374034abe31614a36402d18f99e" -dependencies = [ - "bitflags 2.9.1", - "block", - "core-graphics-types", - "foreign-types", - "log", - "objc", - "paste", -] - -[[package]] -name = "miniz_oxide" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" -dependencies = [ - "adler2", - "simd-adler32", -] - -[[package]] -name = "naga" -version = "24.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e380993072e52eef724eddfcde0ed013b0c023c3f0417336ed041aa9f076994e" -dependencies = [ - "arrayvec", - "bit-set", - "bitflags 2.9.1", - "cfg_aliases", - "codespan-reporting", - "hexf-parse", - "indexmap", - "log", - "rustc-hash 1.1.0", - "spirv", - "strum", - "termcolor", - "thiserror 2.0.12", - "unicode-xid", -] - -[[package]] -name = "ndk" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" -dependencies = [ - "bitflags 2.9.1", - "jni-sys", - "log", - "ndk-sys 0.6.0+11769913", - "num_enum", - "raw-window-handle", - "thiserror 1.0.69", -] - -[[package]] -name = "ndk-context" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" - -[[package]] -name = "ndk-sys" -version = "0.5.0+25.2.9519653" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691" -dependencies = [ - "jni-sys", -] - -[[package]] -name = "ndk-sys" -version = "0.6.0+11769913" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" -dependencies = [ - "jni-sys", -] - -[[package]] -name = "nix" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" -dependencies = [ - "bitflags 2.9.1", - "cfg-if", - "cfg_aliases", - "libc", - "memoffset", -] - -[[package]] -name = "nohash-hasher" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" - -[[package]] -name = "num-derive" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - -[[package]] -name = "num_cpus" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" -dependencies = [ - "hermit-abi", - "libc", -] - -[[package]] -name = "num_enum" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" -dependencies = [ - "num_enum_derive", - "rustversion", -] - -[[package]] -name = "num_enum_derive" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "objc" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" -dependencies = [ - "malloc_buf", -] - -[[package]] -name = "objc-sys" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" - -[[package]] -name = "objc2" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" -dependencies = [ - "objc-sys", - "objc2-encode", -] - -[[package]] -name = "objc2" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88c6597e14493ab2e44ce58f2fdecf095a51f12ca57bec060a11c57332520551" -dependencies = [ - "objc2-encode", -] - -[[package]] -name = "objc2-app-kit" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" -dependencies = [ - "bitflags 2.9.1", - "block2", - "libc", - "objc2 0.5.2", - "objc2-core-data", - "objc2-core-image", - "objc2-foundation 0.2.2", - "objc2-quartz-core", -] - -[[package]] -name = "objc2-app-kit" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc" -dependencies = [ - "bitflags 2.9.1", - "objc2 0.6.1", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-foundation 0.3.1", -] - -[[package]] -name = "objc2-cloud-kit" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" -dependencies = [ - "bitflags 2.9.1", - "block2", - "objc2 0.5.2", - "objc2-core-location", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "objc2-contacts" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" -dependencies = [ - "block2", - "objc2 0.5.2", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "objc2-core-data" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" -dependencies = [ - "bitflags 2.9.1", - "block2", - "objc2 0.5.2", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "objc2-core-foundation" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" -dependencies = [ - "bitflags 2.9.1", - "dispatch2", - "objc2 0.6.1", -] - -[[package]] -name = "objc2-core-graphics" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "989c6c68c13021b5c2d6b71456ebb0f9dc78d752e86a98da7c716f4f9470f5a4" -dependencies = [ - "bitflags 2.9.1", - "dispatch2", - "objc2 0.6.1", - "objc2-core-foundation", - "objc2-io-surface", -] - -[[package]] -name = "objc2-core-image" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" -dependencies = [ - "block2", - "objc2 0.5.2", - "objc2-foundation 0.2.2", - "objc2-metal", -] - -[[package]] -name = "objc2-core-location" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" -dependencies = [ - "block2", - "objc2 0.5.2", - "objc2-contacts", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "objc2-encode" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" - -[[package]] -name = "objc2-foundation" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" -dependencies = [ - "bitflags 2.9.1", - "block2", - "dispatch", - "libc", - "objc2 0.5.2", -] - -[[package]] -name = "objc2-foundation" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c" -dependencies = [ - "bitflags 2.9.1", - "objc2 0.6.1", - "objc2-core-foundation", -] - -[[package]] -name = "objc2-io-surface" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7282e9ac92529fa3457ce90ebb15f4ecbc383e8338060960760fa2cf75420c3c" -dependencies = [ - "bitflags 2.9.1", - "objc2 0.6.1", - "objc2-core-foundation", -] - -[[package]] -name = "objc2-link-presentation" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" -dependencies = [ - "block2", - "objc2 0.5.2", - "objc2-app-kit 0.2.2", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "objc2-metal" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" -dependencies = [ - "bitflags 2.9.1", - "block2", - "objc2 0.5.2", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "objc2-quartz-core" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" -dependencies = [ - "bitflags 2.9.1", - "block2", - "objc2 0.5.2", - "objc2-foundation 0.2.2", - "objc2-metal", -] - -[[package]] -name = "objc2-symbols" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" -dependencies = [ - "objc2 0.5.2", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "objc2-ui-kit" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" -dependencies = [ - "bitflags 2.9.1", - "block2", - "objc2 0.5.2", - "objc2-cloud-kit", - "objc2-core-data", - "objc2-core-image", - "objc2-core-location", - "objc2-foundation 0.2.2", - "objc2-link-presentation", - "objc2-quartz-core", - "objc2-symbols", - "objc2-uniform-type-identifiers", - "objc2-user-notifications", -] - -[[package]] -name = "objc2-uniform-type-identifiers" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" -dependencies = [ - "block2", - "objc2 0.5.2", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "objc2-user-notifications" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" -dependencies = [ - "bitflags 2.9.1", - "block2", - "objc2 0.5.2", - "objc2-core-location", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "once_cell" -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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba0b26cec2e24f08ed8bb31519a9333140a6599b867dac464bb150bdb796fd43" -dependencies = [ - "libredox", -] - -[[package]] -name = "ordered-float" -version = "4.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951" -dependencies = [ - "num-traits", -] - -[[package]] -name = "ordered-stream" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" -dependencies = [ - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "owned_ttf_parser" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec719bbf3b2a81c109a4e20b1f129b5566b7dce654bc3872f6a05abf82b2c4" -dependencies = [ - "ttf-parser", -] - -[[package]] -name = "parking" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" - -[[package]] -name = "parking_lot" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall 0.5.13", - "smallvec", - "windows-targets 0.52.6", -] - -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - -[[package]] -name = "percent-encoding" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" - -[[package]] -name = "pin-project" -version = "1.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "piper" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" -dependencies = [ - "atomic-waker", - "fastrand", - "futures-io", -] - -[[package]] -name = "pkg-config" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" - -[[package]] -name = "png" -version = "0.17.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" -dependencies = [ - "bitflags 1.3.2", - "crc32fast", - "fdeflate", - "flate2", - "miniz_oxide", -] - -[[package]] -name = "polling" -version = "3.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b53a684391ad002dd6a596ceb6c74fd004fdce75f4be2e3f615068abbea5fd50" -dependencies = [ - "cfg-if", - "concurrent-queue", - "hermit-abi", - "pin-project-lite", - "rustix 1.0.7", - "tracing", - "windows-sys 0.59.0", -] - -[[package]] -name = "potential_utf" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" -dependencies = [ - "zerovec", -] - -[[package]] -name = "ppv-lite86" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] - -[[package]] -name = "proc-macro-crate" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" -dependencies = [ - "toml_edit", -] - -[[package]] -name = "proc-macro-error-attr2" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" -dependencies = [ - "proc-macro2", - "quote", -] - -[[package]] -name = "proc-macro-error2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" -dependencies = [ - "proc-macro-error-attr2", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "proc-macro2" -version = "1.0.95" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "profiling" -version = "1.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" - -[[package]] -name = "quick-xml" -version = "0.30.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956" -dependencies = [ - "memchr", - "serde", -] - -[[package]] -name = "quick-xml" -version = "0.37.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" -dependencies = [ - "memchr", -] - -[[package]] -name = "quork" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bd9640e0addc098a3481fd53bdc23970e5dd0edf6b349403aa680fb576c8f83" -dependencies = [ - "cfg-if", - "nix", - "quork-proc", - "thiserror 2.0.12", - "windows-sys 0.59.0", -] - -[[package]] -name = "quork-proc" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "860d36740d9412e39fff90f57010e9870b15c2b48e5325295a6f5a824a480439" -dependencies = [ - "proc-macro-crate", - "proc-macro-error2", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "quote" -version = "1.0.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.16", -] - -[[package]] -name = "raw-window-handle" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" - -[[package]] -name = "redox_syscall" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "redox_syscall" -version = "0.5.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" -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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" - -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - -[[package]] -name = "rustc-hash" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" - -[[package]] -name = "rustix" -version = "0.38.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" -dependencies = [ - "bitflags 2.9.1", - "errno", - "libc", - "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", -] - -[[package]] -name = "rustix" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" -dependencies = [ - "bitflags 2.9.1", - "errno", - "libc", - "linux-raw-sys 0.9.4", - "windows-sys 0.59.0", -] - -[[package]] -name = "rustversion" -version = "1.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" - -[[package]] -name = "ryu" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "sctk-adwaita" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6277f0217056f77f1d8f49f2950ac6c278c0d607c45f5ee99328d792ede24ec" -dependencies = [ - "ab_glyph", - "log", - "memmap2", - "smithay-client-toolkit", - "tiny-skia", -] - -[[package]] -name = "serde" -version = "1.0.219" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.219" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.140" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" -dependencies = [ - "itoa", - "memchr", - "ryu", - "serde", -] - -[[package]] -name = "serde_repr" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_spanned" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" -dependencies = [ - "serde", -] - -[[package]] -name = "sha1" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "signal-hook-registry" -version = "1.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" -dependencies = [ - "libc", -] - -[[package]] -name = "simd-adler32" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" - -[[package]] -name = "slab" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" - -[[package]] -name = "slotmap" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" -dependencies = [ - "version_check", -] - -[[package]] -name = "smallvec" -version = "1.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" - -[[package]] -name = "smithay-client-toolkit" -version = "0.19.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" -dependencies = [ - "bitflags 2.9.1", - "calloop", - "calloop-wayland-source", - "cursor-icon", - "libc", - "log", - "memmap2", - "rustix 0.38.44", - "thiserror 1.0.69", - "wayland-backend", - "wayland-client", - "wayland-csd-frame", - "wayland-cursor", - "wayland-protocols", - "wayland-protocols-wlr", - "wayland-scanner", - "xkeysym", -] - -[[package]] -name = "smithay-clipboard" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc8216eec463674a0e90f29e0ae41a4db573ec5b56b1c6c1c71615d249b6d846" -dependencies = [ - "libc", - "smithay-client-toolkit", - "wayland-backend", -] - -[[package]] -name = "smol_str" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" -dependencies = [ - "serde", -] - -[[package]] -name = "spirv" -version = "0.3.0+sdk-1.3.268.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" -dependencies = [ - "bitflags 2.9.1", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "strict-num" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" - -[[package]] -name = "strum" -version = "0.26.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" -dependencies = [ - "strum_macros", -] - -[[package]] -name = "strum_macros" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "rustversion", - "syn", -] - -[[package]] -name = "syn" -version = "2.0.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "synstructure" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tempfile" -version = "3.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" -dependencies = [ - "fastrand", - "getrandom 0.3.3", - "once_cell", - "rustix 1.0.7", - "windows-sys 0.59.0", -] - -[[package]] -name = "termcolor" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - -[[package]] -name = "thiserror" -version = "2.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" -dependencies = [ - "thiserror-impl 2.0.12", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "threadpool" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" -dependencies = [ - "num_cpus", -] - -[[package]] -name = "tiff" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e" -dependencies = [ - "flate2", - "jpeg-decoder", - "weezl", -] - -[[package]] -name = "tiny-skia" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" -dependencies = [ - "arrayref", - "arrayvec", - "bytemuck", - "cfg-if", - "log", - "tiny-skia-path", -] - -[[package]] -name = "tiny-skia-path" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" -dependencies = [ - "arrayref", - "bytemuck", - "strict-num", -] - -[[package]] -name = "tinystr" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" -dependencies = [ - "displaydoc", - "zerovec", -] - -[[package]] -name = "toml" -version = "0.8.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit", -] - -[[package]] -name = "toml_datetime" -version = "0.6.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_edit" -version = "0.22.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" -dependencies = [ - "indexmap", - "serde", - "serde_spanned", - "toml_datetime", - "toml_write", - "winnow", -] - -[[package]] -name = "toml_write" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" - -[[package]] -name = "tracing" -version = "0.1.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" -dependencies = [ - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tracing-core" -version = "0.1.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" -dependencies = [ - "once_cell", -] - -[[package]] -name = "ttf-parser" -version = "0.25.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" - -[[package]] -name = "type-map" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb30dbbd9036155e74adad6812e9898d03ec374946234fbcebd5dfc7b9187b90" -dependencies = [ - "rustc-hash 2.1.1", -] - -[[package]] -name = "typenum" -version = "1.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" - -[[package]] -name = "uds_windows" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" -dependencies = [ - "memoffset", - "tempfile", - "winapi", -] - -[[package]] -name = "unicode-ident" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" - -[[package]] -name = "unicode-segmentation" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" - -[[package]] -name = "unicode-width" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" - -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - -[[package]] -name = "url" -version = "2.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", -] - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - -[[package]] -name = "uuid" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" -dependencies = [ - "getrandom 0.3.3", - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "walkdir" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] - -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - -[[package]] -name = "wasi" -version = "0.14.2+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" -dependencies = [ - "wit-bindgen-rt", -] - -[[package]] -name = "wasm-bindgen" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.50" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" -dependencies = [ - "cfg-if", - "js-sys", - "once_cell", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "wayland-backend" -version = "0.3.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe770181423e5fc79d3e2a7f4410b7799d5aab1de4372853de3c6aa13ca24121" -dependencies = [ - "cc", - "downcast-rs", - "rustix 0.38.44", - "scoped-tls", - "smallvec", - "wayland-sys", -] - -[[package]] -name = "wayland-client" -version = "0.31.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978fa7c67b0847dbd6a9f350ca2569174974cd4082737054dbb7fbb79d7d9a61" -dependencies = [ - "bitflags 2.9.1", - "rustix 0.38.44", - "wayland-backend", - "wayland-scanner", -] - -[[package]] -name = "wayland-csd-frame" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" -dependencies = [ - "bitflags 2.9.1", - "cursor-icon", - "wayland-backend", -] - -[[package]] -name = "wayland-cursor" -version = "0.31.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a65317158dec28d00416cb16705934070aef4f8393353d41126c54264ae0f182" -dependencies = [ - "rustix 0.38.44", - "wayland-client", - "xcursor", -] - -[[package]] -name = "wayland-protocols" -version = "0.32.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "779075454e1e9a521794fed15886323ea0feda3f8b0fc1390f5398141310422a" -dependencies = [ - "bitflags 2.9.1", - "wayland-backend", - "wayland-client", - "wayland-scanner", -] - -[[package]] -name = "wayland-protocols-plasma" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fd38cdad69b56ace413c6bcc1fbf5acc5e2ef4af9d5f8f1f9570c0c83eae175" -dependencies = [ - "bitflags 2.9.1", - "wayland-backend", - "wayland-client", - "wayland-protocols", - "wayland-scanner", -] - -[[package]] -name = "wayland-protocols-wlr" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cb6cdc73399c0e06504c437fe3cf886f25568dd5454473d565085b36d6a8bbf" -dependencies = [ - "bitflags 2.9.1", - "wayland-backend", - "wayland-client", - "wayland-protocols", - "wayland-scanner", -] - -[[package]] -name = "wayland-scanner" -version = "0.31.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "896fdafd5d28145fce7958917d69f2fd44469b1d4e861cb5961bcbeebc6d1484" -dependencies = [ - "proc-macro2", - "quick-xml 0.37.5", - "quote", -] - -[[package]] -name = "wayland-sys" -version = "0.31.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbcebb399c77d5aa9fa5db874806ee7b4eba4e73650948e8f93963f128896615" -dependencies = [ - "dlib", - "log", - "once_cell", - "pkg-config", -] - -[[package]] -name = "web-sys" -version = "0.3.77" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "web-time" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "webbrowser" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aaf4f3c0ba838e82b4e5ccc4157003fb8c324ee24c058470ffb82820becbde98" -dependencies = [ - "core-foundation 0.10.1", - "jni", - "log", - "ndk-context", - "objc2 0.6.1", - "objc2-foundation 0.3.1", - "url", - "web-sys", -] - -[[package]] -name = "weezl" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a751b3277700db47d3e574514de2eced5e54dc8a5436a3bf7a0b248b2cee16f3" - -[[package]] -name = "wgpu" -version = "24.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b0b3436f0729f6cdf2e6e9201f3d39dc95813fad61d826c1ed07918b4539353" -dependencies = [ - "arrayvec", - "bitflags 2.9.1", - "cfg_aliases", - "document-features", - "js-sys", - "log", - "parking_lot", - "profiling", - "raw-window-handle", - "smallvec", - "static_assertions", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "wgpu-core", - "wgpu-hal", - "wgpu-types", -] - -[[package]] -name = "wgpu-core" -version = "24.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f0aa306497a238d169b9dc70659105b4a096859a34894544ca81719242e1499" -dependencies = [ - "arrayvec", - "bit-vec", - "bitflags 2.9.1", - "cfg_aliases", - "document-features", - "indexmap", - "log", - "naga", - "once_cell", - "parking_lot", - "profiling", - "raw-window-handle", - "rustc-hash 1.1.0", - "smallvec", - "thiserror 2.0.12", - "wgpu-hal", - "wgpu-types", -] - -[[package]] -name = "wgpu-hal" -version = "24.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f112f464674ca69f3533248508ee30cb84c67cf06c25ff6800685f5e0294e259" -dependencies = [ - "android_system_properties", - "arrayvec", - "ash", - "bitflags 2.9.1", - "bytemuck", - "cfg_aliases", - "core-graphics-types", - "glow", - "glutin_wgl_sys", - "gpu-alloc", - "gpu-descriptor", - "js-sys", - "khronos-egl", - "libc", - "libloading", - "log", - "metal", - "naga", - "ndk-sys 0.5.0+25.2.9519653", - "objc", - "once_cell", - "ordered-float", - "parking_lot", - "profiling", - "raw-window-handle", - "renderdoc-sys", - "rustc-hash 1.1.0", - "smallvec", - "thiserror 2.0.12", - "wasm-bindgen", - "web-sys", - "wgpu-types", - "windows", -] - -[[package]] -name = "wgpu-types" -version = "24.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50ac044c0e76c03a0378e7786ac505d010a873665e2d51383dcff8dd227dc69c" -dependencies = [ - "bitflags 2.9.1", - "js-sys", - "log", - "web-sys", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" -dependencies = [ - "windows-sys 0.59.0", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" -dependencies = [ - "windows-core", - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-core" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-result", - "windows-strings", - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-implement" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "windows-interface" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "windows-result" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-strings" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" -dependencies = [ - "windows-result", - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.2", -] - -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" -dependencies = [ - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" - -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_i686_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" - -[[package]] -name = "winit" -version = "0.30.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4409c10174df8779dc29a4788cac85ed84024ccbc1743b776b21a520ee1aaf4" -dependencies = [ - "ahash", - "android-activity", - "atomic-waker", - "bitflags 2.9.1", - "block2", - "bytemuck", - "calloop", - "cfg_aliases", - "concurrent-queue", - "core-foundation 0.9.4", - "core-graphics", - "cursor-icon", - "dpi", - "js-sys", - "libc", - "memmap2", - "ndk", - "objc2 0.5.2", - "objc2-app-kit 0.2.2", - "objc2-foundation 0.2.2", - "objc2-ui-kit", - "orbclient", - "percent-encoding", - "pin-project", - "raw-window-handle", - "redox_syscall 0.4.1", - "rustix 0.38.44", - "sctk-adwaita", - "smithay-client-toolkit", - "smol_str", - "tracing", - "unicode-segmentation", - "wasm-bindgen", - "wasm-bindgen-futures", - "wayland-backend", - "wayland-client", - "wayland-protocols", - "wayland-protocols-plasma", - "web-sys", - "web-time", - "windows-sys 0.52.0", - "x11-dl", - "x11rb", - "xkbcommon-dl", -] - -[[package]] -name = "winnow" -version = "0.7.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd" -dependencies = [ - "memchr", -] - -[[package]] -name = "wit-bindgen-rt" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" -dependencies = [ - "bitflags 2.9.1", -] - -[[package]] -name = "writeable" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" - -[[package]] -name = "x11-dl" -version = "2.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" -dependencies = [ - "libc", - "once_cell", - "pkg-config", -] - -[[package]] -name = "x11rb" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12" -dependencies = [ - "as-raw-xcb-connection", - "gethostname", - "libc", - "libloading", - "once_cell", - "rustix 0.38.44", - "x11rb-protocol", -] - -[[package]] -name = "x11rb-protocol" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" - -[[package]] -name = "xcursor" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "635887f4315a33cb714eb059bdbd7c1c92bfa71bc5b9d5115460502f788c2ab5" - -[[package]] -name = "xdg-home" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec1cdab258fb55c0da61328dc52c8764709b249011b2cad0454c72f0bf10a1f6" -dependencies = [ - "libc", - "windows-sys 0.59.0", -] - -[[package]] -name = "xkbcommon-dl" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" -dependencies = [ - "bitflags 2.9.1", - "dlib", - "log", - "once_cell", - "xkeysym", -] - -[[package]] -name = "xkeysym" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" - -[[package]] -name = "xml-rs" -version = "0.8.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a62ce76d9b56901b19a74f19431b0d8b3bc7ca4ad685a746dfd78ca8f4fc6bda" - -[[package]] -name = "yoke" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" -dependencies = [ - "serde", - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "zbus" -version = "4.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb97012beadd29e654708a0fdb4c84bc046f537aecfde2c3ee0a9e4b4d48c725" -dependencies = [ - "async-broadcast", - "async-executor", - "async-fs", - "async-io", - "async-lock", - "async-process", - "async-recursion", - "async-task", - "async-trait", - "blocking", - "enumflags2", - "event-listener", - "futures-core", - "futures-sink", - "futures-util", - "hex", - "nix", - "ordered-stream", - "rand", - "serde", - "serde_repr", - "sha1", - "static_assertions", - "tracing", - "uds_windows", - "windows-sys 0.52.0", - "xdg-home", - "zbus_macros", - "zbus_names", - "zvariant", -] - -[[package]] -name = "zbus-lockstep" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca2c5dceb099bddaade154055c926bb8ae507a18756ba1d8963fd7b51d8ed1d" -dependencies = [ - "zbus_xml", - "zvariant", -] - -[[package]] -name = "zbus-lockstep-macros" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "709ab20fc57cb22af85be7b360239563209258430bccf38d8b979c5a2ae3ecce" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "zbus-lockstep", - "zbus_xml", - "zvariant", -] - -[[package]] -name = "zbus_macros" -version = "4.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "267db9407081e90bbfa46d841d3cbc60f59c0351838c4bc65199ecd79ab1983e" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn", - "zvariant_utils", -] - -[[package]] -name = "zbus_names" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c" -dependencies = [ - "serde", - "static_assertions", - "zvariant", -] - -[[package]] -name = "zbus_xml" -version = "4.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab3f374552b954f6abb4bd6ce979e6c9b38fb9d0cd7cc68a7d796e70c9f3a233" -dependencies = [ - "quick-xml 0.30.0", - "serde", - "static_assertions", - "zbus_names", - "zvariant", -] - -[[package]] -name = "zerocopy" -version = "0.8.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "zerofrom" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "zerotrie" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", -] - -[[package]] -name = "zerovec" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "zvariant" -version = "4.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2084290ab9a1c471c38fc524945837734fbf124487e105daec2bb57fd48c81fe" -dependencies = [ - "endi", - "enumflags2", - "serde", - "static_assertions", - "zvariant_derive", -] - -[[package]] -name = "zvariant_derive" -version = "4.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73e2ba546bda683a90652bac4a279bc146adad1386f25379cf73200d2002c449" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn", - "zvariant_utils", -] - -[[package]] -name = "zvariant_utils" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] diff --git a/c_compiler/example.c b/c_compiler/example.c new file mode 100644 index 0000000..7bc16ef --- /dev/null +++ b/c_compiler/example.c @@ -0,0 +1,12 @@ +int factorial(int n) { + if (n <= 1) { + return 1; + } + return n * factorial(n - 1); +} + +int main() { + int res = factorial(3); + print(res); + return 0; +} diff --git a/dsa_editor/Cargo.lock b/dsa_editor/Cargo.lock deleted file mode 100644 index 818de07..0000000 --- a/dsa_editor/Cargo.lock +++ /dev/null @@ -1,3985 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "ab_glyph" -version = "0.2.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec3672c180e71eeaaac3a541fbbc5f5ad4def8b747c595ad30d674e43049f7b0" -dependencies = [ - "ab_glyph_rasterizer", - "owned_ttf_parser", -] - -[[package]] -name = "ab_glyph_rasterizer" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" - -[[package]] -name = "accesskit" -version = "0.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3d3b8f9bae46a948369bc4a03e815d4ed6d616bd00de4051133a5019dc31c5a" - -[[package]] -name = "accesskit_atspi_common" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c5dd55e6e94949498698daf4d48fb5659e824d7abec0d394089656ceaf99d4f" -dependencies = [ - "accesskit", - "accesskit_consumer", - "atspi-common", - "serde", - "thiserror 1.0.69", - "zvariant", -] - -[[package]] -name = "accesskit_consumer" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f47983a1084940ba9a39c077a8c63e55c619388be5476ac04c804cfbd1e63459" -dependencies = [ - "accesskit", - "hashbrown", - "immutable-chunkmap", -] - -[[package]] -name = "accesskit_macos" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7329821f3bd1101e03a7d2e03bd339e3ac0dc64c70b4c9f9ae1949e3ba8dece1" -dependencies = [ - "accesskit", - "accesskit_consumer", - "hashbrown", - "objc2 0.5.2", - "objc2-app-kit 0.2.2", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "accesskit_unix" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcee751cc20d88678c33edaf9c07e8b693cd02819fe89053776f5313492273f5" -dependencies = [ - "accesskit", - "accesskit_atspi_common", - "async-channel", - "async-executor", - "async-task", - "atspi", - "futures-lite", - "futures-util", - "serde", - "zbus", -] - -[[package]] -name = "accesskit_windows" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24fcd5d23d70670992b823e735e859374d694a3d12bfd8dd32bd3bd8bedb5d81" -dependencies = [ - "accesskit", - "accesskit_consumer", - "hashbrown", - "paste", - "static_assertions", - "windows", - "windows-core", -] - -[[package]] -name = "accesskit_winit" -version = "0.23.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6a48dad5530b6deb9fc7a52cc6c3bf72cdd9eb8157ac9d32d69f2427a5e879" -dependencies = [ - "accesskit", - "accesskit_macos", - "accesskit_unix", - "accesskit_windows", - "raw-window-handle", - "winit", -] - -[[package]] -name = "adler2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" - -[[package]] -name = "ahash" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" -dependencies = [ - "cfg-if", - "getrandom 0.3.3", - "once_cell", - "version_check", - "zerocopy", -] - -[[package]] -name = "android-activity" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046" -dependencies = [ - "android-properties", - "bitflags 2.9.1", - "cc", - "cesu8", - "jni", - "jni-sys", - "libc", - "log", - "ndk", - "ndk-context", - "ndk-sys 0.6.0+11769913", - "num_enum", - "thiserror 1.0.69", -] - -[[package]] -name = "android-properties" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - -[[package]] -name = "arboard" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1df21f715862ede32a0c525ce2ca4d52626bb0007f8c18b87a384503ac33e70" -dependencies = [ - "clipboard-win", - "image", - "log", - "objc2 0.6.1", - "objc2-app-kit 0.3.1", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-foundation 0.3.1", - "parking_lot", - "percent-encoding", - "windows-sys 0.59.0", - "x11rb", -] - -[[package]] -name = "arrayref" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" - -[[package]] -name = "arrayvec" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" - -[[package]] -name = "as-raw-xcb-connection" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" - -[[package]] -name = "ash" -version = "0.38.0+1.3.281" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f" -dependencies = [ - "libloading", -] - -[[package]] -name = "async-broadcast" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" -dependencies = [ - "event-listener", - "event-listener-strategy", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-channel" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" -dependencies = [ - "concurrent-queue", - "event-listener-strategy", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-executor" -version = "1.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb812ffb58524bdd10860d7d974e2f01cc0950c2438a74ee5ec2e2280c6c4ffa" -dependencies = [ - "async-task", - "concurrent-queue", - "fastrand", - "futures-lite", - "pin-project-lite", - "slab", -] - -[[package]] -name = "async-fs" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebcd09b382f40fcd159c2d695175b2ae620ffa5f3bd6f664131efff4e8b9e04a" -dependencies = [ - "async-lock", - "blocking", - "futures-lite", -] - -[[package]] -name = "async-io" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1237c0ae75a0f3765f58910ff9cdd0a12eeb39ab2f4c7de23262f337f0aacbb3" -dependencies = [ - "async-lock", - "cfg-if", - "concurrent-queue", - "futures-io", - "futures-lite", - "parking", - "polling", - "rustix 1.0.7", - "slab", - "tracing", - "windows-sys 0.59.0", -] - -[[package]] -name = "async-lock" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" -dependencies = [ - "event-listener", - "event-listener-strategy", - "pin-project-lite", -] - -[[package]] -name = "async-process" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cde3f4e40e6021d7acffc90095cbd6dc54cb593903d1de5832f435eb274b85dc" -dependencies = [ - "async-channel", - "async-io", - "async-lock", - "async-signal", - "async-task", - "blocking", - "cfg-if", - "event-listener", - "futures-lite", - "rustix 1.0.7", - "tracing", -] - -[[package]] -name = "async-recursion" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "async-signal" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7605a4e50d4b06df3898d5a70bf5fde51ed9059b0434b73105193bc27acce0d" -dependencies = [ - "async-io", - "async-lock", - "atomic-waker", - "cfg-if", - "futures-core", - "futures-io", - "rustix 1.0.7", - "signal-hook-registry", - "slab", - "windows-sys 0.59.0", -] - -[[package]] -name = "async-task" -version = "4.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" - -[[package]] -name = "async-trait" -version = "0.1.88" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - -[[package]] -name = "atspi" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be534b16650e35237bb1ed189ba2aab86ce65e88cc84c66f4935ba38575cecbf" -dependencies = [ - "atspi-common", - "atspi-connection", - "atspi-proxies", -] - -[[package]] -name = "atspi-common" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1909ed2dc01d0a17505d89311d192518507e8a056a48148e3598fef5e7bb6ba7" -dependencies = [ - "enumflags2", - "serde", - "static_assertions", - "zbus", - "zbus-lockstep", - "zbus-lockstep-macros", - "zbus_names", - "zvariant", -] - -[[package]] -name = "atspi-connection" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "430c5960624a4baaa511c9c0fcc2218e3b58f5dbcc47e6190cafee344b873333" -dependencies = [ - "atspi-common", - "atspi-proxies", - "futures-lite", - "zbus", -] - -[[package]] -name = "atspi-proxies" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e6c5de3e524cf967569722446bcd458d5032348554d9a17d7d72b041ab7496" -dependencies = [ - "atspi-common", - "serde", - "zbus", - "zvariant", -] - -[[package]] -name = "autocfg" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" - -[[package]] -name = "bit-set" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" -dependencies = [ - "bit-vec", -] - -[[package]] -name = "bit-vec" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" -dependencies = [ - "serde", -] - -[[package]] -name = "block" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "block2" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" -dependencies = [ - "objc2 0.5.2", -] - -[[package]] -name = "blocking" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" -dependencies = [ - "async-channel", - "async-task", - "futures-io", - "futures-lite", - "piper", -] - -[[package]] -name = "bumpalo" -version = "3.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db76d6187cd04dff33004d8e6c9cc4e05cd330500379d2394209271b4aeee" - -[[package]] -name = "bytemuck" -version = "1.23.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422" -dependencies = [ - "bytemuck_derive", -] - -[[package]] -name = "bytemuck_derive" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ecc273b49b3205b83d648f0690daa588925572cc5063745bfe547fe7ec8e1a1" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "byteorder-lite" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" - -[[package]] -name = "bytes" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" - -[[package]] -name = "calloop" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" -dependencies = [ - "bitflags 2.9.1", - "log", - "polling", - "rustix 0.38.44", - "slab", - "thiserror 1.0.69", -] - -[[package]] -name = "calloop-wayland-source" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" -dependencies = [ - "calloop", - "rustix 0.38.44", - "wayland-backend", - "wayland-client", -] - -[[package]] -name = "cc" -version = "1.2.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" -dependencies = [ - "jobserver", - "libc", - "shlex", -] - -[[package]] -name = "cesu8" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - -[[package]] -name = "cfg-if" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" - -[[package]] -name = "cfg_aliases" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" - -[[package]] -name = "cgl" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ced0551234e87afee12411d535648dd89d2e7f34c78b753395567aff3d447ff" -dependencies = [ - "libc", -] - -[[package]] -name = "clipboard-win" -version = "5.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15efe7a882b08f34e38556b14f2fb3daa98769d06c7f0c1b076dfd0d983bc892" -dependencies = [ - "error-code", -] - -[[package]] -name = "codespan-reporting" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" -dependencies = [ - "termcolor", - "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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" -dependencies = [ - "bytes", - "memchr", -] - -[[package]] -name = "common_syntax" -version = "0.1.0" -dependencies = [ - "colorful", - "eframe", - "egui", - "serde", -] - -[[package]] -name = "concurrent-queue" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "core-foundation" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - -[[package]] -name = "core-graphics" -version = "0.23.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" -dependencies = [ - "bitflags 1.3.2", - "core-foundation 0.9.4", - "core-graphics-types", - "foreign-types", - "libc", -] - -[[package]] -name = "core-graphics-types" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" -dependencies = [ - "bitflags 1.3.2", - "core-foundation 0.9.4", - "libc", -] - -[[package]] -name = "cpufeatures" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" -dependencies = [ - "libc", -] - -[[package]] -name = "crc32fast" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" - -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "cursor-icon" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", -] - -[[package]] -name = "dispatch" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" - -[[package]] -name = "dispatch2" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" -dependencies = [ - "bitflags 2.9.1", - "objc2 0.6.1", -] - -[[package]] -name = "displaydoc" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "dlib" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" -dependencies = [ - "libloading", -] - -[[package]] -name = "document-features" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d" -dependencies = [ - "litrs", -] - -[[package]] -name = "downcast-rs" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" - -[[package]] -name = "dpi" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" - -[[package]] -name = "ecolor" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc4feb366740ded31a004a0e4452fbf84e80ef432ecf8314c485210229672fd1" -dependencies = [ - "bytemuck", - "emath", -] - -[[package]] -name = "eframe" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0dfe0859f3fb1bc6424c57d41e10e9093fe938f426b691e42272c2f336d915c" -dependencies = [ - "ahash", - "bytemuck", - "document-features", - "egui", - "egui-wgpu", - "egui-winit", - "egui_glow", - "glow", - "glutin", - "glutin-winit", - "image", - "js-sys", - "log", - "objc2 0.5.2", - "objc2-app-kit 0.2.2", - "objc2-foundation 0.2.2", - "parking_lot", - "percent-encoding", - "profiling", - "raw-window-handle", - "static_assertions", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "web-time", - "winapi", - "windows-sys 0.59.0", - "winit", -] - -[[package]] -name = "egui" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25dd34cec49ab55d85ebf70139cb1ccd29c977ef6b6ba4fe85489d6877ee9ef3" -dependencies = [ - "accesskit", - "ahash", - "bitflags 2.9.1", - "emath", - "epaint", - "log", - "nohash-hasher", - "profiling", -] - -[[package]] -name = "egui-wgpu" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d319dfef570f699b6e9114e235e862a2ddcf75f0d1a061de9e1328d92146d820" -dependencies = [ - "ahash", - "bytemuck", - "document-features", - "egui", - "epaint", - "log", - "profiling", - "thiserror 1.0.69", - "type-map", - "web-time", - "wgpu", - "winit", -] - -[[package]] -name = "egui-winit" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d9dfbb78fe4eb9c3a39ad528b90ee5915c252e77bbab9d4ebc576541ab67e13" -dependencies = [ - "accesskit_winit", - "ahash", - "arboard", - "bytemuck", - "egui", - "log", - "profiling", - "raw-window-handle", - "smithay-clipboard", - "web-time", - "webbrowser", - "winit", -] - -[[package]] -name = "egui_glow" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "910906e3f042ea6d2378ec12a6fd07698e14ddae68aed2d819ffe944a73aab9e" -dependencies = [ - "ahash", - "bytemuck", - "egui", - "glow", - "log", - "memoffset", - "profiling", - "wasm-bindgen", - "web-sys", - "winit", -] - -[[package]] -name = "emath" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e4cadcff7a5353ba72b7fea76bf2122b5ebdbc68e8155aa56dfdea90083fe1b" -dependencies = [ - "bytemuck", -] - -[[package]] -name = "endi" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" - -[[package]] -name = "enumflags2" -version = "0.7.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" -dependencies = [ - "enumflags2_derive", - "serde", -] - -[[package]] -name = "enumflags2_derive" -version = "0.7.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "epaint" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fcc0f5a7c613afd2dee5e4b30c3e6acafb8ad6f0edb06068811f708a67c562" -dependencies = [ - "ab_glyph", - "ahash", - "bytemuck", - "ecolor", - "emath", - "epaint_default_fonts", - "log", - "nohash-hasher", - "parking_lot", - "profiling", -] - -[[package]] -name = "epaint_default_fonts" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7e7a64c02cf7a5b51e745a9e45f60660a286f151c238b9d397b3e923f5082f" - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "errno" -version = "0.3.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" -dependencies = [ - "libc", - "windows-sys 0.59.0", -] - -[[package]] -name = "error-code" -version = "3.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" - -[[package]] -name = "event-listener" -version = "5.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] - -[[package]] -name = "event-listener-strategy" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" -dependencies = [ - "event-listener", - "pin-project-lite", -] - -[[package]] -name = "fastrand" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" - -[[package]] -name = "fdeflate" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" -dependencies = [ - "simd-adler32", -] - -[[package]] -name = "flate2" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" -dependencies = [ - "crc32fast", - "miniz_oxide", -] - -[[package]] -name = "foldhash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - -[[package]] -name = "foreign-types" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" -dependencies = [ - "foreign-types-macros", - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-macros" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "foreign-types-shared" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" - -[[package]] -name = "form_urlencoded" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "futures-core" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" - -[[package]] -name = "futures-io" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" - -[[package]] -name = "futures-lite" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" -dependencies = [ - "fastrand", - "futures-core", - "futures-io", - "parking", - "pin-project-lite", -] - -[[package]] -name = "futures-macro" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "futures-sink" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" - -[[package]] -name = "futures-task" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" - -[[package]] -name = "futures-util" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" -dependencies = [ - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "gethostname" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" -dependencies = [ - "libc", - "windows-targets 0.48.5", -] - -[[package]] -name = "getrandom" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.11.1+wasi-snapshot-preview1", -] - -[[package]] -name = "getrandom" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" -dependencies = [ - "cfg-if", - "libc", - "r-efi", - "wasi 0.14.2+wasi-0.2.4", -] - -[[package]] -name = "gl_generator" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" -dependencies = [ - "khronos_api", - "log", - "xml-rs", -] - -[[package]] -name = "glow" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5e5ea60d70410161c8bf5da3fdfeaa1c72ed2c15f8bbb9d19fe3a4fad085f08" -dependencies = [ - "js-sys", - "slotmap", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "glutin" -version = "0.32.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12124de845cacfebedff80e877bb37b5b75c34c5a4c89e47e1cdd67fb6041325" -dependencies = [ - "bitflags 2.9.1", - "cfg_aliases", - "cgl", - "dispatch2", - "glutin_egl_sys", - "glutin_glx_sys", - "glutin_wgl_sys", - "libloading", - "objc2 0.6.1", - "objc2-app-kit 0.3.1", - "objc2-core-foundation", - "objc2-foundation 0.3.1", - "once_cell", - "raw-window-handle", - "wayland-sys", - "windows-sys 0.52.0", - "x11-dl", -] - -[[package]] -name = "glutin-winit" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85edca7075f8fc728f28cb8fbb111a96c3b89e930574369e3e9c27eb75d3788f" -dependencies = [ - "cfg_aliases", - "glutin", - "raw-window-handle", - "winit", -] - -[[package]] -name = "glutin_egl_sys" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c4680ba6195f424febdc3ba46e7a42a0e58743f2edb115297b86d7f8ecc02d2" -dependencies = [ - "gl_generator", - "windows-sys 0.52.0", -] - -[[package]] -name = "glutin_glx_sys" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7bb2938045a88b612499fbcba375a77198e01306f52272e692f8c1f3751185" -dependencies = [ - "gl_generator", - "x11-dl", -] - -[[package]] -name = "glutin_wgl_sys" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c4ee00b289aba7a9e5306d57c2d05499b2e5dc427f84ac708bd2c090212cf3e" -dependencies = [ - "gl_generator", -] - -[[package]] -name = "gpu-alloc" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" -dependencies = [ - "bitflags 2.9.1", - "gpu-alloc-types", -] - -[[package]] -name = "gpu-alloc-types" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" -dependencies = [ - "bitflags 2.9.1", -] - -[[package]] -name = "gpu-descriptor" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca" -dependencies = [ - "bitflags 2.9.1", - "gpu-descriptor-types", - "hashbrown", -] - -[[package]] -name = "gpu-descriptor-types" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" -dependencies = [ - "bitflags 2.9.1", -] - -[[package]] -name = "hashbrown" -version = "0.15.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" -dependencies = [ - "foldhash", -] - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "hermit-abi" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "hexf-parse" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" - -[[package]] -name = "icu_collections" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" -dependencies = [ - "displaydoc", - "potential_utf", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locale_core" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_normalizer" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" - -[[package]] -name = "icu_properties" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_locale_core", - "icu_properties_data", - "icu_provider", - "potential_utf", - "zerotrie", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" - -[[package]] -name = "icu_provider" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" -dependencies = [ - "displaydoc", - "icu_locale_core", - "stable_deref_trait", - "tinystr", - "writeable", - "yoke", - "zerofrom", - "zerotrie", - "zerovec", -] - -[[package]] -name = "idna" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" -dependencies = [ - "icu_normalizer", - "icu_properties", -] - -[[package]] -name = "image" -version = "0.25.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db35664ce6b9810857a38a906215e75a9c879f0696556a39f59c62829710251a" -dependencies = [ - "bytemuck", - "byteorder-lite", - "num-traits", - "png", - "tiff", -] - -[[package]] -name = "immutable-chunkmap" -version = "2.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f97096f508d54f8f8ab8957862eee2ccd628847b6217af1a335e1c44dee578" -dependencies = [ - "arrayvec", -] - -[[package]] -name = "indexmap" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" -dependencies = [ - "equivalent", - "hashbrown", -] - -[[package]] -name = "jni" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" -dependencies = [ - "cesu8", - "cfg-if", - "combine", - "jni-sys", - "log", - "thiserror 1.0.69", - "walkdir", - "windows-sys 0.45.0", -] - -[[package]] -name = "jni-sys" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" - -[[package]] -name = "jobserver" -version = "0.1.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" -dependencies = [ - "getrandom 0.3.3", - "libc", -] - -[[package]] -name = "jpeg-decoder" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" - -[[package]] -name = "js-sys" -version = "0.3.77" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" -dependencies = [ - "once_cell", - "wasm-bindgen", -] - -[[package]] -name = "khronos-egl" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" -dependencies = [ - "libc", - "libloading", - "pkg-config", -] - -[[package]] -name = "khronos_api" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" - -[[package]] -name = "libc" -version = "0.2.173" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8cfeafaffdbc32176b64fb251369d52ea9f0a8fbc6f8759edffef7b525d64bb" - -[[package]] -name = "libloading" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" -dependencies = [ - "cfg-if", - "windows-targets 0.53.2", -] - -[[package]] -name = "libredox" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" -dependencies = [ - "bitflags 2.9.1", - "libc", - "redox_syscall 0.5.13", -] - -[[package]] -name = "linux-raw-sys" -version = "0.4.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" - -[[package]] -name = "linux-raw-sys" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" - -[[package]] -name = "litemap" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" - -[[package]] -name = "litrs" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" - -[[package]] -name = "lock_api" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" -dependencies = [ - "autocfg", - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" - -[[package]] -name = "malloc_buf" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" -dependencies = [ - "libc", -] - -[[package]] -name = "memchr" -version = "2.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" - -[[package]] -name = "memmap2" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" -dependencies = [ - "libc", -] - -[[package]] -name = "memoffset" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" -dependencies = [ - "autocfg", -] - -[[package]] -name = "metal" -version = "0.31.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f569fb946490b5743ad69813cb19629130ce9374034abe31614a36402d18f99e" -dependencies = [ - "bitflags 2.9.1", - "block", - "core-graphics-types", - "foreign-types", - "log", - "objc", - "paste", -] - -[[package]] -name = "miniz_oxide" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" -dependencies = [ - "adler2", - "simd-adler32", -] - -[[package]] -name = "naga" -version = "24.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e380993072e52eef724eddfcde0ed013b0c023c3f0417336ed041aa9f076994e" -dependencies = [ - "arrayvec", - "bit-set", - "bitflags 2.9.1", - "cfg_aliases", - "codespan-reporting", - "hexf-parse", - "indexmap", - "log", - "rustc-hash 1.1.0", - "spirv", - "strum", - "termcolor", - "thiserror 2.0.12", - "unicode-xid", -] - -[[package]] -name = "ndk" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" -dependencies = [ - "bitflags 2.9.1", - "jni-sys", - "log", - "ndk-sys 0.6.0+11769913", - "num_enum", - "raw-window-handle", - "thiserror 1.0.69", -] - -[[package]] -name = "ndk-context" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" - -[[package]] -name = "ndk-sys" -version = "0.5.0+25.2.9519653" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691" -dependencies = [ - "jni-sys", -] - -[[package]] -name = "ndk-sys" -version = "0.6.0+11769913" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" -dependencies = [ - "jni-sys", -] - -[[package]] -name = "nix" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" -dependencies = [ - "bitflags 2.9.1", - "cfg-if", - "cfg_aliases", - "libc", - "memoffset", -] - -[[package]] -name = "nohash-hasher" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - -[[package]] -name = "num_enum" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" -dependencies = [ - "num_enum_derive", -] - -[[package]] -name = "num_enum_derive" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "objc" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" -dependencies = [ - "malloc_buf", -] - -[[package]] -name = "objc-sys" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" - -[[package]] -name = "objc2" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" -dependencies = [ - "objc-sys", - "objc2-encode", -] - -[[package]] -name = "objc2" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88c6597e14493ab2e44ce58f2fdecf095a51f12ca57bec060a11c57332520551" -dependencies = [ - "objc2-encode", -] - -[[package]] -name = "objc2-app-kit" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" -dependencies = [ - "bitflags 2.9.1", - "block2", - "libc", - "objc2 0.5.2", - "objc2-core-data", - "objc2-core-image", - "objc2-foundation 0.2.2", - "objc2-quartz-core", -] - -[[package]] -name = "objc2-app-kit" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc" -dependencies = [ - "bitflags 2.9.1", - "objc2 0.6.1", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-foundation 0.3.1", -] - -[[package]] -name = "objc2-cloud-kit" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" -dependencies = [ - "bitflags 2.9.1", - "block2", - "objc2 0.5.2", - "objc2-core-location", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "objc2-contacts" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" -dependencies = [ - "block2", - "objc2 0.5.2", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "objc2-core-data" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" -dependencies = [ - "bitflags 2.9.1", - "block2", - "objc2 0.5.2", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "objc2-core-foundation" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" -dependencies = [ - "bitflags 2.9.1", - "dispatch2", - "objc2 0.6.1", -] - -[[package]] -name = "objc2-core-graphics" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "989c6c68c13021b5c2d6b71456ebb0f9dc78d752e86a98da7c716f4f9470f5a4" -dependencies = [ - "bitflags 2.9.1", - "dispatch2", - "objc2 0.6.1", - "objc2-core-foundation", - "objc2-io-surface", -] - -[[package]] -name = "objc2-core-image" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" -dependencies = [ - "block2", - "objc2 0.5.2", - "objc2-foundation 0.2.2", - "objc2-metal", -] - -[[package]] -name = "objc2-core-location" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" -dependencies = [ - "block2", - "objc2 0.5.2", - "objc2-contacts", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "objc2-encode" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" - -[[package]] -name = "objc2-foundation" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" -dependencies = [ - "bitflags 2.9.1", - "block2", - "dispatch", - "libc", - "objc2 0.5.2", -] - -[[package]] -name = "objc2-foundation" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c" -dependencies = [ - "bitflags 2.9.1", - "objc2 0.6.1", - "objc2-core-foundation", -] - -[[package]] -name = "objc2-io-surface" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7282e9ac92529fa3457ce90ebb15f4ecbc383e8338060960760fa2cf75420c3c" -dependencies = [ - "bitflags 2.9.1", - "objc2 0.6.1", - "objc2-core-foundation", -] - -[[package]] -name = "objc2-link-presentation" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" -dependencies = [ - "block2", - "objc2 0.5.2", - "objc2-app-kit 0.2.2", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "objc2-metal" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" -dependencies = [ - "bitflags 2.9.1", - "block2", - "objc2 0.5.2", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "objc2-quartz-core" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" -dependencies = [ - "bitflags 2.9.1", - "block2", - "objc2 0.5.2", - "objc2-foundation 0.2.2", - "objc2-metal", -] - -[[package]] -name = "objc2-symbols" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" -dependencies = [ - "objc2 0.5.2", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "objc2-ui-kit" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" -dependencies = [ - "bitflags 2.9.1", - "block2", - "objc2 0.5.2", - "objc2-cloud-kit", - "objc2-core-data", - "objc2-core-image", - "objc2-core-location", - "objc2-foundation 0.2.2", - "objc2-link-presentation", - "objc2-quartz-core", - "objc2-symbols", - "objc2-uniform-type-identifiers", - "objc2-user-notifications", -] - -[[package]] -name = "objc2-uniform-type-identifiers" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" -dependencies = [ - "block2", - "objc2 0.5.2", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "objc2-user-notifications" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" -dependencies = [ - "bitflags 2.9.1", - "block2", - "objc2 0.5.2", - "objc2-core-location", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "once_cell" -version = "1.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" - -[[package]] -name = "orbclient" -version = "0.3.48" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba0b26cec2e24f08ed8bb31519a9333140a6599b867dac464bb150bdb796fd43" -dependencies = [ - "libredox", -] - -[[package]] -name = "ordered-float" -version = "4.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951" -dependencies = [ - "num-traits", -] - -[[package]] -name = "ordered-stream" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" -dependencies = [ - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "owned_ttf_parser" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec719bbf3b2a81c109a4e20b1f129b5566b7dce654bc3872f6a05abf82b2c4" -dependencies = [ - "ttf-parser", -] - -[[package]] -name = "parking" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" - -[[package]] -name = "parking_lot" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall 0.5.13", - "smallvec", - "windows-targets 0.52.6", -] - -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - -[[package]] -name = "percent-encoding" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" - -[[package]] -name = "pin-project" -version = "1.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "piper" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" -dependencies = [ - "atomic-waker", - "fastrand", - "futures-io", -] - -[[package]] -name = "pkg-config" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" - -[[package]] -name = "png" -version = "0.17.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" -dependencies = [ - "bitflags 1.3.2", - "crc32fast", - "fdeflate", - "flate2", - "miniz_oxide", -] - -[[package]] -name = "polling" -version = "3.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b53a684391ad002dd6a596ceb6c74fd004fdce75f4be2e3f615068abbea5fd50" -dependencies = [ - "cfg-if", - "concurrent-queue", - "hermit-abi", - "pin-project-lite", - "rustix 1.0.7", - "tracing", - "windows-sys 0.59.0", -] - -[[package]] -name = "potential_utf" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" -dependencies = [ - "zerovec", -] - -[[package]] -name = "ppv-lite86" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] - -[[package]] -name = "proc-macro-crate" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" -dependencies = [ - "toml_edit", -] - -[[package]] -name = "proc-macro2" -version = "1.0.95" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "profiling" -version = "1.0.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afbdc74edc00b6f6a218ca6a5364d6226a259d4b8ea1af4a0ea063f27e179f4d" - -[[package]] -name = "quick-xml" -version = "0.30.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956" -dependencies = [ - "memchr", - "serde", -] - -[[package]] -name = "quick-xml" -version = "0.37.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" -dependencies = [ - "memchr", -] - -[[package]] -name = "quote" -version = "1.0.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "r-efi" -version = "5.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.16", -] - -[[package]] -name = "raw-window-handle" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" - -[[package]] -name = "redox_syscall" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "redox_syscall" -version = "0.5.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" -dependencies = [ - "bitflags 2.9.1", -] - -[[package]] -name = "renderdoc-sys" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" - -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - -[[package]] -name = "rustc-hash" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" - -[[package]] -name = "rustix" -version = "0.38.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" -dependencies = [ - "bitflags 2.9.1", - "errno", - "libc", - "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", -] - -[[package]] -name = "rustix" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" -dependencies = [ - "bitflags 2.9.1", - "errno", - "libc", - "linux-raw-sys 0.9.4", - "windows-sys 0.59.0", -] - -[[package]] -name = "rustversion" -version = "1.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "sctk-adwaita" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6277f0217056f77f1d8f49f2950ac6c278c0d607c45f5ee99328d792ede24ec" -dependencies = [ - "ab_glyph", - "log", - "memmap2", - "smithay-client-toolkit", - "tiny-skia", -] - -[[package]] -name = "serde" -version = "1.0.219" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.219" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_repr" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "sha1" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "signal-hook-registry" -version = "1.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" -dependencies = [ - "libc", -] - -[[package]] -name = "simd-adler32" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" - -[[package]] -name = "slab" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" - -[[package]] -name = "slotmap" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" -dependencies = [ - "version_check", -] - -[[package]] -name = "smallvec" -version = "1.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" - -[[package]] -name = "smithay-client-toolkit" -version = "0.19.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" -dependencies = [ - "bitflags 2.9.1", - "calloop", - "calloop-wayland-source", - "cursor-icon", - "libc", - "log", - "memmap2", - "rustix 0.38.44", - "thiserror 1.0.69", - "wayland-backend", - "wayland-client", - "wayland-csd-frame", - "wayland-cursor", - "wayland-protocols", - "wayland-protocols-wlr", - "wayland-scanner", - "xkeysym", -] - -[[package]] -name = "smithay-clipboard" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc8216eec463674a0e90f29e0ae41a4db573ec5b56b1c6c1c71615d249b6d846" -dependencies = [ - "libc", - "smithay-client-toolkit", - "wayland-backend", -] - -[[package]] -name = "smol_str" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" -dependencies = [ - "serde", -] - -[[package]] -name = "spirv" -version = "0.3.0+sdk-1.3.268.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" -dependencies = [ - "bitflags 2.9.1", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "strict-num" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" - -[[package]] -name = "strum" -version = "0.26.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" -dependencies = [ - "strum_macros", -] - -[[package]] -name = "strum_macros" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "rustversion", - "syn", -] - -[[package]] -name = "syn" -version = "2.0.103" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "synstructure" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tempfile" -version = "3.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" -dependencies = [ - "fastrand", - "getrandom 0.3.3", - "once_cell", - "rustix 1.0.7", - "windows-sys 0.59.0", -] - -[[package]] -name = "termcolor" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - -[[package]] -name = "thiserror" -version = "2.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" -dependencies = [ - "thiserror-impl 2.0.12", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tiff" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e" -dependencies = [ - "flate2", - "jpeg-decoder", - "weezl", -] - -[[package]] -name = "tiny-skia" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" -dependencies = [ - "arrayref", - "arrayvec", - "bytemuck", - "cfg-if", - "log", - "tiny-skia-path", -] - -[[package]] -name = "tiny-skia-path" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" -dependencies = [ - "arrayref", - "bytemuck", - "strict-num", -] - -[[package]] -name = "tinystr" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" -dependencies = [ - "displaydoc", - "zerovec", -] - -[[package]] -name = "toml_datetime" -version = "0.6.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" - -[[package]] -name = "toml_edit" -version = "0.22.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" -dependencies = [ - "indexmap", - "toml_datetime", - "winnow", -] - -[[package]] -name = "tracing" -version = "0.1.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" -dependencies = [ - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1ffbcf9c6f6b99d386e7444eb608ba646ae452a36b39737deb9663b610f662" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tracing-core" -version = "0.1.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" -dependencies = [ - "once_cell", -] - -[[package]] -name = "ttf-parser" -version = "0.25.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" - -[[package]] -name = "type-map" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb30dbbd9036155e74adad6812e9898d03ec374946234fbcebd5dfc7b9187b90" -dependencies = [ - "rustc-hash 2.1.1", -] - -[[package]] -name = "typenum" -version = "1.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" - -[[package]] -name = "uds_windows" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" -dependencies = [ - "memoffset", - "tempfile", - "winapi", -] - -[[package]] -name = "unicode-ident" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" - -[[package]] -name = "unicode-segmentation" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" - -[[package]] -name = "unicode-width" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" - -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - -[[package]] -name = "url" -version = "2.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", -] - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "walkdir" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] - -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - -[[package]] -name = "wasi" -version = "0.14.2+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" -dependencies = [ - "wit-bindgen-rt", -] - -[[package]] -name = "wasm-bindgen" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.50" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" -dependencies = [ - "cfg-if", - "js-sys", - "once_cell", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "wayland-backend" -version = "0.3.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe770181423e5fc79d3e2a7f4410b7799d5aab1de4372853de3c6aa13ca24121" -dependencies = [ - "cc", - "downcast-rs", - "rustix 0.38.44", - "scoped-tls", - "smallvec", - "wayland-sys", -] - -[[package]] -name = "wayland-client" -version = "0.31.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978fa7c67b0847dbd6a9f350ca2569174974cd4082737054dbb7fbb79d7d9a61" -dependencies = [ - "bitflags 2.9.1", - "rustix 0.38.44", - "wayland-backend", - "wayland-scanner", -] - -[[package]] -name = "wayland-csd-frame" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" -dependencies = [ - "bitflags 2.9.1", - "cursor-icon", - "wayland-backend", -] - -[[package]] -name = "wayland-cursor" -version = "0.31.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a65317158dec28d00416cb16705934070aef4f8393353d41126c54264ae0f182" -dependencies = [ - "rustix 0.38.44", - "wayland-client", - "xcursor", -] - -[[package]] -name = "wayland-protocols" -version = "0.32.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "779075454e1e9a521794fed15886323ea0feda3f8b0fc1390f5398141310422a" -dependencies = [ - "bitflags 2.9.1", - "wayland-backend", - "wayland-client", - "wayland-scanner", -] - -[[package]] -name = "wayland-protocols-plasma" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fd38cdad69b56ace413c6bcc1fbf5acc5e2ef4af9d5f8f1f9570c0c83eae175" -dependencies = [ - "bitflags 2.9.1", - "wayland-backend", - "wayland-client", - "wayland-protocols", - "wayland-scanner", -] - -[[package]] -name = "wayland-protocols-wlr" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cb6cdc73399c0e06504c437fe3cf886f25568dd5454473d565085b36d6a8bbf" -dependencies = [ - "bitflags 2.9.1", - "wayland-backend", - "wayland-client", - "wayland-protocols", - "wayland-scanner", -] - -[[package]] -name = "wayland-scanner" -version = "0.31.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "896fdafd5d28145fce7958917d69f2fd44469b1d4e861cb5961bcbeebc6d1484" -dependencies = [ - "proc-macro2", - "quick-xml 0.37.5", - "quote", -] - -[[package]] -name = "wayland-sys" -version = "0.31.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbcebb399c77d5aa9fa5db874806ee7b4eba4e73650948e8f93963f128896615" -dependencies = [ - "dlib", - "log", - "once_cell", - "pkg-config", -] - -[[package]] -name = "web-sys" -version = "0.3.77" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "web-time" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "webbrowser" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aaf4f3c0ba838e82b4e5ccc4157003fb8c324ee24c058470ffb82820becbde98" -dependencies = [ - "core-foundation 0.10.1", - "jni", - "log", - "ndk-context", - "objc2 0.6.1", - "objc2-foundation 0.3.1", - "url", - "web-sys", -] - -[[package]] -name = "weezl" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a751b3277700db47d3e574514de2eced5e54dc8a5436a3bf7a0b248b2cee16f3" - -[[package]] -name = "wgpu" -version = "24.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b0b3436f0729f6cdf2e6e9201f3d39dc95813fad61d826c1ed07918b4539353" -dependencies = [ - "arrayvec", - "bitflags 2.9.1", - "cfg_aliases", - "document-features", - "js-sys", - "log", - "parking_lot", - "profiling", - "raw-window-handle", - "smallvec", - "static_assertions", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "wgpu-core", - "wgpu-hal", - "wgpu-types", -] - -[[package]] -name = "wgpu-core" -version = "24.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f0aa306497a238d169b9dc70659105b4a096859a34894544ca81719242e1499" -dependencies = [ - "arrayvec", - "bit-vec", - "bitflags 2.9.1", - "cfg_aliases", - "document-features", - "indexmap", - "log", - "naga", - "once_cell", - "parking_lot", - "profiling", - "raw-window-handle", - "rustc-hash 1.1.0", - "smallvec", - "thiserror 2.0.12", - "wgpu-hal", - "wgpu-types", -] - -[[package]] -name = "wgpu-hal" -version = "24.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f112f464674ca69f3533248508ee30cb84c67cf06c25ff6800685f5e0294e259" -dependencies = [ - "android_system_properties", - "arrayvec", - "ash", - "bitflags 2.9.1", - "bytemuck", - "cfg_aliases", - "core-graphics-types", - "glow", - "glutin_wgl_sys", - "gpu-alloc", - "gpu-descriptor", - "js-sys", - "khronos-egl", - "libc", - "libloading", - "log", - "metal", - "naga", - "ndk-sys 0.5.0+25.2.9519653", - "objc", - "once_cell", - "ordered-float", - "parking_lot", - "profiling", - "raw-window-handle", - "renderdoc-sys", - "rustc-hash 1.1.0", - "smallvec", - "thiserror 2.0.12", - "wasm-bindgen", - "web-sys", - "wgpu-types", - "windows", -] - -[[package]] -name = "wgpu-types" -version = "24.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50ac044c0e76c03a0378e7786ac505d010a873665e2d51383dcff8dd227dc69c" -dependencies = [ - "bitflags 2.9.1", - "js-sys", - "log", - "web-sys", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" -dependencies = [ - "windows-sys 0.59.0", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" -dependencies = [ - "windows-core", - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-core" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-result", - "windows-strings", - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-implement" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "windows-interface" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "windows-result" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-strings" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" -dependencies = [ - "windows-result", - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" -dependencies = [ - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" - -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_i686_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" - -[[package]] -name = "winit" -version = "0.30.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4409c10174df8779dc29a4788cac85ed84024ccbc1743b776b21a520ee1aaf4" -dependencies = [ - "ahash", - "android-activity", - "atomic-waker", - "bitflags 2.9.1", - "block2", - "bytemuck", - "calloop", - "cfg_aliases", - "concurrent-queue", - "core-foundation 0.9.4", - "core-graphics", - "cursor-icon", - "dpi", - "js-sys", - "libc", - "memmap2", - "ndk", - "objc2 0.5.2", - "objc2-app-kit 0.2.2", - "objc2-foundation 0.2.2", - "objc2-ui-kit", - "orbclient", - "percent-encoding", - "pin-project", - "raw-window-handle", - "redox_syscall 0.4.1", - "rustix 0.38.44", - "sctk-adwaita", - "smithay-client-toolkit", - "smol_str", - "tracing", - "unicode-segmentation", - "wasm-bindgen", - "wasm-bindgen-futures", - "wayland-backend", - "wayland-client", - "wayland-protocols", - "wayland-protocols-plasma", - "web-sys", - "web-time", - "windows-sys 0.52.0", - "x11-dl", - "x11rb", - "xkbcommon-dl", -] - -[[package]] -name = "winnow" -version = "0.7.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd" -dependencies = [ - "memchr", -] - -[[package]] -name = "wit-bindgen-rt" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" -dependencies = [ - "bitflags 2.9.1", -] - -[[package]] -name = "writeable" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" - -[[package]] -name = "x11-dl" -version = "2.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" -dependencies = [ - "libc", - "once_cell", - "pkg-config", -] - -[[package]] -name = "x11rb" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12" -dependencies = [ - "as-raw-xcb-connection", - "gethostname", - "libc", - "libloading", - "once_cell", - "rustix 0.38.44", - "x11rb-protocol", -] - -[[package]] -name = "x11rb-protocol" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" - -[[package]] -name = "xcursor" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ef33da6b1660b4ddbfb3aef0ade110c8b8a781a3b6382fa5f2b5b040fd55f61" - -[[package]] -name = "xdg-home" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec1cdab258fb55c0da61328dc52c8764709b249011b2cad0454c72f0bf10a1f6" -dependencies = [ - "libc", - "windows-sys 0.59.0", -] - -[[package]] -name = "xkbcommon-dl" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" -dependencies = [ - "bitflags 2.9.1", - "dlib", - "log", - "once_cell", - "xkeysym", -] - -[[package]] -name = "xkeysym" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" - -[[package]] -name = "xml-rs" -version = "0.8.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a62ce76d9b56901b19a74f19431b0d8b3bc7ca4ad685a746dfd78ca8f4fc6bda" - -[[package]] -name = "yoke" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" -dependencies = [ - "serde", - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "zbus" -version = "4.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb97012beadd29e654708a0fdb4c84bc046f537aecfde2c3ee0a9e4b4d48c725" -dependencies = [ - "async-broadcast", - "async-executor", - "async-fs", - "async-io", - "async-lock", - "async-process", - "async-recursion", - "async-task", - "async-trait", - "blocking", - "enumflags2", - "event-listener", - "futures-core", - "futures-sink", - "futures-util", - "hex", - "nix", - "ordered-stream", - "rand", - "serde", - "serde_repr", - "sha1", - "static_assertions", - "tracing", - "uds_windows", - "windows-sys 0.52.0", - "xdg-home", - "zbus_macros", - "zbus_names", - "zvariant", -] - -[[package]] -name = "zbus-lockstep" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca2c5dceb099bddaade154055c926bb8ae507a18756ba1d8963fd7b51d8ed1d" -dependencies = [ - "zbus_xml", - "zvariant", -] - -[[package]] -name = "zbus-lockstep-macros" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "709ab20fc57cb22af85be7b360239563209258430bccf38d8b979c5a2ae3ecce" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "zbus-lockstep", - "zbus_xml", - "zvariant", -] - -[[package]] -name = "zbus_macros" -version = "4.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "267db9407081e90bbfa46d841d3cbc60f59c0351838c4bc65199ecd79ab1983e" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn", - "zvariant_utils", -] - -[[package]] -name = "zbus_names" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c" -dependencies = [ - "serde", - "static_assertions", - "zvariant", -] - -[[package]] -name = "zbus_xml" -version = "4.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab3f374552b954f6abb4bd6ce979e6c9b38fb9d0cd7cc68a7d796e70c9f3a233" -dependencies = [ - "quick-xml 0.30.0", - "serde", - "static_assertions", - "zbus_names", - "zvariant", -] - -[[package]] -name = "zerocopy" -version = "0.8.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "zerofrom" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "zerotrie" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", -] - -[[package]] -name = "zerovec" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "zvariant" -version = "4.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2084290ab9a1c471c38fc524945837734fbf124487e105daec2bb57fd48c81fe" -dependencies = [ - "endi", - "enumflags2", - "serde", - "static_assertions", - "zvariant_derive", -] - -[[package]] -name = "zvariant_derive" -version = "4.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73e2ba546bda683a90652bac4a279bc146adad1386f25379cf73200d2002c449" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn", - "zvariant_utils", -] - -[[package]] -name = "zvariant_utils" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] -- 2.47.3 From 63c9d858b4e98f8102af924f1dca8d4c6e385c51 Mon Sep 17 00:00:00 2001 From: zxq5 Date: Sat, 31 Jan 2026 13:27:31 +0000 Subject: [PATCH 07/53] added a to-do list and bacon.toml for developing the compiler --- c_compiler/bacon.toml | 129 +++++++++++++++++++++++++++++++++++++++ resources/ideas/TODOS.md | 0 2 files changed, 129 insertions(+) create mode 100644 c_compiler/bacon.toml create mode 100644 resources/ideas/TODOS.md diff --git a/c_compiler/bacon.toml b/c_compiler/bacon.toml new file mode 100644 index 0000000..87af4fa --- /dev/null +++ b/c_compiler/bacon.toml @@ -0,0 +1,129 @@ +# This is a configuration file for the bacon tool +# +# Complete help on configuration: https://dystroy.org/bacon/config/ +# +# You may check the current default at +# https://github.com/Canop/bacon/blob/main/defaults/default-bacon.toml + +default_job = "check" + +[jobs.check] +command = ["cargo", "check", "--color", "always"] +need_stdout = false + +[jobs.check-all] +command = ["cargo", "check", "--all-targets", "--color", "always"] +need_stdout = false + +# Run clippy on the default target +[jobs.clippy] +command = [ + "cargo", "clippy", + "--color", "always", +] +need_stdout = false + +# Run clippy on all targets +# To disable some lints, you may change the job this way: +# [jobs.clippy-all] +# command = [ +# "cargo", "clippy", +# "--all-targets", +# "--color", "always", +# "--", +# "-A", "clippy::bool_to_int_with_if", +# "-A", "clippy::collapsible_if", +# "-A", "clippy::derive_partial_eq_without_eq", +# ] +# need_stdout = false +[jobs.clippy-all] +command = [ + "cargo", "clippy", + "--all-targets", + "--color", "always", +] +need_stdout = false + +# This job lets you run +# - all tests: bacon test +# - a specific test: bacon test -- config::test_default_files +# - the tests of a package: bacon test -- -- -p config +[jobs.test] +command = [ + "cargo", "test", "--color", "always", + "--", "--color", "always", # see https://github.com/Canop/bacon/issues/124 +] +need_stdout = true + +[jobs.nextest] +command = [ + "cargo", "nextest", "run", + "--color", "always", + "--hide-progress-bar", "--failure-output", "final" +] +need_stdout = true +analyzer = "nextest" + +[jobs.doc] +command = ["cargo", "doc", "--color", "always", "--no-deps"] +need_stdout = false + +# If the doc compiles, then it opens in your browser and bacon switches +# to the previous job +[jobs.doc-open] +command = ["cargo", "doc", "--color", "always", "--no-deps", "--open"] +need_stdout = false +on_success = "back" # so that we don't open the browser at each change + +# You can run your application and have the result displayed in bacon, +# if it makes sense for this crate. +# Don't forget the `--color always` part or the errors won't be +# properly parsed. +[jobs.run] +command = [ + "cargo", "run", + "--color", "always", + "--", + "example.dsc", + "../resources/dsa/code.dsa" + # put launch parameters for your program behind a `--` separator +] +need_stdout = true +allow_warnings = true +background = true + +# Run your long-running application (eg server) and have the result displayed in bacon. +# For programs that never stop (eg a server), `background` is set to false +# to have the cargo run output immediately displayed instead of waiting for +# program's end. +# 'on_change_strategy' is set to `kill_then_restart` to have your program restart +# on every change (an alternative would be to use the 'F5' key manually in bacon). +# If you often use this job, it makes sense to override the 'r' key by adding +# a binding `r = job:run-long` at the end of this file . +[jobs.run-long] +command = [ + "cargo", "run", + "--color", "always", + # put launch parameters for your program behind a `--` separator +] +need_stdout = true +allow_warnings = true +background = false +on_change_strategy = "kill_then_restart" + +# This parameterized job runs the example of your choice, as soon +# as the code compiles. +# Call it as +# bacon ex -- my-example +[jobs.ex] +command = ["cargo", "run", "--color", "always", "--example"] +need_stdout = true +allow_warnings = true + +# You may define here keybindings that would be specific to +# a project, for example a shortcut to launch a specific job. +# Shortcuts to internal functions (scrolling, toggling, etc.) +# should go in your personal global prefs.toml file instead. +[keybindings] +# alt-m = "job:my-job" +c = "job:clippy-all" # comment this to have 'c' run clippy on only the default target diff --git a/resources/ideas/TODOS.md b/resources/ideas/TODOS.md new file mode 100644 index 0000000..e69de29 -- 2.47.3 From e31deb594ff168929577f08109a2b9b220aba119 Mon Sep 17 00:00:00 2001 From: zxq5 Date: Sat, 31 Jan 2026 13:28:11 +0000 Subject: [PATCH 08/53] fixed a bug with the multiply function in core.dsa and added a print_num function to print.dsa for decimal numbers --- resources/dsa/lib/io/print.dsa | 64 ++++++++++++++++++++++++++++++++ resources/dsa/lib/maths/core.dsa | 1 + 2 files changed, 65 insertions(+) diff --git a/resources/dsa/lib/io/print.dsa b/resources/dsa/lib/io/print.dsa index 377d438..43b2e83 100644 --- a/resources/dsa/lib/io/print.dsa +++ b/resources/dsa/lib/io/print.dsa @@ -28,6 +28,11 @@ // push pcx // jmp print::print_word // +// usage for print_num: +// push (register containing number to print in decimal) +// push pcx +// jmp print::print_num +// include maths "../maths/core.dsa" @@ -209,6 +214,65 @@ print_newline: // _end saves the display state jmp _end +// ------------------------------------------ +// prints arg[0] as a decimal number to the screen. +print_num: + push bpr + mov spr, bpr + + ldw bpr, rg0, 8 // load number to print + lli 0, rg5 // rg5 = digit counter + + // check if number is zero + cmp rg0, zero + jne _print_num_extract_digits + + // special case: print '0' for zero + lli 0x30, rg6 + push rg6 // push digit to stack buffer + lli 1, rg5 // we have 1 digit + jmp _print_num_output + +_print_num_extract_digits: + // divide by 10 repeatedly to get digits + cmp rg0, zero + jeq _print_num_output + + // call divmod(rg0, 10) + push rg0 // dividend + lli 10, rg1 + push rg1 // divisor (10) + call maths::divmod + pop rg0 // quotient (continue dividing this) + pop rg1 // remainder (the digit) + + // convert digit to ASCII and push to stack buffer + addi rg1, 0x30, rg6 // convert to ASCII + push rg6 // push digit to stack + inc rg5 // increment digit counter + + jmp _print_num_extract_digits + +_print_num_output: + // now print digits (pop them off in reverse order) + ldw current, rg1 // get display pointer + +_print_num_output_loop: + // check if we've printed all digits + cmp rg5, zero + jeq _print_num_done + + // pop digit and print it + pop rg6 + stb rg6, rg1 + addi rg1, 1 + dec rg5 + + jmp _print_num_output_loop + +_print_num_done: + jmp _end + // ------------------------------------------ // resets the cursor position on the screen to 0x20000. (0,0) reset: diff --git a/resources/dsa/lib/maths/core.dsa b/resources/dsa/lib/maths/core.dsa index 217c5dc..25a2574 100644 --- a/resources/dsa/lib/maths/core.dsa +++ b/resources/dsa/lib/maths/core.dsa @@ -16,6 +16,7 @@ multiply: ldw bpr, rg0, 8 // load op 2 ldw bpr, rg1, 12 // load op 1 + lwi 0, rg2 // initialise rg2 to zero _multiply_loop: add rg2, rg0, rg2 -- 2.47.3 From 52ef7872f0cf6a1af08359fbf63b200c04d4387f Mon Sep 17 00:00:00 2001 From: zxq5 Date: Sat, 31 Jan 2026 13:28:42 +0000 Subject: [PATCH 09/53] compiler working for some mathematical expressions, function calls and simple conditionals --- c_compiler/example.c | 2 +- c_compiler/example.dsc | 27 +++++++ c_compiler/src/assembly.rs | 106 +++++++++++++++++++++++++++ c_compiler/src/codegen.rs | 96 ++++++++++++++++--------- c_compiler/src/lexer.rs | 70 ++++++++++++++++++ c_compiler/src/main.rs | 3 + c_compiler/src/parser.rs | 30 ++++++++ c_compiler/src/registers.rs | 20 ++++++ resources/dsa/code.dsa | 139 ++++++++++++++++++++++++++++++++++++ resources/dsa/main.dsa | 52 ++------------ 10 files changed, 466 insertions(+), 79 deletions(-) create mode 100644 c_compiler/example.dsc create mode 100644 c_compiler/src/assembly.rs create mode 100644 resources/dsa/code.dsa diff --git a/c_compiler/example.c b/c_compiler/example.c index 7bc16ef..1182420 100644 --- a/c_compiler/example.c +++ b/c_compiler/example.c @@ -7,6 +7,6 @@ int factorial(int n) { int main() { int res = factorial(3); - print(res); + printnum(res); return 0; } diff --git a/c_compiler/example.dsc b/c_compiler/example.dsc new file mode 100644 index 0000000..1582321 --- /dev/null +++ b/c_compiler/example.dsc @@ -0,0 +1,27 @@ +include print: "lib/io/print.dsa" + +int factorial(int n) { + if (n <= 1) { + return 1; + } + return n * factorial(n - 1); +} + +int add_(int a, int b) { + return a + b; +} + +int greater(int a, int b) { + if (a + a > b + b) { + return a; + } else { + return b + a; + } +} + +int main() { + printnum(greater(5, add_(5, 5))); + + printnum(factorial(5)); + return 0; +} diff --git a/c_compiler/src/assembly.rs b/c_compiler/src/assembly.rs new file mode 100644 index 0000000..4b46a4f --- /dev/null +++ b/c_compiler/src/assembly.rs @@ -0,0 +1,106 @@ +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[non_exhaustive] +pub enum Register { + // general purpose registers + Rg0, + Rg1, + Rg2, + Rg3, + Rg4, + Rg5, + Rg6, + Rg7, + Rg8, + Rg9, + Rga, + Rgb, + Rgc, + Rgd, + Rge, + Rgf, + + // special purpose registers + Acc, + Spr, + Bpr, + Ret, + Idr, + Mmr, + Zero, + NoReg, + + // system registers - can't be written to by instructions. + Mar, + Mdr, + Sts, + Cir, + Pcx, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +#[non_exhaustive] +/// A list of all current instructions in the DSA Assembly language. +pub enum Instruction { + // No-op + Nop = 0x0, + + // Data transfer instructions + Mov(Register, Register) = 0x1, + Movs(Register, Register) = 0x2, + + Ldb(Register, Register, Option) = 0x3, + Ldbs(Register, Register, Option) = 0x4, + Ldh(Register, Register, Option) = 0x5, + Ldhs(Register, Register, Option) = 0x6, + Ldw(Register, Register, Option) = 0x7, + + Stb(Register, Register, Option) = 0x8, + Sth(Register, Register, Option) = 0x9, + Stw(Register, Register, Option) = 0xA, + + Lli(u16, Register) = 0xB, + Lui(u16, Register) = 0xC, + + // Jump Instructions + Jump(u16, Register) = 0xD, + JumpEq(u16, Register) = 0xE, + JumpNeq(u16, Register) = 0xF, + JumpGt(u16, Register) = 0x10, + JumpGe(u16, Register) = 0x11, + JumpLt(u16, Register) = 0x12, + JumpLe(u16, Register) = 0x13, + + // Comparison + Compare(Register, Register) = 0x14, + + // // Arithmetic + // Add(args::RTypeArgs) = 0x19, + // Sub(args::RTypeArgs) = 0x1A, + // Increment(args::RTypeArgs) = 0x15, + // Decrement(args::RTypeArgs) = 0x16, + // ShiftLeft(args::RTypeArgs) = 0x17, + // ShiftRight(args::RTypeArgs) = 0x18, + + // // Logical + // And(args::RTypeArgs) = 0x1B, + // Or(args::RTypeArgs) = 0x1C, + // Not(args::RTypeArgs) = 0x1D, + // Xor(args::RTypeArgs) = 0x1E, + // Nand(args::RTypeArgs) = 0x1F, + // Nor(args::RTypeArgs) = 0x20, + // Xnor(args::RTypeArgs) = 0x21, + + // // Misc + // Interrupt(Interrupt) = 0x22, + // IntReturn = 0x23, + // Halt = 0x24, + + // // Immediate Arithmetic + // AddImmediate(args::ITypeArgs) = 0x25, + // SubImmediate(args::ITypeArgs) = 0x26, + + // Fake Instructions + Data(u32) = 0x3E, + Segment(u32) = 0x3F, +} diff --git a/c_compiler/src/codegen.rs b/c_compiler/src/codegen.rs index 308fe43..d5bbb66 100644 --- a/c_compiler/src/codegen.rs +++ b/c_compiler/src/codegen.rs @@ -1,4 +1,5 @@ use std::hash::Hash; +use std::sync::LazyLock; use std::sync::atomic::AtomicU32; use std::time::SystemTime; use std::{collections::HashMap, path::PathBuf}; @@ -18,23 +19,32 @@ pub struct CodeGenerator { imports: HashMap, globals: Vec, functions: Vec, + symbols: Vec, allocator: RegisterAllocator, - call_stack: Vec, } +static GLOBAL_METHODS: LazyLock> = LazyLock::new(|| { + hash_map! { + "print" => "print::print", + "printnum" => "print::print_num" + } +}); + fn import(name: &str, path: &str) -> String { format!("include {name}: \"{}\"", path) } impl CodeGenerator { + const RET: &'static str = "\tjmp _ret"; + pub fn new(ast: Program) -> Self { CodeGenerator { ast, imports: HashMap::new(), globals: Vec::new(), functions: Vec::new(), + symbols: Vec::new(), allocator: RegisterAllocator::new(), - call_stack: Vec::new(), } } @@ -47,11 +57,15 @@ impl CodeGenerator { self.include("print", "./lib/io/print.dsa"); for block in self.ast.clone().declarations { - self.generate_block(block.clone())?; + match block { + Declaration::Variable { name, .. } => self.symbols.push(name), + Declaration::Function { name, .. } => self.symbols.push(name), + Declaration::Import { name, .. } => self.symbols.push(name), + } } - for func in &self.functions { - println!("{func}"); + for block in self.ast.clone().declarations { + self.generate_block(block.clone())?; } self.generate_layout() @@ -88,6 +102,7 @@ impl CodeGenerator { dsa![mov bpr, spr], dsa![push zero], dsa![call main], + dsa![call print::print_newline], dsa![lwi message, rg0], dsa![push rg0], dsa![call print::print], @@ -96,6 +111,13 @@ impl CodeGenerator { dsa![pop zero], dsa![hlt] ], + "", + comment!("Function return boilerplate"), + block! [ "_ret" + dsa![mov bpr, spr], + dsa![pop bpr], + dsa![return] + ], // block! [ "main" // dsa![push bpr], // dsa![mov spr, bpr], @@ -131,6 +153,9 @@ impl CodeGenerator { self.functions.push(format!("{func}\n")); } + Declaration::Import { name, path } => { + self.imports.insert(name, path); + } }; Ok(()) @@ -143,8 +168,6 @@ impl CodeGenerator { params: &[Parameter], body: &[Statement], ) -> Vec { - self.call_stack.push(name.to_string()); - let mut code = Vec::new(); // Reset allocator for new function @@ -154,6 +177,7 @@ impl CodeGenerator { code.push(format!("{}:", name)); code.push("\tpush bpr".to_string()); code.push("\tmov spr, bpr".to_string()); + code.push(String::new()); // Allocate parameters to registers or stack locations for (i, param) in params.iter().enumerate() { @@ -170,13 +194,14 @@ impl CodeGenerator { code.extend(stmt_code); } - // Function epilogue - code.push(format!("_ret_{name}:")); - code.push("\tmov bpr, spr".to_string()); - code.push("\tpop bpr".to_string()); - code.push("\treturn".to_string()); + // automatically return at function end + if let Some(x) = code.last() + && x == Self::RET + { + } else { + code.push(Self::RET.to_string()); + } - self.call_stack.pop(); code } @@ -212,7 +237,7 @@ impl CodeGenerator { let (result_reg, expr_code) = self.generate_expression(e)?; code.extend(expr_code); code.push(format!("\tstw {}, bpr, 8", result_reg)); - code.push(format!("\tjmp _ret_{}", self.call_stack.last().unwrap())); + code.push(format!("\tjmp _ret")); self.allocator.free_temp(&result_reg); } } @@ -419,7 +444,7 @@ impl CodeGenerator { _ => return Err(format!("Unsupported binary operator: {:?}", op)), } - // Free operand registers + // Free operand registers (allocator will protect variables) self.allocator.free_temp(&left_reg); self.allocator.free_temp(&right_reg); @@ -427,9 +452,11 @@ impl CodeGenerator { } Expression::Call { name, args } => { - // Save caller-saved registers - let save_code = self.allocator.save_caller_saved(); - code.extend(save_code); + // Save caller-saved registers and track which ones we saved + let saved_regs = self.allocator.get_caller_saved_registers(); + for reg in &saved_regs { + code.push(format!("\tpush {}", reg)); + } // Evaluate and push arguments in reverse order let mut arg_regs = Vec::new(); @@ -440,18 +467,31 @@ impl CodeGenerator { arg_regs.push(arg_reg); } - if self.functions.contains_key(name) { + if GLOBAL_METHODS.contains_key(name.as_str()) { + code.push(format!("\tcall {}", GLOBAL_METHODS[name.as_str()])); + } else if self.symbols.contains(name) { // Call local function code.push(format!("\tcall {}", name)); + } else { + return Err(format!("undefined function {name}")); } - if self.imports - + // Result is in rg0, allocate a register and move it + let (result_reg, result_alloc) = self.allocator.alloc_temp()?; + code.extend(result_alloc); + code.push(format!("\tpop {}", result_reg)); // Clean up arguments - for _ in 0..args.len() { - code.push("\tpop zero".to_string()); + if args.len() > 1 { + for _ in 0..(args.len() - 1) { + code.push("\tpop zero".to_string()); + } + } + + // Restore caller-saved registers in reverse order (LIFO) + for reg in saved_regs.iter().rev() { + code.push(format!("\tpop {}", reg)); } // Free argument registers @@ -459,16 +499,6 @@ impl CodeGenerator { self.allocator.free_temp(®); } - // Result is in rg0, allocate a register and move it - let (result_reg, result_alloc) = self.allocator.alloc_temp()?; - code.extend(result_alloc); - - if result_reg != "rg0" { - code.push(format!("\tmov rg0, {}", result_reg)); - } - - // Restore caller-saved registers (simplified - you'd track which ones) - Ok((result_reg, code)) } diff --git a/c_compiler/src/lexer.rs b/c_compiler/src/lexer.rs index f6ccc74..60cf402 100644 --- a/c_compiler/src/lexer.rs +++ b/c_compiler/src/lexer.rs @@ -10,10 +10,13 @@ pub enum TokenType { Else, While, Return, + Include, // Identifiers and literals Identifier(String), Number(i32), + String(String), + Char(char), // Operators Plus, @@ -35,10 +38,22 @@ pub enum TokenType { RBrace, Semicolon, Comma, + Colon, + Namespace, Eof, } +pub enum Type { + Int32, + Int16, + Int8, + Uint32, + Uint16, + Uint8, + Char, +} + #[derive(Debug, Clone)] pub struct Token { pub token_type: TokenType, @@ -150,6 +165,45 @@ impl Lexer { ident } + fn read_string(&mut self) -> Result { + let mut string = String::new(); + self.advance(); // Consume the opening quote + + while let Some(ch) = self.peek(0) { + if ch == '"' { + self.advance(); // Consume the closing quote + return Ok(string); + } else if ch == '\\' { + self.advance(); // Consume the backslash + if let Some(escaped_char) = self.peek(0) { + string.push(escaped_char); + self.advance(); + } + } else { + string.push(ch); + self.advance(); + } + } + + Err(String::from("Unexpected EOF")) + } + + fn read_char(&mut self) -> Result { + self.advance(); // Consume the opening quote + + if let Some(ch) = self.peek(0) { + self.advance(); + if self.peek(0) == Some('\'') { + self.advance(); + return Ok(ch); + } else { + Err(String::from("expected closing quote")) + } + } else { + Err(String::from("expected character")) + } + } + pub fn tokenize(&mut self) -> Result, String> { let mut tokens = Vec::new(); @@ -168,6 +222,12 @@ impl Lexer { let token_type = if ch.is_ascii_digit() { let num = self.read_number(); TokenType::Number(num) + } else if ch == '"' { + let string = self.read_string()?; + TokenType::String(string) + } else if ch == '\'' { + let char = self.read_char()?; + TokenType::Char(char) } else if ch.is_alphabetic() || ch == '_' { let ident = self.read_identifier(); match ident.as_str() { @@ -176,10 +236,20 @@ impl Lexer { "else" => TokenType::Else, "while" => TokenType::While, "return" => TokenType::Return, + "include" => TokenType::Include, _ => TokenType::Identifier(ident), } } else { match ch { + ':' if self.peek(1) == Some(':') => { + self.advance(); + self.advance(); + TokenType::Namespace + } + ':' => { + self.advance(); + TokenType::Colon + } '=' if self.peek(1) == Some('=') => { self.advance(); self.advance(); diff --git a/c_compiler/src/main.rs b/c_compiler/src/main.rs index 06758c8..eae157b 100644 --- a/c_compiler/src/main.rs +++ b/c_compiler/src/main.rs @@ -1,7 +1,10 @@ +#![feature(hash_map_macro)] + use std::fmt; use crate::{codegen::CodeGenerator, lexer::Lexer, parser::Parser}; +// mod assembly; pub mod codegen; pub mod lexer; pub mod parser; diff --git a/c_compiler/src/parser.rs b/c_compiler/src/parser.rs index 734cb04..86f2b00 100644 --- a/c_compiler/src/parser.rs +++ b/c_compiler/src/parser.rs @@ -23,6 +23,10 @@ pub enum Declaration { name: String, init: Option, }, + Import { + name: String, + path: String, + }, } #[derive(Debug, Clone)] @@ -225,6 +229,32 @@ impl Parser { } fn parse_declaration(&mut self) -> Result { + // check for an import + if let TokenType::Include = self.current().token_type { + self.advance(); + + let name = + if let TokenType::Identifier(id) = self.current().clone().token_type { + Some(id) + } else { + None + } + .ok_or(String::from("Expected identifier"))?; + + self.advance(); + self.expect(TokenType::Colon)?; + + let path = if let TokenType::String(id) = self.current().clone().token_type { + Some(id) + } else { + None + } + .ok_or(String::from("Expected string literal"))?; + + self.advance(); + return Ok(Declaration::Import { name, path }); + } + self.expect(TokenType::Int)?; let name = match &self.current().token_type { diff --git a/c_compiler/src/registers.rs b/c_compiler/src/registers.rs index d13babd..1d042e9 100644 --- a/c_compiler/src/registers.rs +++ b/c_compiler/src/registers.rs @@ -81,7 +81,17 @@ impl RegisterAllocator { } /// Free a temporary register after use + /// NOTE: This will NOT free registers that contain variables! + /// Variables persist throughout their scope and must not be freed pub fn free_temp(&mut self, reg: &str) { + // Check if this register contains a variable + if self.register_contents.contains_key(reg) { + // This register holds a variable - don't free it! + // Variables are only freed when they go out of scope via free_var() + return; + } + + // This is a true temporary - safe to free self.in_use.insert(reg.to_string(), false); } @@ -243,6 +253,16 @@ impl RegisterAllocator { self.variable_locations.remove(var_name); } + /// Get list of registers that contain variables and are in use + /// These need to be saved before function calls + pub fn get_caller_saved_registers(&self) -> Vec { + self.register_contents + .iter() + .filter(|(reg, _)| *self.in_use.get(*reg).unwrap_or(&false)) + .map(|(reg, _)| reg.clone()) + .collect() + } + /// Save caller-saved registers before a function call /// Returns assembly code to save them pub fn save_caller_saved(&mut self) -> Vec { diff --git a/resources/dsa/code.dsa b/resources/dsa/code.dsa new file mode 100644 index 0000000..6a78ddc --- /dev/null +++ b/resources/dsa/code.dsa @@ -0,0 +1,139 @@ + +// GENERATED BY DSA-C COMPILER +// Generated at 2026-01-31 01:39:55 + +// Imports +include maths: "./lib/maths/core.dsa" +include print: "lib/io/print.dsa" + +// Globals & Reserved Memory + + +// Entry Point +dw stack: 0x10000 +db message: "Process Exited with code:" +_init: + ldw stack, bpr + mov bpr, spr + push zero + call main + call print::print_newline + lwi message, rg0 + push rg0 + call print::print + pop zero + call print::print_hex_word + pop zero + hlt + + +// Function return boilerplate +_ret: + mov bpr, spr + pop bpr + return + + +factorial: + push bpr + mov spr, bpr + + ldw bpr, rg0, 8 + lli 1, rg1 + cmp rg0, rg1 + lli 0, rg2 + jgt _cmp_end_1 + lli 1, rg2 +_cmp_end_1: + cmp rg2, zero + jeq _else_3 +_then_2: + lli 1, rg1 + stw rg1, bpr, 8 + jmp _ret + jmp _end_4 +_else_3: + nop +_end_4: + push rg0 + lli 1, rg1 + sub rg0, rg1, rg2 + push rg2 + call factorial + pop rg1 + pop rg0 + push rg1 + push rg0 + call maths::multiply + pop rg2 + pop zero + stw rg2, bpr, 8 + jmp _ret + +add_: + push bpr + mov spr, bpr + + ldw bpr, rg0, 8 + ldw bpr, rg1, 12 + add rg0, rg1, rg2 + stw rg2, bpr, 8 + jmp _ret + +greater: + push bpr + mov spr, bpr + + ldw bpr, rg0, 8 + ldw bpr, rg1, 12 + add rg0, rg0, rg2 + add rg1, rg1, rg3 + cmp rg2, rg3 + lli 0, rg4 + jle _cmp_end_5 + lli 1, rg4 +_cmp_end_5: + cmp rg4, zero + jeq _else_7 +_then_6: + stw rg0, bpr, 8 + jmp _ret + jmp _end_8 +_else_7: + add rg1, rg0, rg2 + stw rg2, bpr, 8 + jmp _ret +_end_8: + jmp _ret + +main: + push bpr + mov spr, bpr + + lli 5, rg0 + push rg0 + lli 5, rg1 + push rg1 + call add_ + pop rg2 + pop zero + push rg2 + lli 5, rg0 + push rg0 + call greater + pop rg1 + pop zero + push rg1 + call print::print_num + pop rg0 + lli 5, rg0 + push rg0 + call factorial + pop rg1 + push rg1 + call print::print_num + pop rg0 + lli 0, rg0 + stw rg0, bpr, 8 + jmp _ret + diff --git a/resources/dsa/main.dsa b/resources/dsa/main.dsa index 552c80f..3020247 100644 --- a/resources/dsa/main.dsa +++ b/resources/dsa/main.dsa @@ -16,55 +16,17 @@ init: dw string: "hello world" start: - lwi 37, rg0 - lwi 12, rg1 - push rg0 - push rg1 - call maths::divmod - pop rg0 // result - pop rg1 // remainder + lwi 1, rg0 + lwi 2, rg1 - push rg1 push rg0 - call print::print_hex_byte - call print::print_whitespace + push rg1 + call maths::multiply + pop rg0 pop zero - call print::print_hex_byte - call print::print_newline - - lwi string, rg0 - //lwi 10, rg0 - pusha 4 push rg0 - call print::print - //call fib::fib_n - pop zero - call print::print_newline - popa 4 - - pusha 4 - push rg0 - call print::print - //call fib::fib_n - pop zero - call print::print_newline - popa 4 - - pusha 4 - push rg0 - call print::print - //call fib::fib_n - pop zero - call print::print_newline - popa 4 - - pusha 4 - push rg0 - call print::print - //call fib::fib_n - pop zero - call print::print_newline - popa 4 + call print::print_num + pop zero hlt -- 2.47.3 From 8f7163c459d94117b5aefb115ab25d8ad23db4aa Mon Sep 17 00:00:00 2001 From: zxq5 Date: Sun, 1 Feb 2026 22:16:09 +0000 Subject: [PATCH 10/53] added some documentation and started on compiler for custom language (not C) based on previous prototypes. pretty broken state rn. --- compiler/src/lexer.rs | 26 +- compiler/src/main.rs | 16 +- compiler/src/parserprototype.rs | 435 +++++++++++ resources/dsc/example.dsc | 10 +- resources/ideas/DSA_Project_Roadmap.md | 843 ++++++++++++++++++++++ resources/ideas/DSA_Project_Roadmap.pdf | Bin 0 -> 116116 bytes resources/ideas/TODOS.md | 0 resources/ideas/dsa_assembly_reference.md | 427 +++++++++++ resources/ideas/dsa_binary_format.md | 10 + 9 files changed, 1750 insertions(+), 17 deletions(-) create mode 100644 compiler/src/parserprototype.rs create mode 100644 resources/ideas/DSA_Project_Roadmap.md create mode 100644 resources/ideas/DSA_Project_Roadmap.pdf delete mode 100644 resources/ideas/TODOS.md create mode 100644 resources/ideas/dsa_assembly_reference.md create mode 100644 resources/ideas/dsa_binary_format.md diff --git a/compiler/src/lexer.rs b/compiler/src/lexer.rs index e616b93..e76e131 100644 --- a/compiler/src/lexer.rs +++ b/compiler/src/lexer.rs @@ -4,12 +4,15 @@ use std::str::Chars; #[derive(Debug, PartialEq, Clone)] pub enum Token { // Keywords + Fn, + Let, If, Else, Loop, Break, Return, Continue, + Include, // Identifiers and literals Identifier(String), @@ -24,7 +27,7 @@ pub enum Token { Semicolon, // ; Colon, // : Comma, // , - Pipe, // | + // Pipe, // | // Operators Plus, // + @@ -39,6 +42,7 @@ pub enum Token { LessEqual, // <= Greater, // > GreaterEqual, // >= + RightArrow, // -> // Special Eof, @@ -47,7 +51,10 @@ pub enum Token { impl Token { pub fn tt(&self) -> &str { match self { + Token::Include => "Include", + Token::Fn => "Fn", Token::If => "If", + Token::Let => "Let", Token::Else => "Else", Token::Loop => "Loop", Token::Break => "Break", @@ -63,7 +70,8 @@ impl Token { Token::Semicolon => "Semicolon", Token::Colon => "Colon", Token::Comma => "Comma", - Token::Pipe => "Pipe", + Token::RightArrow => "RightArrow", + // Token::Pipe => "Pipe", Token::Plus => "Plus", Token::Minus => "Minus", Token::Star => "Star", @@ -168,11 +176,17 @@ impl<'a> Lexer<'a> { Some(';') => Token::Semicolon, Some(':') => Token::Colon, Some(',') => Token::Comma, - Some('|') => Token::Pipe, + // Some('|') => Token::Pipe, Some('+') => Token::Plus, - Some('-') => Token::Minus, Some('*') => Token::Star, Some('/') => Token::Slash, + Some('-') => { + if self.match_next('>') { + Token::RightArrow + } else { + Token::Minus + } + } Some('!') => { if self.match_next('=') { Token::BangEqual @@ -218,12 +232,14 @@ impl<'a> Lexer<'a> { let mut ident = c.to_string(); ident.push_str(&self.read_identifier()); match ident.as_str() { + "fn" => Token::Fn, "if" => Token::If, "else" => Token::Else, "loop" => Token::Loop, "break" => Token::Break, "return" => Token::Return, "continue" => Token::Continue, + "include" => Token::Include, _ => Token::Identifier(ident), } } else if c.is_ascii_digit() { @@ -331,7 +347,7 @@ mod tests { assert_eq!(lexer.next_token(), Token::Colon); assert_eq!(lexer.next_token(), Token::Identifier("Func".to_string())); assert_eq!(lexer.next_token(), Token::Assign); - assert_eq!(lexer.next_token(), Token::Pipe); + // assert_eq!(lexer.next_token(), Token::Pipe); assert_eq!(lexer.next_token(), Token::Identifier("x".to_string())); assert_eq!(lexer.next_token(), Token::Colon); assert_eq!(lexer.next_token(), Token::Identifier("U32".to_string())); diff --git a/compiler/src/main.rs b/compiler/src/main.rs index 031ecc8..b1a127c 100644 --- a/compiler/src/main.rs +++ b/compiler/src/main.rs @@ -1,7 +1,12 @@ +#![feature(try_trait_v2)] + use std::{fs, path::Path}; pub mod lexer; -pub mod parser; +pub mod parserprototype; +use parserprototype::Parser; + +use crate::parserprototype::ParseResult; fn main() { println!("Hello, world!"); @@ -13,13 +18,16 @@ fn main() { let tokens = lexer.collect::>(); println!("{tokens:?}"); - let mut parser = parser::Parser::new(tokens); + let mut parser = Parser::new(tokens); let ast = match parser.parse() { - Ok(ast) => ast, - Err(e) => { + ParseResult::Accept(ast) => ast, + ParseResult::Reject(e) => { eprintln!("Error: {e:?}"); return; } + ParseResult::Deny => { + panic!("Parser denied parsing") + } }; println!("{ast:?}"); } diff --git a/compiler/src/parserprototype.rs b/compiler/src/parserprototype.rs new file mode 100644 index 0000000..72c3441 --- /dev/null +++ b/compiler/src/parserprototype.rs @@ -0,0 +1,435 @@ +use crate::lexer::Token; +use crate::{expect_tt, expect_value}; +use core::fmt; +use std::ops::{ControlFlow, FromResidual, Try}; + +#[derive(Debug, Clone)] +pub enum ParseResult { + Accept(T), + Deny, + Reject(E), +} + +#[derive(Debug, Clone)] +pub enum CompilerError { + UnexpectedToken(Token), + UnexpectedEndOfInput, + UnexpectedCharacter(char), + InvalidSyntax(String), + Generic(String), +} + +pub struct Parser { + tokens: Vec, + idx: usize, +} + +impl Parser { + pub fn new(tokens: Vec) -> Self { + Self { tokens, idx: 0 } + } + + pub fn parse(&mut self) -> ParseResult { + let mut declarations = Vec::new(); + + while let ParseResult::Accept(_) = self.peek_next() { + declarations.push(self.parse_declaration()?); + } + + ParseResult::Accept(Program { + imports: vec![], + declarations, + }) + } + + fn parse_declaration(&mut self) -> ParseResult { + if expect_tt!(self.peek_next()?, Fn).accepted() { + let x = self.parse_func(); + println!("function {:?}", x); + return x; + } + + println!("{:?}", self.peek_next()?); + + ParseResult::Reject(CompilerError::UnexpectedEndOfInput) + } + + fn parse_func(&mut self) -> ParseResult { + // expect function keyword + // + println!("pre name! {:?}", self.peek_next()?); + + let _ = expect_tt!(self.next()?, Fn); + + println!("this is the name! {:?}", self.peek_next()?); + + // expect function name + let name = match self.next()? { + Token::Identifier(name) => name, + id => return ParseResult::Reject(CompilerError::UnexpectedToken(id)), + }; + + // expect left paren + let _ = expect_tt!(self.next()?, LParen); + + let mut params = Vec::new(); + while expect_tt!(self.peek_next()?, Identifier).accepted() { + let arg = self.parse_var_decl()?; + params.push(arg); + } + + // expect right paren + let _ = expect_tt!(self.next()?, RParen); + + // see if we can parse the return type! + let mut return_type = TypeId::Void; + + if expect_tt!(self.peek_next()?, RightArrow).accepted() { + let _ = self.next(); + return_type = self.parse_type()?; + } + + // expect left brace + let _ = expect_tt!(self.next()?, LBrace); + + let mut body = Vec::new(); + + // expect right brace + let _ = expect_tt!(self.next()?, RBrace); + + ParseResult::Accept(Declaration::Function { + name, + params, + return_type, + body, + }) + } + + fn parse_var_decl(&mut self) -> ParseResult { + let name = match self.next()? { + Token::Identifier(name) => name, + id => return ParseResult::Reject(CompilerError::UnexpectedToken(id)), + }; + + let _ = expect_tt!(self.next()?, Colon); + + let type_ = self.parse_type()?; + + ParseResult::Accept(Variable { + name, + param_type: Some(type_), + }) + } + + fn parse_type(&mut self) -> ParseResult { + // get the type name incl namespace + let typename = self.parse_identifier()?; + + match typename.name.as_str() { + "u32" => ParseResult::Accept(TypeId::U32), + "u16" => ParseResult::Accept(TypeId::U16), + "u8" => ParseResult::Accept(TypeId::U8), + "i32" => ParseResult::Accept(TypeId::I32), + "i16" => ParseResult::Accept(TypeId::I16), + "i8" => ParseResult::Accept(TypeId::I8), + "void" => ParseResult::Accept(TypeId::Void), + "char" => ParseResult::Accept(TypeId::Char), + _ => todo!("Implement parsing for other types!!"), + } + } + + fn parse_identifier(&mut self) -> ParseResult { + let primary = match self.next()? { + Token::Identifier(namespace) => namespace, + id => return ParseResult::Reject(CompilerError::UnexpectedToken(id)), + }; + + if expect_tt!(self.peek_next()?, Colon).accepted() { + let _ = expect_tt!(self.next()?, Colon); + let _ = expect_tt!(self.next()?, Colon); + + let secondary = match self.next()? { + Token::Identifier(name) => name, + id => return ParseResult::Reject(CompilerError::UnexpectedToken(id)), + }; + + ParseResult::Accept(Name { + namespace: Some(primary), + name: secondary, + }) + } else { + ParseResult::Accept(Name { + namespace: None, + name: primary, + }) + } + } + + fn next(&mut self) -> ParseResult { + if self.idx >= self.tokens.len() { + ParseResult::Reject(CompilerError::UnexpectedEndOfInput) + } else { + let token = self.tokens[self.idx].clone(); + println!("NEXT {:?}", token); + self.idx += 1; + ParseResult::Accept(token) + } + } + + fn peek_next(&self) -> ParseResult { + if self.idx >= self.tokens.len() { + ParseResult::Reject(CompilerError::UnexpectedEndOfInput) + } else { + ParseResult::Accept(self.tokens[self.idx].clone()) + } + } +} + +#[derive(Debug, Clone)] +pub struct Program { + pub imports: Vec, + pub declarations: Vec, +} + +#[derive(Debug, Clone)] +pub enum Declaration { + Function { + name: String, + return_type: TypeId, + params: Vec, + body: Block, + }, + Variable { + name: String, + init: Option, + }, +} + +#[derive(Debug, Clone)] +pub struct Dependency { + pub name: String, + pub path: String, +} + +#[derive(Debug, Clone)] +pub struct Variable { + pub name: String, + pub param_type: Option, +} + +#[derive(Debug, Clone)] +pub struct Name { + pub name: String, + pub namespace: Option, +} + +#[derive(Debug, Clone)] +pub enum TypeId { + U8, + U16, + U32, + I8, + I16, + I32, + Char, + Void, + Ptr(Box), + Ref(Box), + Array(Box, usize), + Struct { + name: Name, + fields: Vec<(String, TypeId)>, + }, +} + +pub type Block = Vec; + +#[derive(Debug, Clone)] +pub enum Statement { + Block(Block), + Assign { + var: Variable, + value: Option>, + }, + Expression { + expr: Expression, + }, + If { + condition: Expression, + then_stmt: Block, + else_stmt: Block, + }, + While { + condition: Expression, + body: Vec, + }, + Loop(Block), + Break, + Continue, + Return(Option), +} + +#[derive(Debug, Clone)] +pub enum ConstExpr { + Number(i32), + String(String), +} + +impl fmt::Display for ConstExpr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + ConstExpr::Number(n) => write!(f, "{}", n), + ConstExpr::String(s) => write!(f, "\"{}\"", s), + } + } +} + +#[derive(Debug, Clone)] +pub enum Expression { + Empty, + Binary { + op: BinaryOperator, + left: Box, + right: Box, + }, + Unary { + op: UnaryOperator, + operand: Box, + }, + Variable { + name: Name, + expr_type: Option, + }, + Number { + value: i32, + }, + Call { + name: Name, + args: Vec, + }, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum BinaryOperator { + Add, + Sub, + Mul, + Div, + Eq, + Ne, + Lt, + Gt, + Le, + Ge, +} + +impl fmt::Display for BinaryOperator { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + BinaryOperator::Add => write!(f, "+"), + BinaryOperator::Sub => write!(f, "-"), + BinaryOperator::Mul => write!(f, "*"), + BinaryOperator::Div => write!(f, "/"), + BinaryOperator::Eq => write!(f, "=="), + BinaryOperator::Ne => write!(f, "!="), + BinaryOperator::Lt => write!(f, "<"), + BinaryOperator::Gt => write!(f, ">"), + BinaryOperator::Le => write!(f, "<="), + BinaryOperator::Ge => write!(f, ">="), + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub enum UnaryOperator { + Plus, + Minus, +} + +impl fmt::Display for UnaryOperator { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + UnaryOperator::Plus => write!(f, "+"), + UnaryOperator::Minus => write!(f, "-"), + } + } +} + +impl ParseResult { + pub fn accepted(&self) -> bool { + matches!(self, ParseResult::Accept(_)) + } +} + +pub enum ParseResultResidual { + Deny, + Reject(T), +} + +impl Try for ParseResult { + type Output = T; + type Residual = ParseResultResidual; + + fn from_output(output: T) -> Self { + ParseResult::Accept(output) + } + + fn branch(self) -> ControlFlow { + match self { + ParseResult::Accept(v) => ControlFlow::Continue(v), + ParseResult::Deny => ControlFlow::Break(ParseResultResidual::Deny), + ParseResult::Reject(e) => ControlFlow::Break(ParseResultResidual::Reject(e)), + } + } +} + +impl FromResidual for ParseResult { + fn from_residual(residual: ParseResultResidual) -> Self { + match residual { + ParseResultResidual::Deny => ParseResult::Deny, + ParseResultResidual::Reject(e) => ParseResult::Reject(e), + } + } +} + +#[macro_export] +macro_rules! expect_tt { + ($token:expr, $($variant:ident),+) => {{ + let tt = $token.tt().to_string(); + + // for some reason the code trips tf out without this line + + println!("token {:?}", $token); + + let mut vs = String::new(); + $( + let s = stringify!($variant); + vs.push_str(s); + vs.push_str("|"); + )+ + + match tt.as_str() { + $( + stringify!($variant) => ParseResult::Accept($token.clone()), + )+ + _ => { + println!("EXPECTED!! {} [{}]", tt, vs); + // let expected = format!("[{}]", vec![$(stringify!($variant)),+].join(" | ")); + ParseResult::Reject(CompilerError::UnexpectedToken($token.clone())) + } + } + }}; +} + +#[macro_export] +macro_rules! expect_value { + ($token:expr, $variant:expr) => {{ + match $token { + $variant(x) => ParseResult::Accept(x), + _ => { + let expected = format!("[{}]") + ParseResult::Reject(CompilerError::UnexpectedToken($token.clone())) + } + } + }}; +} diff --git a/resources/dsc/example.dsc b/resources/dsc/example.dsc index 550a707..0f09703 100644 --- a/resources/dsc/example.dsc +++ b/resources/dsc/example.dsc @@ -1,8 +1,2 @@ -main: Func = | x: U32, y: U32 | { - res = add(x, y); - print(res); - - if res > 10 { - print("res is greater than 10"); - } -} +fn factorial(n: u32) -> u32 {} +fn main(x: u32, y: u32) -> u32 {} diff --git a/resources/ideas/DSA_Project_Roadmap.md b/resources/ideas/DSA_Project_Roadmap.md new file mode 100644 index 0000000..563498e --- /dev/null +++ b/resources/ideas/DSA_Project_Roadmap.md @@ -0,0 +1,843 @@ +# DSA Project Roadmap & Task Breakdown + +> **Damn Simple Architecture** — Full ecosystem development plan including emulator, assembler, compiler, debugger, and tooling infrastructure. + +--- + +## Table of Contents + +1. [Phase 1: Foundation & Core Infrastructure](#phase-1-foundation--core-infrastructure) + - [1.1 Binary Format & Linking System](#11-binary-format--linking-system) + - [1.2 Assembler Rewrite](#12-assembler-rewrite) + - [1.3 Documentation Updates](#13-documentation-updates) +2. [Phase 2: Compiler Development](#phase-2-compiler-development) + - [2.1 Language Design & Implementation](#21-language-design--implementation) + - [2.2 Standard Library](#22-standard-library) +3. [Phase 3: Build System & Package Management](#phase-3-build-system--package-management) + - [3.1 Build System](#31-build-system) + - [3.2 Package Management System](#32-package-management-system) +4. [Phase 4: Debugger & Development Tools](#phase-4-debugger--development-tools) + - [4.1 Debug Symbol System](#41-debug-symbol-system) + - [4.2 Debugger Implementation](#42-debugger-implementation) + - [4.3 Enhanced Editor Integration](#43-enhanced-editor-integration) +5. [Phase 5: Integration & Polish](#phase-5-integration--polish) +6. [Phase 6: Future Enhancements (NTH)](#phase-6-future-enhancements-nth) +7. [Summary Timeline](#summary-timeline) +8. [Critical Path](#critical-path) +9. [Recommended Work Order](#recommended-work-order) + +--- + +## Phase 1: Foundation & Core Infrastructure + +**Estimated Duration: 3–4 weeks** + +--- + +### 1.1 Binary Format & Linking System + +> **Priority: CRITICAL** — Everything depends on this. +> **Total Estimate: 1.5 weeks** + +--- + +#### 1.1.1 Design New Binary Format Specification + +**Estimate: 2 days** +**Dependencies:** None +**Deliverable:** `docs/binary-format-spec.md` + +- [ ] Research existing object file formats (ELF, COFF, Mach-O) for inspiration +- [ ] Design `.dsb` object file format specification + - [ ] Symbol table structure + - [ ] Relocation table format + - [ ] Section definitions (code, data, rodata, bss) + - [ ] Debug information structure + - [ ] Metadata headers +- [ ] Design `.dse` executable format specification + - [ ] Entry point definition + - [ ] Memory layout requirements + - [ ] Linking metadata +- [ ] Document format specifications in markdown +- [ ] Create format version strategy for future compatibility + +--- + +#### 1.1.2 Implement DSB Object File Writer + +**Estimate: 3 days** +**Dependencies:** 1.1.1 +**Deliverable:** `dsa-binary-format` crate v0.1.0 + +- [ ] Create new crate: `dsa-binary-format` +- [ ] Implement object file structures + - [ ] Header structure + - [ ] Symbol table builder + - [ ] Section manager + - [ ] Relocation entry creator +- [ ] Write serialization logic +- [ ] Add validation and error handling +- [ ] Write unit tests for each structure +- [ ] Integration tests for complete object files + +--- + +#### 1.1.3 Build Linker Program + +**Estimate: 4 days** +**Dependencies:** 1.1.2 +**Deliverable:** `dsa-link` executable + +- [ ] Create new crate: `dsa-linker` +- [ ] Implement symbol resolution + - [ ] Global symbol table + - [ ] Symbol conflict detection + - [ ] Weak symbol handling +- [ ] Implement relocation processing + - [ ] Address calculation + - [ ] Patch generation + - [ ] Cross-section references +- [ ] Build executable generator + - [ ] Combine sections + - [ ] Generate final memory layout + - [ ] Write `.dse` output +- [ ] Add linker script support (basic) +- [ ] Comprehensive error messages +- [ ] Test suite with complex linking scenarios + +--- + +### 1.2 Assembler Rewrite + +> **Priority: HIGH** — Required for all compiled code. +> **Total Estimate: 1.5 weeks** + +--- + +#### 1.2.1 Assembler Architecture Design + +**Estimate: 1 day** +**Dependencies:** 1.1.1 +**Deliverable:** `docs/assembler-architecture.md` + +- [ ] Design multi-pass architecture + - [ ] Pass 1: Symbol collection + - [ ] Pass 2: Macro expansion + - [ ] Pass 3: Code generation + - [ ] Pass 4: Relocation generation +- [ ] Plan error handling strategy +- [ ] Design threading model for parallel file processing +- [ ] Define module/import resolution system +- [ ] Plan integration points with DSC compiler + +--- + +#### 1.2.2 Implement Core Assembler + +**Estimate: 5 days** +**Dependencies:** 1.1.2, 1.2.1 +**Deliverable:** `dsa-asm` executable v2.0.0 + +- [ ] Create new crate: `dsa-assembler-ng` (next-gen) +- [ ] Implement lexer with better error recovery +- [ ] Build parser with detailed error messages + - [ ] Instruction parsing + - [ ] Directive handling + - [ ] Macro system + - [ ] Include resolution +- [ ] Symbol table management +- [ ] Code generator outputting to DSB format +- [ ] Multi-threading for file parsing +- [ ] Comprehensive test suite +- [ ] Error message testing + +--- + +#### 1.2.3 Import System & DSC Integration + +**Estimate: 2 days** +**Dependencies:** 1.2.2, 2.1.2 +**Deliverable:** Working import system + +- [ ] Design import protocol between DSC and assembler +- [ ] Implement symbol table merging +- [ ] Handle pre-compiled object imports +- [ ] Test DSC → Assembly → Object pipeline +- [ ] Document integration process + +--- + +### 1.3 Documentation Updates + +> **Priority: MEDIUM** — Can be done alongside development. +> **Total Estimate: 3 days (distributed)** + +--- + +#### 1.3.1 Update Assembly Documentation + +**Estimate: 1 day** +**Dependencies:** 1.2.2 +**Deliverable:** Updated `docs/dsa-assembly-reference.md` + +- [ ] Review all instruction documentation +- [ ] Document new pseudo-instructions +- [ ] Update calling convention docs +- [ ] Add examples for new features +- [ ] Document assembler directives +- [ ] Macro system documentation + +--- + +#### 1.3.2 Architecture Documentation + +**Estimate: 1 day** +**Dependencies:** None (can start anytime) +**Deliverable:** `docs/dsa-architecture.md` + +- [ ] Document ISA specification +- [ ] Memory model documentation +- [ ] Interrupt handling +- [ ] Hardware peripheral specs +- [ ] Timing/performance characteristics + +--- + +#### 1.3.3 Build Tools Documentation + +**Estimate: 1 day** +**Dependencies:** 1.2.2, 1.1.3, 3.1.2 +**Deliverable:** `docs/build-tools-guide.md` + +- [ ] Assembler usage guide +- [ ] Linker usage guide +- [ ] Build system guide +- [ ] Tutorial: Building a simple program +- [ ] Tutorial: Multi-file projects + +--- + +## Phase 2: Compiler Development + +**Estimated Duration: 3–4 weeks** + +--- + +### 2.1 Language Design & Implementation + +> **Priority: HIGH** — Core functionality. +> **Total Estimate: 2.5 weeks** + +--- + +#### 2.1.1 Language Syntax Design + +**Estimate: 2 days** +**Dependencies:** None +**Deliverable:** `docs/language-spec.md` + +- [ ] Define syntax goals (simplicity, systems programming) +- [ ] Design type system + - [ ] Primitive types + - [ ] Pointers/references + - [ ] Structs + - [ ] Arrays + - [ ] Function types +- [ ] Control flow syntax +- [ ] Function declaration syntax +- [ ] Module/import system +- [ ] Operator precedence +- [ ] Write EBNF grammar +- [ ] Create example programs + +--- + +#### 2.1.2 Lexer & Parser Implementation + +**Estimate: 4 days** +**Dependencies:** 2.1.1 +**Deliverable:** Parser in `dsc-compiler` crate + +- [ ] Adapt existing C lexer to new syntax +- [ ] Implement new parser for designed syntax +- [ ] AST node definitions +- [ ] Error recovery mechanisms +- [ ] Comprehensive parser tests +- [ ] Syntax error message quality testing + +--- + +#### 2.1.3 Code Generation Improvements + +**Estimate: 5 days** +**Dependencies:** 2.1.2, 1.2.2 +**Deliverable:** Working code generator + +- [ ] Review and fix existing codegen issues +- [ ] Implement missing language features + - [ ] Structs + - [ ] Arrays + - [ ] Pointers/memory operations + - [ ] For loops + - [ ] Switch statements + - [ ] Break/continue +- [ ] Optimize register allocation further +- [ ] Implement proper function calling conventions +- [ ] Add constant folding optimization +- [ ] Dead code elimination +- [ ] Test each feature thoroughly + +--- + +#### 2.1.4 Type Checking & Semantic Analysis + +**Estimate: 3 days** +**Dependencies:** 2.1.2 +**Deliverable:** Type checker integrated in compiler + +- [ ] Implement type checker +- [ ] Symbol table for scoping +- [ ] Type inference where applicable +- [ ] Const checking +- [ ] Definite assignment analysis +- [ ] Comprehensive semantic error messages +- [ ] Test suite for type errors + +--- + +### 2.2 Standard Library + +> **Priority: MEDIUM** — Needed for useful programs. +> **Total Estimate: 1 week** + +--- + +#### 2.2.1 Core Runtime Library (in Assembly) + +**Estimate: 3 days** +**Dependencies:** 1.2.2 +**Deliverable:** `lib/runtime/` directory + +- [ ] Memory allocation (malloc/free) +- [ ] String operations +- [ ] Math functions +- [ ] I/O functions (improved print, read) +- [ ] System call interface +- [ ] Tests for each function + +--- + +#### 2.2.2 Standard Library (in DSC) + +**Estimate: 2 days** +**Dependencies:** 2.1.3, 2.2.1 +**Deliverable:** `lib/std/` directory + +- [ ] String module +- [ ] Collections (array utilities, maybe simple list) +- [ ] File I/O module +- [ ] Math utilities +- [ ] Tests and examples + +--- + +## Phase 3: Build System & Package Management + +**Estimated Duration: 2–3 weeks** + +--- + +### 3.1 Build System + +> **Priority: HIGH** — Required for complex projects. +> **Total Estimate: 1.5 weeks** + +--- + +#### 3.1.1 Build System Design + +**Estimate: 1 day** +**Dependencies:** None +**Deliverable:** `docs/build-system-design.md` + +- [ ] Define project structure conventions +- [ ] Design build manifest format (`dsa-project.toml` or similar) +- [ ] Dependency resolution strategy +- [ ] Build cache design +- [ ] Incremental build strategy +- [ ] Multi-target support + +--- + +#### 3.1.2 Build Tool Implementation + +**Estimate: 5 days** +**Dependencies:** 3.1.1, 1.2.2, 1.1.3, 2.1.3 +**Deliverable:** `dsa-build` executable + +- [ ] Create crate: `dsa-build` +- [ ] Manifest parser +- [ ] Dependency graph builder +- [ ] Task orchestrator + - [ ] Compilation tasks + - [ ] Assembly tasks + - [ ] Linking tasks +- [ ] Build cache implementation +- [ ] Parallel build support +- [ ] Clean, rebuild commands +- [ ] Watch mode for development +- [ ] Comprehensive tests + +--- + +#### 3.1.3 Project Management Commands + +**Estimate: 2 days** +**Dependencies:** 3.1.2 +**Deliverable:** Enhanced `dsa-build` with project management + +- [ ] `dsa new ` — Create new project +- [ ] `dsa init` — Initialize in existing directory +- [ ] `dsa add ` — Add dependency +- [ ] Binary vs library project types +- [ ] Template system for project scaffolding +- [ ] Documentation for each command + +--- + +### 3.2 Package Management System + +> **Priority: MEDIUM** — Enables code sharing. +> **Total Estimate: 1.5 weeks** + +--- + +#### 3.2.1 Package Registry Design + +**Estimate: 2 days** +**Dependencies:** 3.1.1 +**Deliverable:** `docs/package-registry-design.md` + +- [ ] Decide: Git monorepo vs custom hosting +- [ ] Design package naming conventions +- [ ] Version resolution strategy (semver) +- [ ] Package manifest format +- [ ] Security considerations +- [ ] Package storage format (source/binary/both) +- [ ] API design for registry server + +--- + +#### 3.2.2 Local Package Manager Tool + +**Estimate: 4 days** +**Dependencies:** 3.2.1, 3.1.2 +**Deliverable:** `dsa-pkg` tool integrated with `dsa-build` + +- [ ] Create crate: `dsa-pkg` +- [ ] Package index synchronization +- [ ] Dependency resolver +- [ ] Package download/cache system +- [ ] Integration with build system +- [ ] Commands: + - [ ] `dsa install ` + - [ ] `dsa publish` + - [ ] `dsa search ` + - [ ] `dsa update` +- [ ] Lock file generation +- [ ] Test with mock registry + +--- + +#### 3.2.3 Package Registry Implementation + +**Estimate: 3 days** +**Dependencies:** 3.2.1 +**Deliverable:** Package registry (URL or repo) + +- [ ] If **Git monorepo** approach: + - [ ] Set up repository structure + - [ ] CI/CD for validation + - [ ] Submission process + - [ ] Package browser website +- [ ] If **custom hosting**: + - [ ] Simple web server (Rust + Axum/Actix) + - [ ] Package upload API + - [ ] Package search API + - [ ] Basic web UI + - [ ] Database for metadata +- [ ] Documentation for publishing + +--- + +## Phase 4: Debugger & Development Tools + +**Estimated Duration: 3–4 weeks** + +--- + +### 4.1 Debug Symbol System + +> **Priority: HIGH** — Foundation for debugging. +> **Total Estimate: 1 week** + +--- + +#### 4.1.1 Debug Symbol Format Design + +**Estimate: 1 day** +**Dependencies:** 1.1.1 +**Deliverable:** `docs/debug-symbol-format.md` + +- [ ] Design symbol table format + - [ ] Function addresses → names + - [ ] Line number → address mapping + - [ ] Variable location information + - [ ] Type information +- [ ] Define symbol table file format +- [ ] Plan for embedding in DSE/DSB files + +--- + +#### 4.1.2 Symbol Generation in Tools + +**Estimate: 3 days** +**Dependencies:** 4.1.1, 1.2.2, 2.1.3 +**Deliverable:** Debug symbols in build output + +- [ ] Modify assembler to emit debug symbols +- [ ] Modify compiler to emit debug symbols + - [ ] Source file/line tracking + - [ ] Variable scope tracking +- [ ] Linker merges debug symbols +- [ ] Test symbol generation pipeline + +--- + +#### 4.1.3 Symbol Table Loader in Emulator + +**Estimate: 2 days** +**Dependencies:** 4.1.2 +**Deliverable:** Symbol loading in emulator crate + +- [ ] Implement symbol table parser +- [ ] Build address → symbol lookup (HashMap) +- [ ] Build symbol → address lookup +- [ ] Memory efficient storage +- [ ] Tests for symbol resolution + +--- + +### 4.2 Debugger Implementation + +> **Priority: HIGH** — Major productivity boost. +> **Total Estimate: 2 weeks** + +--- + +#### 4.2.1 Core Debugger Features + +**Estimate: 5 days** +**Dependencies:** 4.1.3 +**Deliverable:** Debugger backend + +- [ ] Execution control + - [ ] Step instruction + - [ ] Step over function calls + - [ ] Continue to breakpoint + - [ ] Run to cursor +- [ ] Breakpoint system + - [ ] Address breakpoints + - [ ] Conditional breakpoints + - [ ] Watchpoints (memory access) +- [ ] Register inspection +- [ ] Memory inspection +- [ ] Stack trace generation +- [ ] Test debugger commands + +--- + +#### 4.2.2 Disassembler with Symbol Resolution + +**Estimate: 3 days** +**Dependencies:** 4.1.3 +**Deliverable:** Enhanced disassembler + +- [ ] Instruction decoder +- [ ] Format with labels instead of addresses +- [ ] Show function names at call sites +- [ ] Inline comments with variable names +- [ ] Color coding for instruction types +- [ ] Tests for disassembly output + +--- + +#### 4.2.3 Pseudo-Instruction Decompiler + +> ⚠️ **COMPLEX TASK** — Separate pass to decompile assembly into readable pseudo-instructions. + +**Estimate: 4 days** +**Dependencies:** 4.2.2 +**Deliverable:** Pseudo-instruction view mode + +- [ ] Pattern recognition for common sequences + - [ ] Function prologue/epilogue + - [ ] Multiplication using shifts/adds + - [ ] Division + - [ ] Conditional moves +- [ ] Control flow reconstruction + - [ ] If/else detection + - [ ] Loop detection + - [ ] Switch statement detection +- [ ] Expression reconstruction +- [ ] Format as higher-level pseudo-code +- [ ] Extensive pattern testing + +--- + +#### 4.2.4 Execution History Tracking + +**Estimate: 2 days** +**Dependencies:** 4.2.1 +**Deliverable:** Execution trace feature + +- [ ] Circular buffer for instruction history +- [ ] Register state snapshots over time +- [ ] Configurable history depth +- [ ] Efficient memory usage +- [ ] Playback/reverse debugging (basic) +- [ ] Export trace to file + +--- + +### 4.3 Enhanced Editor Integration + +> **Priority: MEDIUM** — UX improvement. +> **Total Estimate: 1 week** + +--- + +#### 4.3.1 Tiling Window System + +**Estimate: 2 days** +**Dependencies:** None (UI work) +**Deliverable:** Panel system in emulator + +- [ ] Research Rust tiling libraries (`egui_tiles`, or custom) +- [ ] Design panel layout system + - [ ] Code editor panel + - [ ] Disassembly panel + - [ ] Register panel + - [ ] Memory panel + - [ ] Console panel +- [ ] Implement drag-and-drop panel management +- [ ] Save/load layouts + +--- + +#### 4.3.2 Assembly Editor Improvements + +**Estimate: 2 days** +**Dependencies:** 4.3.1 +**Deliverable:** Enhanced assembly editor + +- [ ] Syntax highlighting for DSA assembly +- [ ] Auto-completion for instructions +- [ ] Label/symbol auto-completion +- [ ] Error highlighting +- [ ] Inline documentation tooltips +- [ ] Jump-to-definition for labels + +--- + +#### 4.3.3 High-Level Language Editor + +**Estimate: 2 days** +**Dependencies:** 4.3.1, 2.1.4 +**Deliverable:** DSC language editor + +- [ ] Syntax highlighting for DSC language +- [ ] Basic auto-completion +- [ ] Bracket matching +- [ ] Error highlighting from compiler +- [ ] Go-to-definition (using debug symbols) +- [ ] Inline type hints + +--- + +#### 4.3.4 Integrate Build Tools in Editor + +**Estimate: 1 day** +**Dependencies:** 4.3.1, 3.1.2 +**Deliverable:** Integrated build experience + +- [ ] Build button/command in UI +- [ ] Show build output in console panel +- [ ] Error navigation (click to jump to source) +- [ ] Hot reload on successful build +- [ ] Build status indicator + +--- + +## Phase 5: Integration & Polish + +**Estimated Duration: 1–2 weeks** + +--- + +### 5.1 Tool Integration + +> **Priority: HIGH** — Everything works together. +> **Total Estimate: 1 week** + +--- + +#### 5.1.1 Unified Toolchain + +**Estimate: 3 days** +**Dependencies:** All previous phases +**Deliverable:** `dsa` unified command-line tool + +- [ ] Create meta-crate: `dsa-tools` +- [ ] Unified CLI with subcommands + - [ ] `dsa build` + - [ ] `dsa run` + - [ ] `dsa debug` + - [ ] `dsa test` + - [ ] `dsa pkg` +- [ ] Shared configuration system +- [ ] Tool interop testing +- [ ] Documentation for workflow + +--- + +#### 5.1.2 Emulator Integration + +**Estimate: 2 days** +**Dependencies:** 5.1.1, 4.3.4 +**Deliverable:** Fully integrated development environment + +- [ ] Add build tools as emulator dependencies +- [ ] In-editor build triggered from emulator +- [ ] Debugger uses build output directly +- [ ] Source-level debugging with line mapping +- [ ] Test full edit → build → debug cycle + +--- + +#### 5.1.3 Documentation & Tutorials + +**Estimate: 2 days** +**Dependencies:** 5.1.2 +**Deliverable:** Complete documentation suite + +- [ ] Getting started guide +- [ ] Full tutorial: Building a simple game +- [ ] Debugger usage guide +- [ ] Best practices document +- [ ] Troubleshooting guide + +--- + +## Phase 6: Future Enhancements (NTH) + +> **Priority: LOW** — Nice to have, long-term goal. +> **Estimated Duration: 4+ weeks** + +--- + +### 6.1 Command-Line Emulator + +> ⚠️ **COMPLEX LONG-TERM GOAL** — Requires significant design and UX consideration. + +--- + +#### 6.1.1 Design Phase + +**Estimate: 1 week** +**Dependencies:** None +**Deliverable:** `docs/cli-emulator-design.md` + +- [ ] UX research for terminal-based debuggers +- [ ] Design TUI layout (using `ratatui` or similar) +- [ ] Command syntax design +- [ ] Scripting support design +- [ ] Accessibility considerations + +--- + +#### 6.1.2 Implementation + +**Estimate: 3+ weeks** +**Dependencies:** 6.1.1, Phase 4 complete +**Deliverable:** `dsa-emu-cli` executable + +- [ ] TUI framework setup +- [ ] Core emulator integration +- [ ] Command parser +- [ ] Panel rendering (code, registers, memory, etc.) +- [ ] Keyboard shortcuts +- [ ] Mouse support +- [ ] Configuration system +- [ ] Extensive usability testing + +--- + +## Summary Timeline + +| Phase | Duration | Key Dependencies | +|---|---|---| +| Phase 1: Foundation | 3–4 weeks | None | +| Phase 2: Compiler | 3–4 weeks | Phase 1 complete | +| Phase 3: Build System | 2–3 weeks | Phases 1–2 complete | +| Phase 4: Debugger | 3–4 weeks | Phases 1–3 complete | +| Phase 5: Integration | 1–2 weeks | Phases 1–4 complete | +| Phase 6: CLI Emulator *(NTH)* | 4+ weeks | Phase 4 complete | + +**Total Estimated Time: 12–17 weeks (3–4 months) for Phases 1–5** + +--- + +## Critical Path + +The following tasks are on the critical path and will block other work if delayed: + +``` +1.1.1 Binary format design + └── 1.1.2 Object file writer + └── 1.1.3 Linker + └── 1.2.2 Assembler rewrite + └── 2.1.3 Compiler codegen + └── 3.1.2 Build system + └── 4.1.2 Debug symbols + └── 4.2.1 Debugger +``` + +--- + +## Recommended Work Order + +| Weeks | Focus | Tasks | +|---|---|---| +| 1–2 | Binary Format & Linker | 1.1.1 → 1.1.2 → 1.1.3 | +| 3–4 | Assembler Rewrite | 1.2.1 → 1.2.2 | +| 5–6 | Compiler Syntax & Parser | 2.1.1 → 2.1.2 *(start 1.3 docs in parallel)* | +| 7–9 | Compiler Codegen & Types | 2.1.3 → 2.1.4 *(start 2.2.1 runtime in parallel)* | +| 10–11 | Build System | 3.1.1 → 3.1.2 → 3.1.3 | +| 12–13 | Package Management *(if desired now)* | 3.2.1 → 3.2.2 → 3.2.3 | +| 14–15 | Debug Symbols | 4.1.1 → 4.1.2 → 4.1.3 | +| 16–18 | Core Debugger | 4.2.1 → 4.2.2 → 4.2.4 | +| 19–20 | Editor Enhancements | 4.3.1 → 4.3.2 → 4.3.3 → 4.3.4 | +| 21–22 | Integration & Polish | 5.1.1 → 5.1.2 → 5.1.3 | + +--- + +## Notes + +- Time estimates assume ~6–8 productive hours per day. +- Add **20–30% buffer** for unexpected issues. +- Testing time is included in each estimate. +- Documentation is distributed throughout rather than batched at the end. +- Package management (3.2) can be deferred if time-constrained. +- Pseudo-instruction decompiler (4.2.3) can be a stretch goal. +- CLI emulator (Phase 6) is explicitly a "nice to have" and should not block other work. diff --git a/resources/ideas/DSA_Project_Roadmap.pdf b/resources/ideas/DSA_Project_Roadmap.pdf new file mode 100644 index 0000000000000000000000000000000000000000..ba50e5c2358e9b8a59c0f13a65be3cf6ec67fd93 GIT binary patch literal 116116 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 z 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

VaGX@{g~J3C4i>aw0I9^ns3r?a2cAwumw>s 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/write: protection fault (unless in kernel mode)** | +| `mmr` | Privileged | Hardware memory map table address
**on-read/write: protection fault (unless in kernel mode)** | +| `zero` | Read-only | Always contains zero
**on-read: always returns zero**
**on-write: value is voided** | +| `pcx` | Read-only | Program counter
**on-write: protection fault** | +| `noreg` | Placeholder | Indicates absence of register argument
**on-read/write: illegal instruction fault** | + +## Hardware Instructions + +### Data Movement Instructions + +| Mnemonic | Operands | Description | +|----------|----------|-------------| +| **MOV** | `src_reg, dest_reg` | Copy value from source to destination register | +| **MOVS** | `src_reg, dest_reg` | Copy with sign extension | + +**Examples:** +```asm +mov rg0, rg1 ; Copy rg0 to rg1 +movs rg0, rg1 ; Copy rg0 to rg1 with sign extension +``` +### Memory Access Instructions + +#### Load Instructions + +| Mnemonic | Operands | Description | +|----------|----------|-------------| +| **LDB** | `base_reg, dest_reg [, offset]`
`label, dest_reg [, offset]` | Load byte from memory | +| **LDBS** | `base_reg, dest_reg [, offset]`
`label, dest_reg [, offset]` | Load byte with sign extension | +| **LDH** | `base_reg, dest_reg [, offset]`
`label, dest_reg [, offset]` | Load half-word (16-bit) | +| **LDHS** | `base_reg, dest_reg [, offset]`
`label, dest_reg [, offset]` | Load half-word with sign extension | +| **LDW** | `base_reg, dest_reg [, offset]`
`label, dest_reg [, offset]` | Load word (32-bit) | + +**Examples:** +```asm +; Direct register addressing +ldb rg0, rg1 ; Load byte from address in rg0 +ldw rg0, rg1, 8 ; Load word from (rg0 + 8) + +; Label addressing +ldb buffer, rg2 ; Load byte from label 'buffer' +ldw stack, bpr ; Load stack address into base pointer +``` +**Label Expansions:** +```asm +; ldb buffer, rg2 expands to: +lli buffer, rg2 ; Load lower 16 bits of buffer address +lui buffer, rg2 ; Load upper 16 bits of buffer address +ldb rg2, rg2 ; Load byte from address in rg2 + +; ldw stack, bpr expands to: +lli stack, bpr ; Load lower 16 bits of stack address +lui stack, bpr ; Load upper 16 bits of stack address +ldw bpr, bpr ; Load word from address in bpr +``` +#### Store Instructions + +| Mnemonic | Operands | Description | +|----------|----------|-------------| +| **STB** | `src_reg, base_reg [, offset]`
`src_reg, label [, offset]` | Store byte to memory | +| **STH** | `src_reg, base_reg [, offset]`
`src_reg, label [, offset]` | Store half-word to memory | +| **STW** | `src_reg, base_reg [, offset]`
`src_reg, label [, offset]` | Store word to memory | + +**Examples:** +```asm +; Direct register addressing +stb rg0, rg1 ; Store byte from rg0 to address in rg1 +stw rg0, rg1, 12 ; Store word to (rg1 + 12) + +; Label addressing +stb acc, buffer ; Store byte from accumulator to 'buffer' +stw rg1, current ; Store word to 'current' variable +``` +**Label Expansions:** +```asm +; stb acc, buffer expands to: +lli buffer, rgf ; Load lower 16 bits of buffer address +lui buffer, rgf ; Load upper 16 bits of buffer address +stb acc, rgf ; Store byte from acc to address in rgf + +; stw rg1, current expands to: +lli current, rgf ; Load lower 16 bits of current address +lui current, rgf ; Load upper 16 bits of current address +stw rg1, rgf ; Store word from rg1 to address in rgf +``` +### Immediate Load Instructions + +| Mnemonic | Operands | Description | +|----------|----------|------------------------------------------------------------------------| +| **LLI** | `imm, dest_reg` | Load 16-bit immediate into lower 16 bits
**Clears upper 16 bits!** | +| **LUI** | `imm, dest_reg` | Load 16-bit immediate into upper 16 bits | + +**Usage** + +ensure that you always run **Lli** before **Lui** as **Lli** clears the upper 16 bits. + +**Examples:** +```asm +lli 0x1234, rg0 ; Load 0x1234 into lower 16 bits of rg0 +lui 0xABCD, rg0 ; Load 0xABCD into upper 16 bits of rg0 +``` +### Jump Instructions + +| Mnemonic | Operands | Description | +|----------|----------|-------------| +| **JMP** | `addr [, offset_reg]`
`imm, offset_reg` | Unconditional jump | +| **JEQ** | `addr [, offset_reg]` | Jump if equal flag set | +| **JNE** | `addr [, offset_reg]` | Jump if not equal flag set | +| **JGT** | `addr [, offset_reg]` | Jump if greater than flag set | +| **JGE** | `addr [, offset_reg]` | Jump if greater or equal flags set | +| **JLT** | `addr [, offset_reg]` | Jump if less than flag set | +| **JLE** | `addr [, offset_reg]` | Jump if less or equal flags set | + +**Examples:** +```asm +jmp start ; Jump to label 'start' +jmp 4, ret ; Jump to address (4 + ret register) +jeq end ; Jump to 'end' if equal flag set +jgt loop ; Jump to 'loop' if greater than flag set +``` +### Arithmetic Instructions + +| Mnemonic | Operands | Description | +|----------|----------|-------------| +| **ADD** | `src1_reg, src2_reg, dest_reg` | Addition | +| **SUB** | `src1_reg, src2_reg, dest_reg` | Subtraction | +| **IADD** | `src_reg, imm [, dest_reg]` | Immediate addition | +| **ISUB** | `src_reg, imm [, dest_reg]` | Immediate subtraction | +| **INC** | `reg` | Increment register by 1 | +| **DEC** | `reg` | Decrement register by 1 | + +**Examples:** +```asm +add rg0, rg1, rg2 ; rg2 = rg0 + rg1 +sub rg0, rg1, rg2 ; rg2 = rg0 - rg1 +iadd rg0, 10 ; rg0 = rg0 + 10 +// or using alternate syntax +addi rg0, 1 ; rg0 = rg0 + 1 +inc rg0 ; rg0 = rg0 + 1 +``` +### Bitwise Operations + +| Mnemonic | Operands | Description | +|----------|----------|-------------| +| **AND** | `src1_reg, src2_reg, dest_reg` | Bitwise AND | +| **OR** | `src1_reg, src2_reg, dest_reg` | Bitwise OR | +| **XOR** | `src1_reg, src2_reg, dest_reg` | Bitwise XOR | +| **NOT** | `src_reg, dest_reg` | Bitwise NOT | +| **NAND** | `src1_reg, src2_reg, dest_reg` | Bitwise NAND | +| **NOR** | `src1_reg, src2_reg, dest_reg` | Bitwise NOR | +| **XNOR** | `src1_reg, src2_reg, dest_reg` | Bitwise XNOR | + +**Examples:** +```asm +and rg0, rg1, rg2 ; rg2 = rg0 & rg1 +not rg0, rg1 ; rg1 = ~rg0 +``` +### Shift Operations + +| Mnemonic | Operands | Description | +|----------|----------|-------------| +| **SHL** | `reg, shift_amount` | Shift left | +| **SHR** | `reg, shift_amount` | Shift right | + +**Examples:** +```asm +shl rg0, 2 ; Shift rg0 left by 2 bits +shr rg0, 3 ; Shift rg0 right by 3 bits +``` +### Comparison and Control + +| Mnemonic | Operands | Description | +|----------|----------|-------------| +| **CMP** | `reg1, reg2` | Compare registers and set flags | + +**Examples:** +```asm +cmp rg0, zero ; Compare rg0 with zero register +cmp rg1, rg2 ; Compare rg1 with rg2 +``` +### System Instructions + +| Mnemonic | Operands | Description | +|----------|----------|-------------| +| **HLT** | - | Halt processor execution | +| **NOP** | - | No operation | +| **INT** | `interrupt_code` | Trigger interrupt | +| **IRT** | - | Return from interrupt | + +**Examples:** +```asm +hlt ; Stop processor execution +int 0x21 ; Trigger interrupt 0x21 +``` +## Pseudo-Instructions + +### Data Definition + +| Mnemonic | Syntax | Description | +|----------|--------|-------------| +| **DB** | `name: value1 [, value2, ...]` | Define bytes | +| **DH** | `name: value1 [, value2, ...]` | Define half-words | +| **DW** | `name: value1 [, value2, ...]` | Define words | + +**Examples:** +```asm +db message: "Hello World", 0 +dh numbers: 1000, 2000, 3000 +dw stack: 0x10000 +``` +### Memory Reservation + +| Mnemonic | Syntax | Description | +|----------|--------|-------------| +| **RESB** | `name: size` | Reserve bytes | +| **RESH** | `name: size` | Reserve half-words | +| **RESW** | `name: size` | Reserve words | + +**Examples:** +```asm +resb buffer: 256 ; Reserve 256 bytes +resh array: 100 ; Reserve space for 100 half-words +resw heap: 1024 ; Reserve space for 1024 words +``` +### Stack Operations + +| Mnemonic | Operands | Description | +|----------|----------|-------------| +| **PUSH** | `reg` | Push register value onto stack | +| **POP** | `reg` | Pop stack value into register | + +**Examples:** +```asm +push rg0 ; Push rg0 value onto stack +pop ret ; Pop return address +``` +### Memory Access Shortcuts + +| Mnemonic | Operands | Description | +|----------|----------|-------------| +| **LWI** | `name, reg` | Load address into register | + +**Examples:** +```asm +lwi string, rg1 ; Load address of 'string' into rg1 +``` + +### Function Control + +| Mnemonic | Operands | Description | +|----------|----------|-------------| +| **CALL** | `namespace::function` | Call a function with automatic return address management | +| **RETURN** | - | Return from a function to the caller | + +**Examples:** +```asm +call print::print ; Call the print function from the print namespace +return ; Return from the current function +``` + +### Module System + +| Mnemonic | Syntax | Description | +|----------|--------|-------------| +| **INCLUDE** | `module_name "path"` | Include module | + +**Examples:** +```asm +include print "print.dsa" +include fib "fib.dsa" +``` +## Library Examples + +### Multiplication Library (multiply.dsa) + +```asm +// multiply.dsa +// usage: +// +// include multiply "" +// +// usage for multiply: +// push (arg1) +// push (arg0) +// call multiply::multiply +// pop (arg0) +// pop (arg1) + +multiply: + push bpr + mov spr, bpr + + ldw bpr, rg0, 8 // load op 1 + ldw bpr, rg1, 12 // load op 2 + + lli 0, acc // initialize accumulator + +start: + add acc, rg0, acc + dec rg1 + + cmp rg1, zero + jgt start + +end: + stw acc, bpr, 8 // store result for caller + mov bpr, spr + pop bpr + return +``` + +### Print Library (print.dsa) + +```asm +// print.dsa +// usage: +// +// include print "" +// +// usage for print: +// push (register containing address of string) +// call print::print +// pop zero +// +// usage for reset: +// call print::reset + +dw display: 0x20000 +dw current: 0x20000 + +// prints the given text to the screen. +print: + push bpr + mov spr, bpr + + ldw bpr, rg0, 8 // get string address argument + ldw current, rg1 // get current display position + +print_loop: + ldb rg0, acc + stb acc, rg1 + + iadd rg0, 1 + iadd rg1, 1 + + cmp acc, zero + jne print_loop + jmp end + +// return +end: + stw rg1, current + + mov bpr, spr + pop bpr + return + +// resets the cursor position on the screen +reset: + push bpr + mov spr, bpr + ldw display, rg1 + stw rg1, current + mov bpr, spr + pop bpr + return +``` + +### Example Program (main.dsa) + +```asm +include print "./print.dsa" + +dw stack: 0x10000 +db string: "'To confuse your enemy, you must first confuse yourself' - Probably Sun Tzu." + +init: + // set up a stack. + ldw stack, bpr + mov bpr, spr + +start: + lwi string, rg1 + + // push string address argument + push rg1 + // call print function + call print::print + // clean up stack + pop rg1 + + hlt +``` \ No newline at end of file diff --git a/resources/ideas/dsa_binary_format.md b/resources/ideas/dsa_binary_format.md new file mode 100644 index 0000000..3a372ad --- /dev/null +++ b/resources/ideas/dsa_binary_format.md @@ -0,0 +1,10 @@ +# DSA File formatting specification. + +First, a clarification on what formats this document references. + +- .dsb: DSA Binary object, similar to a .o object file +- .dse: DSA Executable file, similar to a .exe/ELF binary + +## Format Specification + +### DSB binary format -- 2.47.3 From 5573c5a609a923611c92ec4c10d51383c6d8b4b4 Mon Sep 17 00:00:00 2001 From: zxq5 Date: Mon, 2 Feb 2026 11:15:45 +0000 Subject: [PATCH 11/53] minor code changes for codegen --- c_compiler/example.dsc | 4 +--- c_compiler/src/codegen.rs | 9 +++------ c_compiler/src/main.rs | 2 -- 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/c_compiler/example.dsc b/c_compiler/example.dsc index 1582321..232535d 100644 --- a/c_compiler/example.dsc +++ b/c_compiler/example.dsc @@ -20,8 +20,6 @@ int greater(int a, int b) { } int main() { - printnum(greater(5, add_(5, 5))); - - printnum(factorial(5)); + printnum(-5); return 0; } diff --git a/c_compiler/src/codegen.rs b/c_compiler/src/codegen.rs index d5bbb66..ee3a598 100644 --- a/c_compiler/src/codegen.rs +++ b/c_compiler/src/codegen.rs @@ -1,8 +1,8 @@ +use std::collections::HashMap; use std::hash::Hash; use std::sync::LazyLock; use std::sync::atomic::AtomicU32; use std::time::SystemTime; -use std::{collections::HashMap, path::PathBuf}; use chrono::{DateTime, Local}; @@ -24,10 +24,7 @@ pub struct CodeGenerator { } static GLOBAL_METHODS: LazyLock> = LazyLock::new(|| { - hash_map! { - "print" => "print::print", - "printnum" => "print::print_num" - } + HashMap::from([("print", "print::print"), ("printnum", "print::print_num")]) }); fn import(name: &str, path: &str) -> String { @@ -183,7 +180,7 @@ impl CodeGenerator { for (i, param) in params.iter().enumerate() { let offset = 8 + (i as i32 * 4); // Parameters start at bpr+8 // Track that this parameter is at a stack location - let (reg, mut load_code) = self.allocator.alloc_var(¶m.name).unwrap(); + let (reg, load_code) = self.allocator.alloc_var(¶m.name).unwrap(); code.extend(load_code); code.push(format!("\tldw bpr, {}, {}", reg, offset)); } diff --git a/c_compiler/src/main.rs b/c_compiler/src/main.rs index eae157b..06cbfed 100644 --- a/c_compiler/src/main.rs +++ b/c_compiler/src/main.rs @@ -1,5 +1,3 @@ -#![feature(hash_map_macro)] - use std::fmt; use crate::{codegen::CodeGenerator, lexer::Lexer, parser::Parser}; -- 2.47.3 From 3afeafc9d4011b0b466061130cd551b31c098988 Mon Sep 17 00:00:00 2001 From: zxq5 Date: Tue, 3 Feb 2026 02:11:30 +0000 Subject: [PATCH 12/53] - compiler works for basic maths expressions and functions - basic pointers and reading values from pointers works - writing to pointers not yet implemented (looks painful so a problem for tomorrow) - updated print library. the compiler has this hardcoded in all programs for now --- compiler/Cargo.toml | 1 + {c_compiler => compiler}/bacon.toml | 4 +- compiler/src/codegen.rs | 712 +++++++++++++++++++++++ compiler/src/lexer.rs | 35 +- compiler/src/main.rs | 48 +- compiler/src/parser.rs | 855 +++++++++++++++++++++------- compiler/src/parserprototype.rs | 435 -------------- compiler/src/registers.rs | 375 ++++++++++++ compiler/src/semantic_analyser.rs | 13 + resources/dsa/code.dsa | 139 ----- resources/dsa/lib/io/print.dsa | 29 +- resources/dsa/lib/maths/core.dsa | 43 ++ resources/dsa/lib/memory/alloc.dsa | 37 ++ resources/dsa/main.dsa | 13 +- resources/dsa/output.dsa | 67 +++ resources/dsc/example.dsc | 10 +- resources/ideas/design.rnote | Bin 0 -> 490603 bytes 17 files changed, 2009 insertions(+), 807 deletions(-) rename {c_compiler => compiler}/bacon.toml (98%) create mode 100644 compiler/src/codegen.rs delete mode 100644 compiler/src/parserprototype.rs create mode 100644 compiler/src/registers.rs create mode 100644 compiler/src/semantic_analyser.rs delete mode 100644 resources/dsa/code.dsa create mode 100644 resources/dsa/lib/memory/alloc.dsa create mode 100644 resources/dsa/output.dsa create mode 100644 resources/ideas/design.rnote diff --git a/compiler/Cargo.toml b/compiler/Cargo.toml index 15e1473..c228e0b 100644 --- a/compiler/Cargo.toml +++ b/compiler/Cargo.toml @@ -5,3 +5,4 @@ edition.workspace = true authors.workspace = true [dependencies] +chrono = "0.4.43" diff --git a/c_compiler/bacon.toml b/compiler/bacon.toml similarity index 98% rename from c_compiler/bacon.toml rename to compiler/bacon.toml index 87af4fa..f016c40 100644 --- a/c_compiler/bacon.toml +++ b/compiler/bacon.toml @@ -84,8 +84,8 @@ command = [ "cargo", "run", "--color", "always", "--", - "example.dsc", - "../resources/dsa/code.dsa" + "../resources/dsc/example.dsc", + "../resources/dsa/output.dsa" # put launch parameters for your program behind a `--` separator ] need_stdout = true diff --git a/compiler/src/codegen.rs b/compiler/src/codegen.rs new file mode 100644 index 0000000..878f167 --- /dev/null +++ b/compiler/src/codegen.rs @@ -0,0 +1,712 @@ +use std::collections::HashMap; +use std::hash::Hash; +use std::sync::LazyLock; +use std::sync::atomic::AtomicU32; +use std::time::SystemTime; + +use chrono::{DateTime, Local}; + +use crate::registers::{Location, RegisterAllocator}; +use crate::{block, cmd, comment, dsa}; + +use crate::parser::{ + BinaryOperator, CompilerError, ConstExpr, Declaration, Dependency, Expression, + Program, Statement, UnaryOperator, Variable, +}; + +pub struct CodeGenerator { + ast: Program, + imports: HashMap, + globals: Vec, + functions: Vec, + symbols: Vec, + allocator: RegisterAllocator, +} + +static GLOBAL_METHODS: LazyLock> = LazyLock::new(|| { + HashMap::from([ + ("print", "print::print"), + ("println", "print::println"), + ("printnum", "print::print_num"), + ("print_space", "print::print_whitespace"), + ("print_char", "print::print_byte"), + ("print_word", "print::print_word"), + ]) +}); + +fn import(name: &str, path: &str) -> String { + format!("include {name}: \"{}\"", path) +} + +impl CodeGenerator { + const RET: &'static str = "\tjmp _ret"; + + pub fn new(ast: Program) -> Self { + CodeGenerator { + ast, + imports: HashMap::new(), + globals: Vec::new(), + functions: Vec::new(), + symbols: Vec::new(), + allocator: RegisterAllocator::new(), + } + } + + pub fn include(&mut self, name: &str, path: &str) { + self.imports.insert(name.to_string(), path.to_string()); + } + + fn is_global(&self, name: &str) -> bool { + // Check if this variable is in the globals list + self.globals + .iter() + .any(|g| g.contains(&format!("dw {}:", name))) + } + + pub fn generate(&mut self) -> Result { + // always include the print library for debugging! + self.include("print", "./lib/io/print.dsa"); + + for block in self.ast.clone().declarations { + match block { + Declaration::Variable { + var: Variable { name, .. }, + .. + } => self.symbols.push(name), + Declaration::Function { name, .. } => self.symbols.push(name), + Declaration::Dependency(Dependency { name, .. }) => { + self.symbols.push(name) + } + } + } + + for block in self.ast.clone().declarations { + self.generate_block(block.clone())?; + } + + self.generate_layout() + } + + fn generate_layout(&mut self) -> Result { + let datetime: DateTime = SystemTime::now().into(); + Ok(dsa![ + "", + comment!("GENERATED BY DSC COMPILER"), + comment!(format!( + "Generated at {}", + datetime.format("%Y-%m-%d %H:%M:%S") + )), + "", + // imports + comment!("Imports"), + self.imports + .iter() + .map(|(k, v)| import(k, v)) + .collect::>() + .join("\n"), + "", + // reserved memory + comment!("Globals & Reserved Memory"), + self.globals.join("\n"), + "", + // entry point + comment!("Entry Point"), + "dw stack: 0x10000", + "db message: \"Process Exited with code:\"", + block! [ "_init" + dsa![ldw stack, bpr], + dsa![mov bpr, spr], + dsa![push zero], + dsa![call main], + dsa![call print::print_newline], + dsa![lwi message, rg0], + dsa![push rg0], + dsa![call print::print], + dsa![pop zero], + dsa![call print::print_hex_word], + dsa![pop zero], + dsa![hlt] + ], + "", + comment!("Return"), + block! [ "_ret" + dsa![mov bpr, spr], + dsa![pop bpr], + dsa![return] + ], + comment!("Compiled Code Starts..."), + // block! [ "main" + // dsa![push bpr], + // dsa![mov spr, bpr], + // dsa![lwi 67, rg1], + // dsa![stw rg1, spr, 8], + // dsa![mov bpr, spr], + // dsa![pop bpr], + // dsa![return] + // ], + self.functions.join("\n"), + ]) + } + + fn generate_global(&mut self, name: &str, init: Option) { + self.globals.push(format!( + "dw {}: {}", + name, + init.unwrap_or(ConstExpr::Number(0)) + )) + } + + fn generate_block(&mut self, block: Declaration) -> Result<(), CompilerError> { + match block { + Declaration::Variable { var, init, .. } => { + self.generate_global(&var.name, init) + } + Declaration::Function { + name, + return_type, + params, + body, + } => { + let func = self.generate_function(&name, ¶ms, &body).join("\n"); + + self.functions.push(format!("{func}\n")); + } + Declaration::Dependency(Dependency { name, path }) => { + self.imports.insert(name, path); + } + }; + + Ok(()) + } + + // Example: Generate code for a function + fn generate_function( + &mut self, + name: &str, + params: &[Variable], + body: &[Statement], + ) -> Vec { + let mut code = Vec::new(); + + // Reset allocator for new function + self.allocator.reset(); + + // Function prologue + code.push(format!("{}:", name)); + code.push("\tpush bpr".to_string()); + code.push("\tmov spr, bpr".to_string()); + code.push(String::new()); + + // Allocate parameters to registers or stack locations + for (i, param) in params.iter().enumerate() { + let offset = 8 + (i as i32 * 4); // Parameters start at bpr+8 + // Track that this parameter is at a stack location + let (reg, load_code) = self.allocator.alloc_var(¶m.name).unwrap(); + code.extend(load_code); + code.push(format!("\tldw bpr, {}, {}", reg, offset)); + } + + // Generate code for function body + for stmt in body { + let stmt_code = self.generate_statement(stmt).unwrap(); + code.extend(stmt_code); + } + + // automatically return at function end + if let Some(x) = code.last() + && x == Self::RET + { + } else { + code.push(Self::RET.to_string()); + } + + code + } + + // Example: Generate code for a statement + fn generate_statement( + &mut self, + stmt: &Statement, + ) -> Result, CompilerError> { + let mut code = Vec::new(); + + match stmt { + Statement::Declaration { var, value } => { + if let Some(expr) = value { + // Evaluate expression + let (result_reg, expr_code) = self.generate_expression(expr, true)?; + code.extend(expr_code); + + // Store result in variable + let store_code = self.allocator.store_var(&var.name, &result_reg); + code.extend(store_code); + + // Free temporary register + self.allocator.free_temp(&result_reg); + } else { + // Just declaring variable without initialization + self.allocator.alloc_var(&var.name)?; + } + } + + Statement::Break => unimplemented!(), + Statement::Continue => unimplemented!(), + + Statement::Assign { varname, value } => { + // Evaluate expression + let (result_reg, expr_code) = self.generate_expression(value, true)?; + code.extend(expr_code); + + // Check if this is a global variable + if self.is_global(varname) { + // Store to global label + code.push(format!("\tstw {}, {}", result_reg, varname)); + } else { + // Store result in local variable + let store_code = self.allocator.store_var(varname, &result_reg); + code.extend(store_code); + } + + // Free temporary register + self.allocator.free_temp(&result_reg); + } + + Statement::Return(expr) => { + if let Some(e) = expr { + let (result_reg, expr_code) = self.generate_expression(e, true)?; + code.extend(expr_code); + code.push(format!("\tstw {}, bpr, 8", result_reg)); + code.push(format!("\tjmp _ret")); + self.allocator.free_temp(&result_reg); + } + } + + Statement::If { + condition, + then_stmt, + else_stmt, + } => { + // Generate condition + let (cond_reg, cond_code) = self.generate_expression(condition, true)?; + code.extend(cond_code); + + // Compare with zero + code.push(format!("\tcmp {}, zero", cond_reg)); + self.allocator.free_temp(&cond_reg); + + // Generate unique labels + let then_label = format!("_then_{}", self.get_unique_label()); + let else_label = format!("_else_{}", self.get_unique_label()); + let end_label = format!("_end_{}", self.get_unique_label()); + + // Jump to else if condition is false (equal to zero) + code.push(format!("\tjeq {}", else_label)); + + // Then block + code.push(format!("{}:", then_label)); + for s in then_stmt { + code.extend(self.generate_statement(s)?); + } + + if then_stmt.len() == 0 { + code.push("\tnop".to_string()); + } + + code.push(format!("\tjmp {}", end_label)); + + // Else block + code.push(format!("{}:", else_label)); + for s in else_stmt { + code.extend(self.generate_statement(s)?); + } + + if else_stmt.len() == 0 { + code.push("\tnop".to_string()); + } + + code.push(format!("{}:", end_label)); + } + + Statement::While { condition, body } => { + let loop_start = format!("_while_start_{}", self.get_unique_label()); + let loop_end = format!("_while_end_{}", self.get_unique_label()); + + code.push(format!("{}:", loop_start)); + + // Generate condition + let (cond_reg, cond_code) = self.generate_expression(condition, true)?; + code.extend(cond_code); + + code.push(format!("\tcmp {}, zero", cond_reg)); + self.allocator.free_temp(&cond_reg); + + code.push(format!("\tjeq {}", loop_end)); + + // Loop body + for s in body { + code.extend(self.generate_statement(s)?); + } + + code.push(format!("\tjmp {}", loop_start)); + code.push(format!("{}:", loop_end)); + } + + Statement::Loop(body) => { + let loop_start = format!("_loop_start_{}", self.get_unique_label()); + + code.push(format!("{}:", loop_start)); + + for s in body { + code.extend(self.generate_statement(s)?); + } + + code.push(format!("\tjmp {}", loop_start)); + } + + Statement::Expression { expr } => { + let (result_reg, expr_code) = self.generate_expression(expr, false)?; + code.extend(expr_code); + self.allocator.free_temp(&result_reg); + } + + Statement::Block(statements) => { + for s in statements { + code.extend(self.generate_statement(s)?); + } + } + } + + Ok(code) + } + + // Example: Generate code for an expression + // Returns (register containing result, assembly code) + fn generate_expression( + &mut self, + expr: &Expression, + use_result: bool, + ) -> Result<(String, Vec), CompilerError> { + let mut code = Vec::new(); + + match expr { + Expression::StringLiteral(value) => { + let (reg, alloc_code) = self.allocator.alloc_temp()?; + code.extend(alloc_code); + + // write string into memory + let uuid = self.get_unique_label(); + code.push(format!("\tdb str_{uuid}: \"{value}\"")); + + // Load pointer to string + code.push(format!("\tlwi str_{uuid}, {reg}")); + + Ok((reg, code)) + } + + Expression::CharLiteral(value) => { + let (reg, alloc_code) = self.allocator.alloc_temp()?; + code.extend(alloc_code); + + // Load immediate value + code.push(format!("\tlli {}, {} // '{value}'", *value as u8, reg)); + + Ok((reg, code)) + } + + Expression::Number(value) => { + let (reg, alloc_code) = self.allocator.alloc_temp()?; + code.extend(alloc_code); + + // Load immediate value + code.push(format!("\tlli {}, {}", value & 0xFFFF, reg)); + if *value > 0xFFFF || *value < 0 { + code.push(format!("\tlui {}, {}", (value >> 16) & 0xFFFF, reg)); + } + + Ok((reg, code)) + } + + Expression::Variable { name, .. } => { + if self.is_global(&name.name) { + // Allocate a temporary register for the global + let (reg, alloc_code) = self.allocator.alloc_temp()?; + code.extend(alloc_code); + + // Load from global label + code.push(format!("\tldw {}, {}", name.name, reg)); + + Ok((reg, code)) + } else { + // Local variable - use existing allocator logic + let (reg, load_code) = self.allocator.load_var(&name.name)?; + code.extend(load_code); + Ok((reg, code)) + } + } + + Expression::Binary { op, left, right } => { + // Evaluate left operand + let (left_reg, left_code) = self.generate_expression(left, true)?; + code.extend(left_code); + + // Evaluate right operand + let (right_reg, right_code) = self.generate_expression(right, true)?; + code.extend(right_code); + + // Allocate result register + let (result_reg, result_alloc) = self.allocator.alloc_temp()?; + code.extend(result_alloc); + + // Generate operation + match op { + BinaryOperator::Add => { + code.push(format!( + "\tadd {}, {}, {}", + left_reg, right_reg, result_reg + )); + } + BinaryOperator::Sub => { + code.push(format!( + "\tsub {}, {}, {}", + left_reg, right_reg, result_reg + )); + } + BinaryOperator::Mul => { + self.include("maths", "./lib/maths/core.dsa"); + // Call multiply function + code.push(format!("\tpush {}", right_reg)); + code.push(format!("\tpush {}", left_reg)); + code.push("\tcall maths::multiply".to_string()); + code.push(format!("\tpop {}", result_reg)); + code.push("\tpop zero".to_string()); + } + // Comparison operators - return 1 (true) or 0 (false) + BinaryOperator::Eq => { + code.push(format!("\tcmp {}, {}", left_reg, right_reg)); + code.push(format!("\tlli 0, {}", result_reg)); + let end_label = format!("_cmp_end_{}", self.get_unique_label()); + code.push(format!("\tjne {}", end_label)); // If not equal, skip setting to 1 + code.push(format!("\tlli 1, {}", result_reg)); + code.push(format!("{}:", end_label)); + } + BinaryOperator::Ne => { + code.push(format!("\tcmp {}, {}", left_reg, right_reg)); + code.push(format!("\tlli 0, {}", result_reg)); + let end_label = format!("_cmp_end_{}", self.get_unique_label()); + code.push(format!("\tjeq {}", end_label)); // If equal, skip setting to 1 + code.push(format!("\tlli 1, {}", result_reg)); + code.push(format!("{}:", end_label)); + } + BinaryOperator::Lt => { + code.push(format!("\tcmp {}, {}", left_reg, right_reg)); + code.push(format!("\tlli 0, {}", result_reg)); + let end_label = format!("_cmp_end_{}", self.get_unique_label()); + code.push(format!("\tjge {}", end_label)); // If greater or equal, skip setting to 1 + code.push(format!("\tlli 1, {}", result_reg)); + code.push(format!("{}:", end_label)); + } + BinaryOperator::Le => { + code.push(format!("\tcmp {}, {}", left_reg, right_reg)); + code.push(format!("\tlli 0, {}", result_reg)); + let end_label = format!("_cmp_end_{}", self.get_unique_label()); + code.push(format!("\tjgt {}", end_label)); // If greater than, skip setting to 1 + code.push(format!("\tlli 1, {}", result_reg)); + code.push(format!("{}:", end_label)); + } + BinaryOperator::Gt => { + code.push(format!("\tcmp {}, {}", left_reg, right_reg)); + code.push(format!("\tlli 0, {}", result_reg)); + let end_label = format!("_cmp_end_{}", self.get_unique_label()); + code.push(format!("\tjle {}", end_label)); // If less or equal, skip setting to 1 + code.push(format!("\tlli 1, {}", result_reg)); + code.push(format!("{}:", end_label)); + } + BinaryOperator::Ge => { + code.push(format!("\tcmp {}, {}", left_reg, right_reg)); + code.push(format!("\tlli 0, {}", result_reg)); + let end_label = format!("_cmp_end_{}", self.get_unique_label()); + code.push(format!("\tjlt {}", end_label)); // If less than, skip setting to 1 + code.push(format!("\tlli 1, {}", result_reg)); + code.push(format!("{}:", end_label)); + } + _ => unimplemented!(), + } + + // Free operand registers (allocator will protect variables) + self.allocator.free_temp(&left_reg); + self.allocator.free_temp(&right_reg); + + Ok((result_reg, code)) + } + + Expression::Call { name, args } => { + // Save caller-saved registers and track which ones we saved + let saved_regs = self.allocator.get_caller_saved_registers(); + for reg in &saved_regs { + code.push(format!("\tpush {}", reg)); + } + + // Evaluate and push arguments in reverse order + let mut arg_regs = Vec::new(); + for arg in args.iter().rev() { + let (arg_reg, arg_code) = self.generate_expression(arg, true)?; + code.extend(arg_code); + code.push(format!("\tpush {}", arg_reg)); + arg_regs.push(arg_reg); + } + + if GLOBAL_METHODS.contains_key(name.name.as_str()) { + code.push(format!("\tcall {}", GLOBAL_METHODS[name.name.as_str()])); + } else if self.symbols.contains(&name.name) { + // Call local function + code.push(format!("\tcall {}", name.name)); + } else { + return Err(CompilerError::Undefined(name.clone())); + } + + let result_reg = String::new(); + + if use_result { + let (result_reg, result_alloc) = self.allocator.alloc_temp()?; + + code.extend(result_alloc); + code.push(format!("\tpop {}", result_reg)); + + // Clean up arguments + if args.len() > 1 { + for _ in 0..(args.len() - 1) { + code.push("\tpop zero".to_string()); + } + } + } else { + // Clean up arguments + if args.len() > 0 { + for _ in 0..(args.len()) { + code.push("\tpop zero".to_string()); + } + } + } + + // Restore caller-saved registers in reverse order (LIFO) + for reg in saved_regs.iter().rev() { + code.push(format!("\tpop {}", reg)); + } + + // Free argument registers + for reg in arg_regs { + self.allocator.free_temp(®); + } + + Ok((result_reg, code)) + } + + Expression::Unary { op, operand } => { + let (operand_reg, operand_code) = + self.generate_expression(operand, true)?; + code.extend(operand_code); + + let (result_reg, result_alloc) = self.allocator.alloc_temp()?; + code.extend(result_alloc); + + match op { + UnaryOperator::Minus => { + // Negate: result = 0 - operand + code.push(format!("\tsub zero, {}, {}", operand_reg, result_reg)); + } + UnaryOperator::Plus => { + // Just move + code.push(format!("\tmov {}, {}", operand_reg, result_reg)); + } + UnaryOperator::Dereference => { + code.push(format!("\tldw {}, {}", operand_reg, result_reg)); + } + UnaryOperator::Reference => { + code.extend(self.allocator.spill_register(&operand_reg)?); + code.push(format!( + "\tsubi bpr {} {}", + -(4 + self.allocator.get_stack_offset()), + result_reg + )) + } + } + + self.allocator.free_temp(&operand_reg); + Ok((result_reg, code)) + } + + Expression::Empty => Ok(("zero".to_string(), code)), + } + } + + // Helper for generating unique labels + fn get_unique_label(&mut self) -> String { + // You'd implement a counter here + static COUNTER: AtomicU32 = AtomicU32::new(0); + + let val = COUNTER.fetch_add(1, std::sync::atomic::Ordering::SeqCst); + (val + 1).to_string() + } +} + +/// Build a single string from any number of arguments. +/// Each argument must implement `Display` or be convertible to a string. +#[macro_export] +macro_rules! dsa { + ($($arg:expr),* $(,)?) => {{ + // Start with an empty String – we’ll grow it as we go. + use std::fmt::Write; + let mut s = ::std::string::String::new(); + $( + // `write!` is cheaper than `format!` for each element + // because it re‑uses the same buffer. + + write!(s, "{}\n", $arg).expect("write to String failed"); + )* + s + }}; +} + +// ──────────────────────── dsa! ──────────────────────── +// A tiny helper that just turns its token‑stream into a string. +// The trailing comma is kept – it’s part of the syntax you want. +#[macro_export] +macro_rules! cmd { + ($($tokens:tt)*) => {{ + // We’ll just stringify the tokens and return a String. + format!("{}", concat!(stringify!($tokens), "\n")) + }}; +} + +// ──────────────────────── block! ──────────────────────── +// Usage: +// +// let asm = block![ "name" +// dsa![mov rg0, rg1], +// dsa![add rg1, rg1] +// ]; +// +// `asm` is a `&'static str` containing: +// +// name: +// mov rg0, rg1 +// add rg1, rg1 +// +#[macro_export] +macro_rules! block { + // The first token must be a string literal – that’s the label. + ($label:literal $(dsa![$($ins:tt)*]),* ) => {{ + // Build a single string at compile time. + const CODE: &str = concat!( + $label, ":\n", + // Each `dsa!` call yields a string like `"mov rg0, rg1"`. + // We add a newline after each one to get the desired layout. + $(concat!("\t", stringify!($($ins)*), "\n")),* + ); + CODE + }}; +} + +#[macro_export] +macro_rules! comment { + ($text:expr) => {{ format!("// {}", $text) }}; +} diff --git a/compiler/src/lexer.rs b/compiler/src/lexer.rs index e76e131..142343f 100644 --- a/compiler/src/lexer.rs +++ b/compiler/src/lexer.rs @@ -9,15 +9,19 @@ pub enum Token { If, Else, Loop, + While, Break, Return, Continue, Include, + Static, + Const, // Identifiers and literals Identifier(String), String(String), - Number(i64), + Integer(u32), + Char(char), // Symbols LeftParen, // ( @@ -30,9 +34,10 @@ pub enum Token { // Pipe, // | // Operators - Plus, // + - Minus, // - - Star, // * + Plus, // + + Minus, // - + Star, // * + Amphersand, Slash, // / Assign, // = EqualEqual, // == @@ -51,18 +56,22 @@ pub enum Token { impl Token { pub fn tt(&self) -> &str { match self { + Token::Const => "Const", + Token::Static => "Static", Token::Include => "Include", Token::Fn => "Fn", Token::If => "If", Token::Let => "Let", Token::Else => "Else", Token::Loop => "Loop", + Token::While => "While", Token::Break => "Break", Token::Return => "Return", Token::Continue => "Continue", Token::Identifier(_) => "Identifier", Token::String(_) => "String", - Token::Number(_) => "Number", + Token::Integer(_) => "UnsignedInt", + Token::Char(_) => "Char", Token::LeftParen => "LeftParen", Token::RightParen => "RightParen", Token::LeftBrace => "LeftBrace", @@ -75,6 +84,7 @@ impl Token { Token::Plus => "Plus", Token::Minus => "Minus", Token::Star => "Star", + Token::Amphersand => "Amphersand", Token::Slash => "Slash", Token::Assign => "Assign", Token::EqualEqual => "EqualEqual", @@ -143,7 +153,7 @@ impl<'a> Lexer<'a> { } fn read_number(&mut self) -> i64 { - let mut num_str = String::new(); + let mut num_str = String::from(self.current.unwrap()); while let Some(&c) = self.peek() { if c.is_ascii_digit() { num_str.push(c); @@ -152,7 +162,7 @@ impl<'a> Lexer<'a> { break; } } - num_str.parse().unwrap_or(0) + num_str.parse().unwrap() } fn match_next(&mut self, expected: char) -> bool { @@ -176,6 +186,7 @@ impl<'a> Lexer<'a> { Some(';') => Token::Semicolon, Some(':') => Token::Colon, Some(',') => Token::Comma, + Some('&') => Token::Amphersand, // Some('|') => Token::Pipe, Some('+') => Token::Plus, Some('*') => Token::Star, @@ -235,15 +246,19 @@ impl<'a> Lexer<'a> { "fn" => Token::Fn, "if" => Token::If, "else" => Token::Else, + "while" => Token::While, "loop" => Token::Loop, "break" => Token::Break, "return" => Token::Return, "continue" => Token::Continue, "include" => Token::Include, + "let" => Token::Let, + "const" => Token::Const, + "static" => Token::Static, _ => Token::Identifier(ident), } } else if c.is_ascii_digit() { - Token::Number(self.read_number()) + Token::Integer(self.read_number() as u32) } else { // Skip unknown characters for now self.advance(); @@ -298,8 +313,8 @@ mod tests { assert_eq!(lexer.next_token(), Token::Identifier("x".to_string())); assert_eq!(lexer.next_token(), Token::Identifier("y42".to_string())); assert_eq!(lexer.next_token(), Token::Identifier("_test".to_string())); - assert_eq!(lexer.next_token(), Token::Number(123)); - assert_eq!(lexer.next_token(), Token::Number(45)); + assert_eq!(lexer.next_token(), Token::Integer(123)); + assert_eq!(lexer.next_token(), Token::Integer(45)); assert_eq!(lexer.next_token(), Token::Eof); } diff --git a/compiler/src/main.rs b/compiler/src/main.rs index b1a127c..854cd9d 100644 --- a/compiler/src/main.rs +++ b/compiler/src/main.rs @@ -3,18 +3,33 @@ use std::{fs, path::Path}; pub mod lexer; -pub mod parserprototype; -use parserprototype::Parser; +pub mod parser; +use parser::Parser; +pub mod codegen; +mod registers; +mod semantic_analyser; -use crate::parserprototype::ParseResult; +use crate::{codegen::CodeGenerator, parser::ParseResult, semantic_analyser::Analyser}; fn main() { - println!("Hello, world!"); + // read from input file: syntax "c_compiler [output.dsa]" + let args: Vec = std::env::args().collect(); + if args.len() < 2 { + eprintln!("Usage: c_compiler [output.dsa]"); + return; + } - let path = Path::new("../resources/dsc/example.dsc"); - let contents = fs::read_to_string(path).expect("Failed to read file"); + let input_file = &args[1]; + let output_file = if args.len() > 2 { + &args[2] + } else { + "output.dsa" + }; - let lexer = lexer::Lexer::new(&contents); + // read input + let input = std::fs::read_to_string(input_file).expect("Failed to read input file"); + + let lexer = lexer::Lexer::new(&input); let tokens = lexer.collect::>(); println!("{tokens:?}"); @@ -29,5 +44,22 @@ fn main() { panic!("Parser denied parsing") } }; - println!("{ast:?}"); + println!("{ast:#?}"); + + let analyser = Analyser::new(); + analyser.analyse(ast.clone()).unwrap(); + + // Code Gen + let mut generator = CodeGenerator::new(ast); + let result = match generator.generate() { + Ok(code) => code, + Err(e) => { + eprintln!("Parsing error: {:?}", e); + return; + } + }; + + println!("{result}"); + std::fs::write(output_file, &result).expect("Failed to write output"); + println!("Result written to {}", output_file); } diff --git a/compiler/src/parser.rs b/compiler/src/parser.rs index 7784e09..755df29 100644 --- a/compiler/src/parser.rs +++ b/compiler/src/parser.rs @@ -1,270 +1,728 @@ -use crate::expect_tt; use crate::lexer::Token; +use crate::{expect_tt, expect_value}; +use core::fmt; +use std::ops::{ControlFlow, FromResidual, Try}; + +#[derive(Debug, Clone)] +pub enum ParseResult { + Accept(T), + Deny, + Reject(E), +} + +#[derive(Debug, Clone)] +pub enum CompilerError { + UnexpectedToken(Token), + UnexpectedEndOfInput, + UnexpectedCharacter(char), + Undefined(Name), + InvalidSyntax(String), + Generic(String), +} pub struct Parser { - ast: Node, - idx: usize, tokens: Vec, + idx: usize, } impl Parser { pub fn new(tokens: Vec) -> Self { - Self { - ast: Node::Block { - children: Vec::new(), - }, - idx: 0, - tokens, - } + Self { tokens, idx: 0 } } - pub fn parse(&mut self) -> Result { - self.parse_block() - } + pub fn parse(&mut self) -> ParseResult { + let mut declarations = Vec::new(); - fn parse_block(&mut self) -> Result { - let mut statements = Vec::new(); - - while self.peek_next().is_ok() { - statements.push(self.parse_statement()?); + while let ParseResult::Accept(_) = self.peek_next() { + declarations.push(self.parse_declaration()?); } - Ok(Node::Block { - children: statements, - }) + ParseResult::Accept(Program { declarations }) } - fn parse_statement(&mut self) -> Result { - // first token in a statement is always an identifier - let left = if let Ok(typed_var) = self.parse_typed_var() { - Box::new(typed_var) - } else { - let tok = expect_tt!(self.next()?, Identifier)?; - Box::new(Node::Terminal { value: tok }) - }; - - let _ = expect_tt!(self.next()?, Assign)?; - - let right = Box::new(self.parse_expression()?); - - Ok(Node::Statement { left, right }) - } - - fn parse_typed_var(&mut self) -> Result { - let name = expect_tt!(self.next()?, Identifier)?; - let _ = expect_tt!(self.next()?, Colon)?; - let type_ = expect_tt!(self.next()?, Identifier)?; - - Ok(Node::TypedVar { name, type_ }) - } - - fn parse_expression(&mut self) -> Result { - if expect_tt!(self.peek_next()?, Pipe).is_ok() { + fn parse_declaration(&mut self) -> ParseResult { + if expect_tt!(self.peek_next()?, Fn).accepted() { return self.parse_func(); } - if expect_tt!(self.peek_next()?, If).is_ok() { - return self.parse_if(); + if expect_tt!(self.peek_next()?, Include).accepted() { + // expect include keyword + let _ = self.next(); + + // expect namespace identifier + let name = expect_value!(self.next()?, Identifier)?; + + // expect colon + let _ = expect_tt!(self.next()?, Colon)?; + + // expect string literal (module path) + let path = expect_value!(self.next()?, String)?; + + // expect semicolon + let _ = expect_tt!(self.next()?, Semicolon)?; + + return ParseResult::Accept(Declaration::Dependency(Dependency { + name, + path, + })); } - if expect_tt!(self.peek_next()?, Loop).is_ok() { - return self.parse_loop(); - } - - if expect_tt!(self.peek_next()?, Identifier, String, Number).is_ok() { - let left = Node::Terminal { - value: self.next()?, + if expect_tt!(self.peek_next()?, Const, Static).accepted() { + let is_const = match self.next()? { + Token::Const => true, + Token::Static => false, + _ => { + return ParseResult::Reject(CompilerError::Generic(String::from( + "This can't happen!", + ))); + } }; - if expect_tt!( - self.next()?, - Plus, - Minus, - Star, - Slash, - EqualEqual, - BangEqual, - Less, - LessEqual, - Greater, - GreaterEqual - ) - .is_err() - { - return Ok(left); - } + let var = self.parse_var_decl()?; - let operator = self.next()?; - let right = Box::new(self.parse_expression()?); + let _ = expect_tt!(self.next()?, Assign)?; - return Ok(Node::BinaryOp { - left: Box::new(left), - op: operator, - right, + let value = self.next()?; + let init = match value { + Token::String(x) => Some(ConstExpr::String(x)), + Token::Integer(x) => Some(ConstExpr::Number(x as i32)), + _ => return ParseResult::Reject(CompilerError::UnexpectedToken(value)), + }; + + let _ = expect_tt!(self.next()?, Semicolon)?; + + return ParseResult::Accept(Declaration::Variable { + var, + init, + is_const, }); } - Err(CompileError::Generic) + ParseResult::Reject(CompilerError::UnexpectedEndOfInput) } - fn parse_func(&mut self) -> Result { - // left arg delimiter - let _ = expect_tt!(self.next()?, Pipe)?; + fn parse_func(&mut self) -> ParseResult { + // expect function keyword + let _ = expect_tt!(self.next()?, Fn); + // expect function name + let name = expect_value!(self.next()?, Identifier)?; - // parse args - let mut args = Vec::new(); + // expect left paren + let _ = expect_tt!(self.next()?, LeftParen)?; - while expect_tt!(self.peek_next()?, Identifier).is_ok() { - // add a typed var - let arg = self.parse_typed_var()?; - args.push(arg); + let mut params = Vec::new(); + while expect_tt!(self.peek_next()?, Identifier).accepted() { + let arg = self.parse_var_decl()?; + params.push(arg); + + if expect_tt!(self.peek_next()?, Comma).accepted() { + self.next()?; + } else { + break; + } } - // right arg delimiter - let _ = expect_tt!(self.next()?, Pipe)?; + // expect right paren + let _ = expect_tt!(self.next()?, RightParen)?; - // ensure we have an open brace - let _ = expect_tt!(self.next()?, LeftBrace)?; + // see if we can parse the return type! + let mut return_type = TypeId::Void; + if expect_tt!(self.peek_next()?, RightArrow).accepted() { + let _ = self.next(); + return_type = self.parse_type()?; + } - // parse the body - let body = Box::new(self.parse_block()?); - - // ensure we have a close brace - let _ = expect_tt!(self.next()?, RightBrace)?; - - Ok(Node::FunctionDef { params: args, body }) + // expect vald block + if expect_tt!(self.peek_next()?, LeftBrace).accepted() { + ParseResult::Accept(Declaration::Function { + name, + params, + return_type, + body: self.parse_block()?, + }) + } else { + ParseResult::Reject(CompilerError::UnexpectedToken(self.peek_next()?)) + } } - fn parse_loop(&mut self) -> Result { - let _ = expect_tt!(self.next()?, Loop)?; - - // ensure we have an open brace + fn parse_block(&mut self) -> ParseResult { + // expect left brace let _ = expect_tt!(self.next()?, LeftBrace)?; - // parse the body - let body = Box::new(self.parse_block()?); + let mut block = Vec::new(); + while !expect_tt!(self.peek_next()?, RightBrace).accepted() { + block.push(self.parse_statement()?); + } - // ensure we have a close brace + // expect right brace let _ = expect_tt!(self.next()?, RightBrace)?; - Ok(Node::Loop { body }) + ParseResult::Accept(block) } - fn parse_if(&mut self) -> Result { - let _ = expect_tt!(self.next()?, If)?; + fn parse_statement(&mut self) -> ParseResult { + // handle if statements + if expect_tt!(self.peek_next()?, If).accepted() { + self.next()?; - // parse condition (expr) - let condition = Box::new(self.parse_expression()?); + let condition = self.parse_expression()?; - // ensure we have an open brace - let _ = expect_tt!(self.next()?, LeftBrace)?; + let then_stmt = self.parse_block()?; - // parse the "then" branch (expr/statement) - let then_branch = Box::new(self.parse_expression()?); + if !expect_tt!(self.peek_next()?, Else).accepted() { + return ParseResult::Accept(Statement::If { + condition, + then_stmt, + else_stmt: vec![], + }); + } - // ensure we have a close brace - let _ = expect_tt!(self.next()?, RightBrace)?; + let _ = expect_tt!(self.next()?, Else)?; - // if there is an else branch, we include it in the statement - let else_branch = self.parse_else()?.map(Box::new); + let else_stmt = self.parse_block()?; - Ok(Node::If { - condition, - then_branch, - else_branch, + return ParseResult::Accept(Statement::If { + condition, + then_stmt, + else_stmt, + }); + } + + // handle while loops + if expect_tt!(self.peek_next()?, While).accepted() { + self.next()?; + + // expect valid expression + let expr = self.parse_expression()?; + + // expect valid block after + let block = self.parse_block()?; + + // return result + return ParseResult::Accept(Statement::While { + condition: expr, + body: block, + }); + } + + // handle indefinite loops + if expect_tt!(self.peek_next()?, Loop).accepted() { + self.next()?; + + // parse the inner block + return ParseResult::Accept(Statement::Loop(self.parse_block()?)); + } + + if expect_tt!(self.peek_next()?, Return).accepted() { + self.next()?; + + // handle case where nothing is returned + if expect_tt!(self.peek_next()?, Semicolon).accepted() { + return ParseResult::Accept(Statement::Return(None)); + } + + let expr = self.parse_expression()?; + expect_tt!(self.next()?, Semicolon)?; + + return ParseResult::Accept(Statement::Return(Some(expr))); + } + + if expect_tt!(self.peek_next()?, Break).accepted() { + self.next()?; + + // expect semicolon + expect_tt!(self.next()?, Semicolon)?; + + // return result + return ParseResult::Accept(Statement::Break); + } + + if expect_tt!(self.peek_next()?, Continue).accepted() { + self.next()?; + + // expect semicolon + expect_tt!(self.next()?, Semicolon)?; + + // return result + return ParseResult::Accept(Statement::Continue); + } + + // handle let statements (declarations) + if expect_tt!(self.peek_next()?, Let).accepted() { + self.next(); + + // expect variable name and type. + let name = self.parse_var_decl()?; + + // handle uninitialised variable case + if expect_tt!(self.peek_next()?, Semicolon).accepted() { + self.next(); + return ParseResult::Accept(Statement::Declaration { + var: name, + value: None, + }); + } + + // handle initialised case + // expect equals + let _ = expect_tt!(self.next()?, Assign)?; + + // expect a valid expression + let expr = self.parse_expression()?; + + let _ = expect_tt!(self.next()?, Semicolon); + + // return statement + return ParseResult::Accept(Statement::Declaration { + var: name, + value: Some(expr), + }); + } + + // handle assignment without "let" + let name = expect_value!(self.peek_next()?, Identifier); + if name.accepted() { + let varname = name?; + + println!("expr acc"); + + if expect_tt!(self.peek(1)?, LeftParen).accepted() { + println!("func call acc"); + let expr = self.parse_expression()?; // a function call expr + let _ = expect_tt!(self.next()?, Semicolon)?; + return ParseResult::Accept(Statement::Expression { expr }); + } + + self.next()?; + let _ = expect_tt!(self.next()?, Assign)?; + + let value = self.parse_expression()?; + + let _ = expect_tt!(self.next()?, Semicolon); + + return ParseResult::Accept(Statement::Assign { varname, value }); + } + + ParseResult::Reject(CompilerError::UnexpectedToken(self.peek_next()?)) + } + + fn parse_expression(&mut self) -> ParseResult { + self.parse_comparison() + } + + fn parse_comparison(&mut self) -> ParseResult { + let mut expr = self.parse_additive()?; + + while let Some(op) = match self.peek_next()? { + Token::EqualEqual => Some(BinaryOperator::Ne), + Token::BangEqual => Some(BinaryOperator::Ne), + Token::Less => Some(BinaryOperator::Lt), + Token::Greater => Some(BinaryOperator::Gt), + Token::LessEqual => Some(BinaryOperator::Le), + Token::GreaterEqual => Some(BinaryOperator::Ge), + _ => None, + } { + self.next()?; + let right = Box::new(self.parse_additive()?); + expr = Expression::Binary { + op, + left: Box::new(expr), + right, + } + } + + ParseResult::Accept(expr) + } + + fn parse_additive(&mut self) -> ParseResult { + let left = self.parse_multiplicative()?; + + let op = match self.peek_next()? { + Token::Plus => BinaryOperator::Add, + Token::Minus => BinaryOperator::Sub, + _ => return ParseResult::Accept(left), + }; + + self.next()?; + ParseResult::Accept(Expression::Binary { + op, + left: Box::new(left), + right: Box::new(self.parse_additive()?), }) } - fn parse_else(&mut self) -> Result, CompileError> { - // if there is no else branch, return None. - if expect_tt!(self.peek_next()?, Else).is_err() { - return Ok(None); - } - let _ = self.next()?; + fn parse_multiplicative(&mut self) -> ParseResult { + let left = self.parse_unary()?; - if expect_tt!(self.peek_next()?, If).is_ok() { - Ok(Some(self.parse_if()?)) + let op = match self.peek_next()? { + Token::Star => BinaryOperator::Mul, + Token::Slash => BinaryOperator::Div, + _ => return ParseResult::Accept(left), + }; + + self.next()?; + ParseResult::Accept(Expression::Binary { + op, + left: Box::new(left), + right: Box::new(self.parse_multiplicative()?), + }) + } + + fn parse_unary(&mut self) -> ParseResult { + let op = match self.peek_next()? { + Token::Plus => UnaryOperator::Plus, + Token::Minus => UnaryOperator::Minus, + Token::Star => UnaryOperator::Dereference, + Token::Amphersand => UnaryOperator::Reference, + _ => return ParseResult::Accept(self.parse_primary()?), + }; + + self.next()?; + let operand = Box::new(self.parse_unary()?); + ParseResult::Accept(Expression::Unary { op, operand }) + } + + fn parse_primary(&mut self) -> ParseResult { + match self.peek_next()? { + Token::Integer(value) => { + self.next()?; + ParseResult::Accept(Expression::Number(value as isize)) + } + Token::String(value) => { + self.next()?; + ParseResult::Accept(Expression::StringLiteral(value)) + } + Token::Identifier(_) => { + let name = self.parse_identifier()?; + + if matches!(self.peek_next()?, Token::LeftParen) { + // Function call + self.next()?; + let mut args = Vec::new(); + + if !matches!(self.peek_next()?, Token::RightParen) { + args.push(self.parse_expression()?); + + while matches!(self.peek_next()?, Token::Comma) { + self.next()?; + args.push(self.parse_expression()?); + } + } + + let _ = expect_tt!(self.next()?, RightParen)?; + + ParseResult::Accept(Expression::Call { name, args }) + } else { + ParseResult::Accept(Expression::Variable { + name, + expr_type: None, + }) + } + } + Token::LeftParen => { + self.next()?; + let expr = self.parse_expression()?; + let _ = expect_tt!(self.next()?, RightParen)?; + ParseResult::Accept(expr) + } + _ => ParseResult::Reject(CompilerError::UnexpectedToken(self.peek_next()?)), + } + } + + fn parse_var_decl(&mut self) -> ParseResult { + let name = expect_value!(self.next()?, Identifier)?; + + let _ = expect_tt!(self.next()?, Colon)?; + + let type_id = self.parse_type()?; + + ParseResult::Accept(Variable { name, type_id }) + } + + fn parse_type(&mut self) -> ParseResult { + // get the type name incl namespace + let typename = self.parse_identifier()?; + + match typename.name.as_str() { + "u32" => ParseResult::Accept(TypeId::U32), + "u16" => ParseResult::Accept(TypeId::U16), + "u8" => ParseResult::Accept(TypeId::U8), + "i32" => ParseResult::Accept(TypeId::I32), + "i16" => ParseResult::Accept(TypeId::I16), + "i8" => ParseResult::Accept(TypeId::I8), + "void" => ParseResult::Accept(TypeId::Void), + "char" => ParseResult::Accept(TypeId::Char), + "str" => ParseResult::Accept(TypeId::Ptr(Box::new(TypeId::Char))), + _ => todo!("Implement parsing for other types!!"), + } + } + + fn parse_identifier(&mut self) -> ParseResult { + let primary = expect_value!(self.next()?, Identifier)?; + + if expect_tt!(self.peek_next()?, Colon).accepted() { + let _ = expect_tt!(self.next()?, Colon)?; + let _ = expect_tt!(self.next()?, Colon)?; + + let secondary = expect_value!(self.next()?, Identifier)?; + + ParseResult::Accept(Name { + namespace: Some(primary), + name: secondary, + }) } else { - Ok(Some(self.parse_expression()?)) + ParseResult::Accept(Name { + namespace: None, + name: primary, + }) } } - fn next(&mut self) -> Result { + fn next(&mut self) -> ParseResult { if self.idx >= self.tokens.len() { - return Err(CompileError::UnexpectedEOF); + ParseResult::Reject(CompilerError::UnexpectedEndOfInput) + } else { + let token = self.tokens[self.idx].clone(); + self.idx += 1; + ParseResult::Accept(token) } - - let token = self.tokens[self.idx].clone(); - self.idx += 1; - - Ok(token) } - fn peek_next(&mut self) -> Result { + fn peek_next(&self) -> ParseResult { if self.idx >= self.tokens.len() { - return Err(CompileError::UnexpectedEOF); + ParseResult::Reject(CompilerError::UnexpectedEndOfInput) + } else { + ParseResult::Accept(self.tokens[self.idx].clone()) } + } - Ok(self.tokens[self.idx].clone()) + fn peek(&self, offset: usize) -> ParseResult { + if self.idx + offset >= self.tokens.len() { + ParseResult::Reject(CompilerError::UnexpectedEndOfInput) + } else { + ParseResult::Accept(self.tokens[self.idx + offset].clone()) + } } } -#[derive(Debug, Clone, PartialEq)] -pub enum Node { - /// A scope, which is a list of child nodes that are evaluated in order. - Block { children: Vec }, - /// A leaf node with a value. - Terminal { value: Token }, +#[derive(Debug, Clone)] +pub struct Program { + pub declarations: Vec, +} - /// A unary operator with a right operand. - UnaryOp { op: Token, right: Box }, - - /// A binary operator with a left and right operand. - BinaryOp { - left: Box, - op: Token, - right: Box, +#[derive(Debug, Clone)] +pub enum Declaration { + Function { + name: String, + return_type: TypeId, + params: Vec, + body: Block, }, + Variable { + var: Variable, + init: Option, + is_const: bool, + }, + Dependency(Dependency), +} - /// A statement, consisting of a value to assign to, and an expression. - Statement { left: Box, right: Box }, +#[derive(Debug, Clone)] +pub struct Dependency { + pub name: String, + pub path: String, +} - /// An if expression, which evaluates to either the then branch or the else branch. +#[derive(Debug, Clone)] +pub struct Name { + pub name: String, + pub namespace: Option, +} + +#[derive(Debug, Clone)] +pub enum TypeId { + U8, + U16, + U32, + I8, + I16, + I32, + Char, + Void, + Ptr(Box), + Ref(Box), + Array(Box, usize), + Struct { name: Name, fields: Vec }, +} + +pub type Block = Vec; + +#[derive(Debug, Clone)] +pub struct Variable { + pub name: String, + pub type_id: TypeId, +} + +#[derive(Debug, Clone)] +pub enum Statement { + Block(Block), + Declaration { + var: Variable, + value: Option, + }, + Assign { + varname: String, + value: Expression, + }, + Expression { + expr: Expression, + }, If { - condition: Box, - then_branch: Box, - else_branch: Option>, + condition: Expression, + then_stmt: Block, + else_stmt: Block, }, - - /// A loop expression, which evaluates to the last value of the loop. - /// a loop can be exited with the break keyword. - Loop { body: Box }, - - /// A function definition. ``` | param: type .. | -> ret_type { body }``` - FunctionDef { params: Vec, body: Box }, - - /// A typed variable definition: ```val: Type``` - TypedVar { name: Token, type_: Token }, - - /// A type definition, which is a list of fields. ```type MyType { field: Type }``` - TypeDef { name: Token, fields: Vec }, + While { + condition: Expression, + body: Vec, + }, + Loop(Block), + Break, + Continue, + Return(Option), } -#[derive(Debug)] -pub enum CompileError { - Generic, - ExpectedToken { expected: String, found: Token }, - UnexpectedEOF, +#[derive(Debug, Clone)] +pub enum ConstExpr { + Number(i32), + String(String), +} + +impl fmt::Display for ConstExpr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + ConstExpr::Number(n) => write!(f, "{}", n), + ConstExpr::String(s) => write!(f, "\"{}\"", s), + } + } +} + +#[derive(Debug, Clone)] +pub enum Expression { + Empty, + Binary { + op: BinaryOperator, + left: Box, + right: Box, + }, + Unary { + op: UnaryOperator, + operand: Box, + }, + Variable { + name: Name, + expr_type: Option, + }, + Call { + name: Name, + args: Vec, + }, + Number(isize), + StringLiteral(String), + CharLiteral(char), +} + +#[derive(Debug, Clone, PartialEq)] +pub enum BinaryOperator { + Add, + Sub, + Mul, + Div, + Eq, + Ne, + Lt, + Gt, + Le, + Ge, +} + +impl fmt::Display for BinaryOperator { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + BinaryOperator::Add => write!(f, "+"), + BinaryOperator::Sub => write!(f, "-"), + BinaryOperator::Mul => write!(f, "*"), + BinaryOperator::Div => write!(f, "/"), + BinaryOperator::Eq => write!(f, "=="), + BinaryOperator::Ne => write!(f, "!="), + BinaryOperator::Lt => write!(f, "<"), + BinaryOperator::Gt => write!(f, ">"), + BinaryOperator::Le => write!(f, "<="), + BinaryOperator::Ge => write!(f, ">="), + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub enum UnaryOperator { + Plus, + Minus, + Reference, + Dereference, +} + +impl fmt::Display for UnaryOperator { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + UnaryOperator::Plus => write!(f, "+"), + UnaryOperator::Minus => write!(f, "-"), + UnaryOperator::Dereference => write!(f, "*"), + UnaryOperator::Reference => write!(f, "&"), + } + } +} + +impl ParseResult { + pub fn accepted(&self) -> bool { + matches!(self, ParseResult::Accept(_)) + } +} + +pub enum ParseResultResidual { + Deny, + Reject(T), +} + +impl Try for ParseResult { + type Output = T; + type Residual = ParseResultResidual; + + fn from_output(output: T) -> Self { + ParseResult::Accept(output) + } + + fn branch(self) -> ControlFlow { + match self { + ParseResult::Accept(v) => ControlFlow::Continue(v), + ParseResult::Deny => ControlFlow::Break(ParseResultResidual::Deny), + ParseResult::Reject(e) => ControlFlow::Break(ParseResultResidual::Reject(e)), + } + } +} + +impl FromResidual for ParseResult { + fn from_residual(residual: ParseResultResidual) -> Self { + match residual { + ParseResultResidual::Deny => ParseResult::Deny, + ParseResultResidual::Reject(e) => ParseResult::Reject(e), + } + } } #[macro_export] macro_rules! expect_tt { ($token:expr, $($variant:ident),+) => {{ - let tt = $token.tt().to_string(); - - println!("CASE"); - println!("TOK {:?}", $token); - println!("TT {}", tt); + let token = $token.clone(); + let tt = token.tt().to_string(); let mut vs = String::new(); $( @@ -275,15 +733,11 @@ macro_rules! expect_tt { match tt.as_str() { $( - stringify!($variant) => Ok($token.clone()), + stringify!($variant) => ParseResult::Accept(token), )+ _ => { - println!("EXPECTED!! {} [{}]", tt, vs); - let expected = format!("[{}]", vec![$(stringify!($variant)),+].join(" | ")); - Err(CompileError::ExpectedToken { - expected, - found: $token.clone(), - }) + // let expected = format!("[{}]", vec![$(stringify!($variant)),+].join(" | ")); + ParseResult::Reject(CompilerError::UnexpectedToken(token)) } } }}; @@ -291,16 +745,11 @@ macro_rules! expect_tt { #[macro_export] macro_rules! expect_value { - ($token:expr, $variant:expr) => {{ - match $token { - $variant(x) => Ok(x), - _ => { - let expected = format!("[{}]") - Err(CompileError::ExpectedToken { - expected, - found: $token.clone(), - }) - } + ($expr:expr, $variant:ident) => {{ + let tok = $expr; + match tok.clone() { + Token::$variant(value) => ParseResult::Accept(value), + _ => ParseResult::Reject(CompilerError::UnexpectedToken(tok)), } }}; } diff --git a/compiler/src/parserprototype.rs b/compiler/src/parserprototype.rs deleted file mode 100644 index 72c3441..0000000 --- a/compiler/src/parserprototype.rs +++ /dev/null @@ -1,435 +0,0 @@ -use crate::lexer::Token; -use crate::{expect_tt, expect_value}; -use core::fmt; -use std::ops::{ControlFlow, FromResidual, Try}; - -#[derive(Debug, Clone)] -pub enum ParseResult { - Accept(T), - Deny, - Reject(E), -} - -#[derive(Debug, Clone)] -pub enum CompilerError { - UnexpectedToken(Token), - UnexpectedEndOfInput, - UnexpectedCharacter(char), - InvalidSyntax(String), - Generic(String), -} - -pub struct Parser { - tokens: Vec, - idx: usize, -} - -impl Parser { - pub fn new(tokens: Vec) -> Self { - Self { tokens, idx: 0 } - } - - pub fn parse(&mut self) -> ParseResult { - let mut declarations = Vec::new(); - - while let ParseResult::Accept(_) = self.peek_next() { - declarations.push(self.parse_declaration()?); - } - - ParseResult::Accept(Program { - imports: vec![], - declarations, - }) - } - - fn parse_declaration(&mut self) -> ParseResult { - if expect_tt!(self.peek_next()?, Fn).accepted() { - let x = self.parse_func(); - println!("function {:?}", x); - return x; - } - - println!("{:?}", self.peek_next()?); - - ParseResult::Reject(CompilerError::UnexpectedEndOfInput) - } - - fn parse_func(&mut self) -> ParseResult { - // expect function keyword - // - println!("pre name! {:?}", self.peek_next()?); - - let _ = expect_tt!(self.next()?, Fn); - - println!("this is the name! {:?}", self.peek_next()?); - - // expect function name - let name = match self.next()? { - Token::Identifier(name) => name, - id => return ParseResult::Reject(CompilerError::UnexpectedToken(id)), - }; - - // expect left paren - let _ = expect_tt!(self.next()?, LParen); - - let mut params = Vec::new(); - while expect_tt!(self.peek_next()?, Identifier).accepted() { - let arg = self.parse_var_decl()?; - params.push(arg); - } - - // expect right paren - let _ = expect_tt!(self.next()?, RParen); - - // see if we can parse the return type! - let mut return_type = TypeId::Void; - - if expect_tt!(self.peek_next()?, RightArrow).accepted() { - let _ = self.next(); - return_type = self.parse_type()?; - } - - // expect left brace - let _ = expect_tt!(self.next()?, LBrace); - - let mut body = Vec::new(); - - // expect right brace - let _ = expect_tt!(self.next()?, RBrace); - - ParseResult::Accept(Declaration::Function { - name, - params, - return_type, - body, - }) - } - - fn parse_var_decl(&mut self) -> ParseResult { - let name = match self.next()? { - Token::Identifier(name) => name, - id => return ParseResult::Reject(CompilerError::UnexpectedToken(id)), - }; - - let _ = expect_tt!(self.next()?, Colon); - - let type_ = self.parse_type()?; - - ParseResult::Accept(Variable { - name, - param_type: Some(type_), - }) - } - - fn parse_type(&mut self) -> ParseResult { - // get the type name incl namespace - let typename = self.parse_identifier()?; - - match typename.name.as_str() { - "u32" => ParseResult::Accept(TypeId::U32), - "u16" => ParseResult::Accept(TypeId::U16), - "u8" => ParseResult::Accept(TypeId::U8), - "i32" => ParseResult::Accept(TypeId::I32), - "i16" => ParseResult::Accept(TypeId::I16), - "i8" => ParseResult::Accept(TypeId::I8), - "void" => ParseResult::Accept(TypeId::Void), - "char" => ParseResult::Accept(TypeId::Char), - _ => todo!("Implement parsing for other types!!"), - } - } - - fn parse_identifier(&mut self) -> ParseResult { - let primary = match self.next()? { - Token::Identifier(namespace) => namespace, - id => return ParseResult::Reject(CompilerError::UnexpectedToken(id)), - }; - - if expect_tt!(self.peek_next()?, Colon).accepted() { - let _ = expect_tt!(self.next()?, Colon); - let _ = expect_tt!(self.next()?, Colon); - - let secondary = match self.next()? { - Token::Identifier(name) => name, - id => return ParseResult::Reject(CompilerError::UnexpectedToken(id)), - }; - - ParseResult::Accept(Name { - namespace: Some(primary), - name: secondary, - }) - } else { - ParseResult::Accept(Name { - namespace: None, - name: primary, - }) - } - } - - fn next(&mut self) -> ParseResult { - if self.idx >= self.tokens.len() { - ParseResult::Reject(CompilerError::UnexpectedEndOfInput) - } else { - let token = self.tokens[self.idx].clone(); - println!("NEXT {:?}", token); - self.idx += 1; - ParseResult::Accept(token) - } - } - - fn peek_next(&self) -> ParseResult { - if self.idx >= self.tokens.len() { - ParseResult::Reject(CompilerError::UnexpectedEndOfInput) - } else { - ParseResult::Accept(self.tokens[self.idx].clone()) - } - } -} - -#[derive(Debug, Clone)] -pub struct Program { - pub imports: Vec, - pub declarations: Vec, -} - -#[derive(Debug, Clone)] -pub enum Declaration { - Function { - name: String, - return_type: TypeId, - params: Vec, - body: Block, - }, - Variable { - name: String, - init: Option, - }, -} - -#[derive(Debug, Clone)] -pub struct Dependency { - pub name: String, - pub path: String, -} - -#[derive(Debug, Clone)] -pub struct Variable { - pub name: String, - pub param_type: Option, -} - -#[derive(Debug, Clone)] -pub struct Name { - pub name: String, - pub namespace: Option, -} - -#[derive(Debug, Clone)] -pub enum TypeId { - U8, - U16, - U32, - I8, - I16, - I32, - Char, - Void, - Ptr(Box), - Ref(Box), - Array(Box, usize), - Struct { - name: Name, - fields: Vec<(String, TypeId)>, - }, -} - -pub type Block = Vec; - -#[derive(Debug, Clone)] -pub enum Statement { - Block(Block), - Assign { - var: Variable, - value: Option>, - }, - Expression { - expr: Expression, - }, - If { - condition: Expression, - then_stmt: Block, - else_stmt: Block, - }, - While { - condition: Expression, - body: Vec, - }, - Loop(Block), - Break, - Continue, - Return(Option), -} - -#[derive(Debug, Clone)] -pub enum ConstExpr { - Number(i32), - String(String), -} - -impl fmt::Display for ConstExpr { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - ConstExpr::Number(n) => write!(f, "{}", n), - ConstExpr::String(s) => write!(f, "\"{}\"", s), - } - } -} - -#[derive(Debug, Clone)] -pub enum Expression { - Empty, - Binary { - op: BinaryOperator, - left: Box, - right: Box, - }, - Unary { - op: UnaryOperator, - operand: Box, - }, - Variable { - name: Name, - expr_type: Option, - }, - Number { - value: i32, - }, - Call { - name: Name, - args: Vec, - }, -} - -#[derive(Debug, Clone, PartialEq)] -pub enum BinaryOperator { - Add, - Sub, - Mul, - Div, - Eq, - Ne, - Lt, - Gt, - Le, - Ge, -} - -impl fmt::Display for BinaryOperator { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - BinaryOperator::Add => write!(f, "+"), - BinaryOperator::Sub => write!(f, "-"), - BinaryOperator::Mul => write!(f, "*"), - BinaryOperator::Div => write!(f, "/"), - BinaryOperator::Eq => write!(f, "=="), - BinaryOperator::Ne => write!(f, "!="), - BinaryOperator::Lt => write!(f, "<"), - BinaryOperator::Gt => write!(f, ">"), - BinaryOperator::Le => write!(f, "<="), - BinaryOperator::Ge => write!(f, ">="), - } - } -} - -#[derive(Debug, Clone, PartialEq)] -pub enum UnaryOperator { - Plus, - Minus, -} - -impl fmt::Display for UnaryOperator { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - UnaryOperator::Plus => write!(f, "+"), - UnaryOperator::Minus => write!(f, "-"), - } - } -} - -impl ParseResult { - pub fn accepted(&self) -> bool { - matches!(self, ParseResult::Accept(_)) - } -} - -pub enum ParseResultResidual { - Deny, - Reject(T), -} - -impl Try for ParseResult { - type Output = T; - type Residual = ParseResultResidual; - - fn from_output(output: T) -> Self { - ParseResult::Accept(output) - } - - fn branch(self) -> ControlFlow { - match self { - ParseResult::Accept(v) => ControlFlow::Continue(v), - ParseResult::Deny => ControlFlow::Break(ParseResultResidual::Deny), - ParseResult::Reject(e) => ControlFlow::Break(ParseResultResidual::Reject(e)), - } - } -} - -impl FromResidual for ParseResult { - fn from_residual(residual: ParseResultResidual) -> Self { - match residual { - ParseResultResidual::Deny => ParseResult::Deny, - ParseResultResidual::Reject(e) => ParseResult::Reject(e), - } - } -} - -#[macro_export] -macro_rules! expect_tt { - ($token:expr, $($variant:ident),+) => {{ - let tt = $token.tt().to_string(); - - // for some reason the code trips tf out without this line - - println!("token {:?}", $token); - - let mut vs = String::new(); - $( - let s = stringify!($variant); - vs.push_str(s); - vs.push_str("|"); - )+ - - match tt.as_str() { - $( - stringify!($variant) => ParseResult::Accept($token.clone()), - )+ - _ => { - println!("EXPECTED!! {} [{}]", tt, vs); - // let expected = format!("[{}]", vec![$(stringify!($variant)),+].join(" | ")); - ParseResult::Reject(CompilerError::UnexpectedToken($token.clone())) - } - } - }}; -} - -#[macro_export] -macro_rules! expect_value { - ($token:expr, $variant:expr) => {{ - match $token { - $variant(x) => ParseResult::Accept(x), - _ => { - let expected = format!("[{}]") - ParseResult::Reject(CompilerError::UnexpectedToken($token.clone())) - } - } - }}; -} diff --git a/compiler/src/registers.rs b/compiler/src/registers.rs new file mode 100644 index 0000000..910f065 --- /dev/null +++ b/compiler/src/registers.rs @@ -0,0 +1,375 @@ +use std::collections::HashMap; + +use crate::parser::CompilerError; + +/// Register allocator for DSA assembly generation +/// Manages general-purpose registers (rg0-rgf) and handles stack spilling +pub struct RegisterAllocator { + /// Available general-purpose registers + available_registers: Vec, + + /// Maps variable names to their current location (register or stack offset) + variable_locations: HashMap, + + /// Maps registers to the variables they currently hold + register_contents: HashMap, + + /// Current stack offset for local variables (relative to bpr) + /// Starts at -4 (going downward from base pointer) + stack_offset: i32, + + /// Track which registers are currently in use + in_use: HashMap, +} + +#[derive(Debug, Clone)] +pub enum Location { + Register(String), + Stack(i32), // offset from bpr +} + +impl RegisterAllocator { + pub fn new() -> Self { + // Initialize with available GP registers (rg0-rgf = 16 registers) + let registers = vec![ + "rg0", "rg1", "rg2", "rg3", "rg4", "rg5", "rg6", "rg7", "rg8", "rg9", "rga", + "rgb", "rgc", "rgd", "rge", "rgf", + ] + .into_iter() + .map(String::from) + .collect(); + + RegisterAllocator { + available_registers: registers, + variable_locations: HashMap::new(), + register_contents: HashMap::new(), + stack_offset: -4, // Start at -4 (first local below saved bpr) + in_use: HashMap::new(), + } + } + + /// Allocate a temporary register for expression evaluation + /// Returns the register name and optionally assembly code to save it + pub fn alloc_temp(&mut self) -> Result<(String, Vec), CompilerError> { + let mut code = Vec::new(); + + // Try to find an unused register + for reg in &self.available_registers { + if !self.in_use.get(reg).unwrap_or(&false) { + self.in_use.insert(reg.clone(), true); + return Ok((reg.clone(), code)); + } + } + + // All registers in use - need to spill one + // Choose the first register with a variable we can spill + // Find a register to spill + let reg_to_spill = self + .available_registers + .iter() + .find(|reg| self.register_contents.contains_key(*reg)) + .cloned(); + + if let Some(reg) = reg_to_spill { + // Spill this variable to stack + let spill_code = self.spill_register(®)?; + code.extend(spill_code); + + self.in_use.insert(reg.clone(), true); + return Ok((reg, code)); + } + + Err(CompilerError::Generic( + "All registers are used up yet there are no variables to spill to the stack" + .to_string(), + )) + } + + /// Free a temporary register after use + /// NOTE: This will NOT free registers that contain variables! + /// Variables persist throughout their scope and must not be freed + pub fn free_temp(&mut self, reg: &str) { + // Check if this register contains a variable + if self.register_contents.contains_key(reg) { + // This register holds a variable - don't free it! + // Variables are only freed when they go out of scope via free_var() + return; + } + + // This is a true temporary - safe to free + self.in_use.insert(reg.to_string(), false); + } + + /// Allocate a register for a named variable + /// Returns the register and any necessary assembly code + pub fn alloc_var( + &mut self, + var_name: &str, + ) -> Result<(String, Vec), CompilerError> { + if let Some(location) = self.variable_locations.get(var_name).cloned() { + match location { + Location::Register(reg) => { + return Ok((reg.clone(), Vec::new())); + } + Location::Stack(offset) => { + // Variable was pushed, need to calculate actual position + let (reg, mut code) = self.alloc_temp()?; + + // Load from bpr + offset (offset is negative) + code.push(format!("\tsubi bpr {} {}", -(offset + 4), reg)); + code.push(format!("\tldw {}, {}", reg, reg)); + + // Update location to register + self.variable_locations + .insert(var_name.to_string(), Location::Register(reg.clone())); + self.register_contents + .insert(reg.clone(), var_name.to_string()); + + return Ok((reg, code)); + } + } + } + + // Variable doesn't have a location yet, allocate a new register + let (reg, code) = self.alloc_temp()?; + self.variable_locations + .insert(var_name.to_string(), Location::Register(reg.clone())); + self.register_contents + .insert(reg.clone(), var_name.to_string()); + + Ok((reg, code)) + } + + /// Get the current location of a variable + pub fn get_var_location(&self, var_name: &str) -> Option<&Location> { + self.variable_locations.get(var_name) + } + + /// Load a variable into a register (allocating if necessary) + /// Returns the register and assembly code to load it + pub fn load_var( + &mut self, + var_name: &str, + ) -> Result<(String, Vec), CompilerError> { + self.alloc_var(var_name) + } + + /// Store a value from a register into a variable + /// Updates tracking and returns any necessary assembly code + pub fn store_var(&mut self, var_name: &str, source_reg: &str) -> Vec { + let mut code = Vec::new(); + + // Check if variable already has a location + if let Some(location) = self.variable_locations.get(var_name) { + match location { + Location::Register(dest_reg) => { + if dest_reg != source_reg { + code.push(format!("\tmov {}, {}", source_reg, dest_reg)); + } + } + Location::Stack(offset) => { + code.push(format!("\tstw {}, bpr, {}", source_reg, offset)); + } + } + } else { + // Variable doesn't exist yet, we can just use the same reg. + + self.variable_locations.insert( + var_name.to_string(), + Location::Register(source_reg.to_string()), + ); + self.register_contents + .insert(source_reg.to_string(), var_name.to_string()); + self.in_use.insert(source_reg.to_string(), true); + + // this is not needed for now as if we're storing a var we already have a temp + // register allocated. + // if let Some(free_reg) = self.find_free_register() { + // if &free_reg != source_reg { + // code.push(format!("\tmov {}, {}", source_reg, free_reg)); + // } + // self.variable_locations + // .insert(var_name.to_string(), + // Location::Register(free_reg.clone())); + // self.register_contents + // .insert(free_reg.clone(), var_name.to_string()); + // self.in_use.insert(free_reg, true); + // } else { + // // No free registers - allocate on stack + // code.push(format!("\tstw {}, bpr, {}", source_reg, self.stack_offset)); + // self.variable_locations + // .insert(var_name.to_string(), Location::Stack(self.stack_offset)); + // self.stack_offset -= 4; // Move to next stack slot + // } + } + + code + } + + /// Spill a register to the stack + /// Returns assembly code to perform the spill + pub fn spill_register(&mut self, reg: &str) -> Result, CompilerError> { + let mut code = Vec::new(); + + if let Some(var_name) = self.register_contents.get(reg).cloned() { + // PUSH register to stack (spr decrements automatically) + code.push(format!("\tpush {}", reg)); + + // Track that we pushed one word + self.stack_offset -= 4; + + // Update variable location - it's now at current spr + // Note: We track offset from bpr for consistency + self.variable_locations + .insert(var_name.clone(), Location::Stack(self.stack_offset)); + + // Remove from register tracking + self.register_contents.remove(reg); + } + + Ok(code) + } + + /// Find a free register (not currently in use) + fn find_free_register(&self) -> Option { + for reg in &self.available_registers { + if !self.in_use.get(reg).unwrap_or(&false) { + return Some(reg.clone()); + } + } + None + } + + /// Spill all registers to stack (useful before function calls) + pub fn spill_all(&mut self) -> Vec { + let mut code = Vec::new(); + + let regs_to_spill: Vec = self.register_contents.keys().cloned().collect(); + + for reg in regs_to_spill { + if let Ok(spill_code) = self.spill_register(®) { + code.extend(spill_code); + } + } + + code + } + + /// Get the total stack offset + pub fn get_stack_offset(&self) -> i32 { + self.stack_offset + } + + /// Get the total stack space needed for local variables + pub fn get_stack_size(&self) -> i32 { + -self.stack_offset // Convert negative offset to positive size + } + + /// Reset allocator for a new function + pub fn reset(&mut self) { + self.variable_locations.clear(); + self.register_contents.clear(); + self.stack_offset = -4; + self.in_use.clear(); + } + + /// Mark a variable as dead (no longer needed) + /// Frees its register if it's in one + pub fn free_var(&mut self, var_name: &str) { + if let Some(Location::Register(reg)) = self.variable_locations.get(var_name) { + let reg = reg.clone(); + self.register_contents.remove(®); + self.in_use.insert(reg, false); + } + self.variable_locations.remove(var_name); + } + + /// Get list of registers that contain variables and are in use + /// These need to be saved before function calls + pub fn get_caller_saved_registers(&self) -> Vec { + self.register_contents + .iter() + .filter(|(reg, _)| *self.in_use.get(*reg).unwrap_or(&false)) + .map(|(reg, _)| reg.clone()) + .collect() + } + + /// Save caller-saved registers before a function call + /// Returns assembly code to save them + pub fn save_caller_saved(&mut self) -> Vec { + let mut code = Vec::new(); + + // For simplicity, save all currently used registers + // In a more sophisticated compiler, you'd only save registers that are live + for (reg, var_name) in self.register_contents.clone() { + if *self.in_use.get(®).unwrap_or(&false) { + code.push(format!("\tpush {}", reg)); + } + } + + code + } + + /// Restore caller-saved registers after a function call + /// Returns assembly code to restore them + pub fn restore_caller_saved(&mut self, saved_regs: &[String]) -> Vec { + let mut code = Vec::new(); + + // Restore in reverse order (LIFO) + for reg in saved_regs.iter().rev() { + code.push(format!("\tpop {}", reg)); + } + + code + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_basic_allocation() { + let mut allocator = RegisterAllocator::new(); + + let (reg1, code1) = allocator.alloc_temp().unwrap(); + assert_eq!(code1.len(), 0); // No spill needed + assert_eq!(reg1, "rg0"); + + let (reg2, code2) = allocator.alloc_temp().unwrap(); + assert_eq!(code2.len(), 0); + assert_eq!(reg2, "rg1"); + + allocator.free_temp(®1); + + let (reg3, code3) = allocator.alloc_temp().unwrap(); + assert_eq!(code3.len(), 0); + assert_eq!(reg3, "rg0"); // Reuses freed register + } + + #[test] + fn test_variable_allocation() { + let mut allocator = RegisterAllocator::new(); + + let (reg, _) = allocator.alloc_var("x").unwrap(); + assert_eq!(reg, "rg0"); + + // Requesting same variable again should return same register + let (reg2, _) = allocator.alloc_var("x").unwrap(); + assert_eq!(reg2, "rg0"); + } + + #[test] + fn test_stack_allocation() { + let mut allocator = RegisterAllocator::new(); + + // Allocate all 16 registers + for i in 0..16 { + allocator.alloc_var(&format!("var{}", i)).unwrap(); + } + + // Next allocation should spill to stack + let (reg, code) = allocator.alloc_var("var16").unwrap(); + assert!(code.len() > 0); // Should have spill code + } +} diff --git a/compiler/src/semantic_analyser.rs b/compiler/src/semantic_analyser.rs new file mode 100644 index 0000000..d8f3ce5 --- /dev/null +++ b/compiler/src/semantic_analyser.rs @@ -0,0 +1,13 @@ +use crate::parser::{CompilerError, Program}; + +pub struct Analyser; + +impl Analyser { + pub fn new() -> Self { + Self + } + + pub fn analyse(&self, ast: Program) -> Result<(), CompilerError> { + Ok(()) + } +} diff --git a/resources/dsa/code.dsa b/resources/dsa/code.dsa deleted file mode 100644 index 6a78ddc..0000000 --- a/resources/dsa/code.dsa +++ /dev/null @@ -1,139 +0,0 @@ - -// GENERATED BY DSA-C COMPILER -// Generated at 2026-01-31 01:39:55 - -// Imports -include maths: "./lib/maths/core.dsa" -include print: "lib/io/print.dsa" - -// Globals & Reserved Memory - - -// Entry Point -dw stack: 0x10000 -db message: "Process Exited with code:" -_init: - ldw stack, bpr - mov bpr, spr - push zero - call main - call print::print_newline - lwi message, rg0 - push rg0 - call print::print - pop zero - call print::print_hex_word - pop zero - hlt - - -// Function return boilerplate -_ret: - mov bpr, spr - pop bpr - return - - -factorial: - push bpr - mov spr, bpr - - ldw bpr, rg0, 8 - lli 1, rg1 - cmp rg0, rg1 - lli 0, rg2 - jgt _cmp_end_1 - lli 1, rg2 -_cmp_end_1: - cmp rg2, zero - jeq _else_3 -_then_2: - lli 1, rg1 - stw rg1, bpr, 8 - jmp _ret - jmp _end_4 -_else_3: - nop -_end_4: - push rg0 - lli 1, rg1 - sub rg0, rg1, rg2 - push rg2 - call factorial - pop rg1 - pop rg0 - push rg1 - push rg0 - call maths::multiply - pop rg2 - pop zero - stw rg2, bpr, 8 - jmp _ret - -add_: - push bpr - mov spr, bpr - - ldw bpr, rg0, 8 - ldw bpr, rg1, 12 - add rg0, rg1, rg2 - stw rg2, bpr, 8 - jmp _ret - -greater: - push bpr - mov spr, bpr - - ldw bpr, rg0, 8 - ldw bpr, rg1, 12 - add rg0, rg0, rg2 - add rg1, rg1, rg3 - cmp rg2, rg3 - lli 0, rg4 - jle _cmp_end_5 - lli 1, rg4 -_cmp_end_5: - cmp rg4, zero - jeq _else_7 -_then_6: - stw rg0, bpr, 8 - jmp _ret - jmp _end_8 -_else_7: - add rg1, rg0, rg2 - stw rg2, bpr, 8 - jmp _ret -_end_8: - jmp _ret - -main: - push bpr - mov spr, bpr - - lli 5, rg0 - push rg0 - lli 5, rg1 - push rg1 - call add_ - pop rg2 - pop zero - push rg2 - lli 5, rg0 - push rg0 - call greater - pop rg1 - pop zero - push rg1 - call print::print_num - pop rg0 - lli 5, rg0 - push rg0 - call factorial - pop rg1 - push rg1 - call print::print_num - pop rg0 - lli 0, rg0 - stw rg0, bpr, 8 - jmp _ret - diff --git a/resources/dsa/lib/io/print.dsa b/resources/dsa/lib/io/print.dsa index 43b2e83..8a5626c 100644 --- a/resources/dsa/lib/io/print.dsa +++ b/resources/dsa/lib/io/print.dsa @@ -40,7 +40,7 @@ dw display: 0x20000 dw current: 0x20000 // ------------------------------------------ -// prints the string at addr(arg[0]) to the screen. +// prints the string at addr(arg[0]) to the screen. (no trailing whitespace unless explicitly provided) print: push bpr mov spr, bpr @@ -50,13 +50,36 @@ print: _print_loop: ldb rg0, acc + cmp acc, zero + jeq _end stb acc, rg1 addi rg0, 1 addi rg1, 1 - cmp acc, zero - jne _print_loop + jmp _print_loop + +// ------------------------------------------ +println: + push bpr + mov spr, bpr + + ldw bpr, rg0, 8 + ldw current, rg1 + +_println_loop: + ldb rg0, acc + cmp acc, zero + jeq _println_end + stb acc, rg1 + + addi rg0, 1 + addi rg1, 1 + + jmp _println_loop + +_println_end: + call print_newline jmp _end // ------------------------------------------ diff --git a/resources/dsa/lib/maths/core.dsa b/resources/dsa/lib/maths/core.dsa index 25a2574..9fc72e8 100644 --- a/resources/dsa/lib/maths/core.dsa +++ b/resources/dsa/lib/maths/core.dsa @@ -59,3 +59,46 @@ _divmod_end: mov bpr, spr pop bpr return + +// multiply.dsa - improved version +// Multiplies two 32-bit numbers using shift-and-add +// +// Usage: +// push operand2 (multiplier) +// push operand1 (multiplicand) +// call multiply::multiply +// pop result +// pop zero (discard second argument) + +new_multiply: + push bpr + mov spr, bpr + + ldw bpr, rg0, 8 // rg0 = multiplicand + ldw bpr, rg1, 12 // rg1 = multiplier + + lli 0, rg2 // rg2 = result (accumulator) + lli 32, rg3 // rg3 = bit counter + +mult_loop: + // Check if lowest bit of multiplier is 1 + lli 1, acc + and rg1, acc, acc // acc = rg1 & 1 + cmp acc, zero + jeq skip_add // if (rg1 & 1) == 0, skip addition + + // Add multiplicand to result + add rg2, rg0, rg2 + +skip_add: + shl rg0, 1 // shift multiplicand left + shr rg1, 1 // shift multiplier right + + dec rg3 + cmp rg3, zero + jgt mult_loop + + stw rg2, bpr, 8 // store result + mov bpr, spr + pop bpr + return diff --git a/resources/dsa/lib/memory/alloc.dsa b/resources/dsa/lib/memory/alloc.dsa new file mode 100644 index 0000000..29a0243 --- /dev/null +++ b/resources/dsa/lib/memory/alloc.dsa @@ -0,0 +1,37 @@ +dw global_arena_start: 0x30000 +dw global_arena_current: 0x30000 +dw global_arena_end: 0x40000 + +arena_alloc: + // Just like bump allocator + push bpr + mov spr, bpr + + ldw bpr, rg0, 8 // size argument + ldw global_arena_current, rg1 + + add rg1, rg0, rg2 // new_current = current + size + ldw global_arena_end, rg3 + + cmp rg2, rg3 + jgt out_of_memory + + stw rg2, global_arena_current + mov rg1, acc // return old current + stw acc, bpr, 8 + + mov bpr, spr + pop bpr + return + +arena_reset: + // Reset to start + push bpr + mov spr, bpr + + ldw global_arena_start, rg0 + stw rg0, global_arena_current + + mov bpr, spr + pop bpr + return diff --git a/resources/dsa/main.dsa b/resources/dsa/main.dsa index 3020247..0f8daa2 100644 --- a/resources/dsa/main.dsa +++ b/resources/dsa/main.dsa @@ -15,13 +15,16 @@ init: dw string: "hello world" start: + lwi 100, rg0 + lwi 10, rg1 - lwi 1, rg0 - lwi 2, rg1 - - push rg0 push rg1 - call maths::multiply + push rg0 + call maths::new_divide + pop rg0 + pop rg1 + hlt + pop rg0 pop zero push rg0 diff --git a/resources/dsa/output.dsa b/resources/dsa/output.dsa new file mode 100644 index 0000000..23b3407 --- /dev/null +++ b/resources/dsa/output.dsa @@ -0,0 +1,67 @@ + +// GENERATED BY DSC COMPILER +// Generated at 2026-02-03 02:08:02 + +// Imports +include print: "./lib/io/print.dsa" + +// Globals & Reserved Memory + + +// Entry Point +dw stack: 0x10000 +db message: "Process Exited with code:" +_init: + ldw stack, bpr + mov bpr, spr + push zero + call main + call print::print_newline + lwi message, rg0 + push rg0 + call print::print + pop zero + call print::print_hex_word + pop zero + hlt + + +// Return +_ret: + mov bpr, spr + pop bpr + return + +// Compiled Code Starts... +main: + push bpr + mov spr, bpr + + lli 5, rg0 + db str_1: "Hello world" + lwi str_1, rg1 + db str_2: "test" + lwi str_2, rg2 + push rg0 + push rg1 + push rg2 + db str_3: "hello world 2 electric boogaloo" + lwi str_3, rg3 + push rg3 + call print::println + pop zero + pop rg2 + pop rg1 + pop rg0 + push rg0 + push rg1 + push rg2 + lli 213, rg3 + push rg3 + call print::print_num + pop zero + pop rg2 + pop rg1 + pop rg0 + jmp _ret + diff --git a/resources/dsc/example.dsc b/resources/dsc/example.dsc index 0f09703..cb07da2 100644 --- a/resources/dsc/example.dsc +++ b/resources/dsc/example.dsc @@ -1,2 +1,8 @@ -fn factorial(n: u32) -> u32 {} -fn main(x: u32, y: u32) -> u32 {} +fn main() -> u32 { + let x: u32 = 5; + let stringgg: str = "Hello world"; + let test: str = "test"; + + println("hello world 2 electric boogaloo"); + printnum(213); +} diff --git a/resources/ideas/design.rnote b/resources/ideas/design.rnote new file mode 100644 index 0000000000000000000000000000000000000000..1e969277c1c8afe46b3b23e76af512b086199561 GIT binary patch literal 490603 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

T8XABHV1-?yJfE>9kS@q_bk^oJ{4LJBI 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%0D6#KDe8^Lkc5Ng}A_YMZDc7ln`z23jFiI}e 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%0A-CQj@adwA-yjSqX)F!@SYj*pf=J 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 literal 0 HcmV?d00001 -- 2.47.3 From ce2eda72a030adae00e2aa2e45bb979c7f7afa4a Mon Sep 17 00:00:00 2001 From: zxq5 Date: Tue, 3 Feb 2026 15:34:35 +0000 Subject: [PATCH 13/53] updated roadmap with progress --- resources/ideas/DSA_Project_Roadmap.md | 82 +++++++++++++++----------- 1 file changed, 48 insertions(+), 34 deletions(-) diff --git a/resources/ideas/DSA_Project_Roadmap.md b/resources/ideas/DSA_Project_Roadmap.md index 563498e..a9604c4 100644 --- a/resources/ideas/DSA_Project_Roadmap.md +++ b/resources/ideas/DSA_Project_Roadmap.md @@ -236,19 +236,19 @@ **Dependencies:** None **Deliverable:** `docs/language-spec.md` -- [ ] Define syntax goals (simplicity, systems programming) +- [x] Define syntax goals (simplicity, systems programming) - [ ] Design type system - - [ ] Primitive types - - [ ] Pointers/references + - [x] Primitive types + - [x] Pointers/references - [ ] Structs - [ ] Arrays - - [ ] Function types -- [ ] Control flow syntax -- [ ] Function declaration syntax -- [ ] Module/import system -- [ ] Operator precedence + - [x] Function types +- [x] Control flow syntax +- [x] Function declaration syntax +- [x] Module/import system +- [x] Operator precedence - [ ] Write EBNF grammar -- [ ] Create example programs +- [x] Create example programs --- @@ -258,9 +258,13 @@ **Dependencies:** 2.1.1 **Deliverable:** Parser in `dsc-compiler` crate -- [ ] Adapt existing C lexer to new syntax +- [x] Adapt existing C lexer to new syntax - [ ] Implement new parser for designed syntax -- [ ] AST node definitions + - [ ] Array syntax + - [ ] Struct syntax + - [x] Pointer syntax + - [ ] Namespaced call syntax +- [x] AST node definitions - [ ] Error recovery mechanisms - [ ] Comprehensive parser tests - [ ] Syntax error message quality testing @@ -273,16 +277,16 @@ **Dependencies:** 2.1.2, 1.2.2 **Deliverable:** Working code generator -- [ ] Review and fix existing codegen issues +- [x] Review and fix existing codegen issues - [ ] Implement missing language features - [ ] Structs - [ ] Arrays - - [ ] Pointers/memory operations + - [x] Pointers/memory operations - [ ] For loops - [ ] Switch statements - [ ] Break/continue - [ ] Optimize register allocation further -- [ ] Implement proper function calling conventions +- [x] Implement proper function calling conventions - [ ] Add constant folding optimization - [ ] Dead code elimination - [ ] Test each feature thoroughly @@ -321,7 +325,17 @@ - [ ] Memory allocation (malloc/free) - [ ] String operations - [ ] Math functions + - [x] Multiply + - [ ] Divide (fix as very slow and broken) - [ ] I/O functions (improved print, read) + - [x] Print number + - [x] Print hex value + - [x] Print word + - [x] Print byte + - [x] Print from string ptr + - [x] Print whitespace and newline + - [x] Reset display + - [x] Reset cursor - [ ] System call interface - [ ] Tests for each function @@ -785,14 +799,14 @@ ## Summary Timeline -| Phase | Duration | Key Dependencies | -|---|---|---| -| Phase 1: Foundation | 3–4 weeks | None | -| Phase 2: Compiler | 3–4 weeks | Phase 1 complete | -| Phase 3: Build System | 2–3 weeks | Phases 1–2 complete | -| Phase 4: Debugger | 3–4 weeks | Phases 1–3 complete | -| Phase 5: Integration | 1–2 weeks | Phases 1–4 complete | -| Phase 6: CLI Emulator *(NTH)* | 4+ weeks | Phase 4 complete | +| Phase | Duration | Key Dependencies | +| ----------------------------- | --------- | ------------------- | +| Phase 1: Foundation | 3–4 weeks | None | +| Phase 2: Compiler | 3–4 weeks | Phase 1 complete | +| Phase 3: Build System | 2–3 weeks | Phases 1–2 complete | +| Phase 4: Debugger | 3–4 weeks | Phases 1–3 complete | +| Phase 5: Integration | 1–2 weeks | Phases 1–4 complete | +| Phase 6: CLI Emulator _(NTH)_ | 4+ weeks | Phase 4 complete | **Total Estimated Time: 12–17 weeks (3–4 months) for Phases 1–5** @@ -817,18 +831,18 @@ The following tasks are on the critical path and will block other work if delaye ## Recommended Work Order -| Weeks | Focus | Tasks | -|---|---|---| -| 1–2 | Binary Format & Linker | 1.1.1 → 1.1.2 → 1.1.3 | -| 3–4 | Assembler Rewrite | 1.2.1 → 1.2.2 | -| 5–6 | Compiler Syntax & Parser | 2.1.1 → 2.1.2 *(start 1.3 docs in parallel)* | -| 7–9 | Compiler Codegen & Types | 2.1.3 → 2.1.4 *(start 2.2.1 runtime in parallel)* | -| 10–11 | Build System | 3.1.1 → 3.1.2 → 3.1.3 | -| 12–13 | Package Management *(if desired now)* | 3.2.1 → 3.2.2 → 3.2.3 | -| 14–15 | Debug Symbols | 4.1.1 → 4.1.2 → 4.1.3 | -| 16–18 | Core Debugger | 4.2.1 → 4.2.2 → 4.2.4 | -| 19–20 | Editor Enhancements | 4.3.1 → 4.3.2 → 4.3.3 → 4.3.4 | -| 21–22 | Integration & Polish | 5.1.1 → 5.1.2 → 5.1.3 | +| Weeks | Focus | Tasks | +| ----- | ------------------------------------- | ------------------------------------------------- | +| 1–2 | Binary Format & Linker | 1.1.1 → 1.1.2 → 1.1.3 | +| 3–4 | Assembler Rewrite | 1.2.1 → 1.2.2 | +| 5–6 | Compiler Syntax & Parser | 2.1.1 → 2.1.2 _(start 1.3 docs in parallel)_ | +| 7–9 | Compiler Codegen & Types | 2.1.3 → 2.1.4 _(start 2.2.1 runtime in parallel)_ | +| 10–11 | Build System | 3.1.1 → 3.1.2 → 3.1.3 | +| 12–13 | Package Management _(if desired now)_ | 3.2.1 → 3.2.2 → 3.2.3 | +| 14–15 | Debug Symbols | 4.1.1 → 4.1.2 → 4.1.3 | +| 16–18 | Core Debugger | 4.2.1 → 4.2.2 → 4.2.4 | +| 19–20 | Editor Enhancements | 4.3.1 → 4.3.2 → 4.3.3 → 4.3.4 | +| 21–22 | Integration & Polish | 5.1.1 → 5.1.2 → 5.1.3 | --- -- 2.47.3 From 7973b2afcaa26a37ac153aeb85757823a99fa8f7 Mon Sep 17 00:00:00 2001 From: zxq5 Date: Tue, 3 Feb 2026 15:37:38 +0000 Subject: [PATCH 14/53] - refactored lexer - updated lexer to allow hex and binary integer literals - updated parser with support for writing to pointers - updated code generation to support writing to pointers - fixed a bug with codegen where args are loaded from incorrect offsets due to saving registers prior to calling. --- compiler/src/codegen.rs | 42 ++- compiler/src/lexer.rs | 613 +++++++++++++++++++++++++++++++++------- compiler/src/parser.rs | 43 +++ 3 files changed, 580 insertions(+), 118 deletions(-) diff --git a/compiler/src/codegen.rs b/compiler/src/codegen.rs index 878f167..54d0ba3 100644 --- a/compiler/src/codegen.rs +++ b/compiler/src/codegen.rs @@ -29,8 +29,10 @@ static GLOBAL_METHODS: LazyLock> = LazyLock::new(|| { ("println", "print::println"), ("printnum", "print::print_num"), ("print_space", "print::print_whitespace"), + ("print_newline", "print::print_newline"), ("print_char", "print::print_byte"), ("print_word", "print::print_word"), + ("print_hex", "print::print_hex_word"), ]) }); @@ -252,6 +254,19 @@ impl CodeGenerator { Statement::Break => unimplemented!(), Statement::Continue => unimplemented!(), + Statement::PtrWrite { ptr, value } => { + let (result_reg, expr_code) = self.generate_expression(value, true)?; + code.extend(expr_code); + + let (ptr_reg, ptr_code) = self.generate_expression(ptr, true)?; + code.extend(ptr_code); + + code.push(format!("\tstw {}, {}", result_reg, ptr_reg)); + + self.allocator.free_temp(&result_reg); + self.allocator.free_temp(&ptr_reg); + } + Statement::Assign { varname, value } => { // Evaluate expression let (result_reg, expr_code) = self.generate_expression(value, true)?; @@ -540,6 +555,14 @@ impl CodeGenerator { } Expression::Call { name, args } => { + // first evaluate all the args we're going to need + let mut arg_regs = Vec::new(); + for arg in args.iter().rev() { + let (arg_reg, arg_code) = self.generate_expression(arg, true)?; + code.extend(arg_code); + arg_regs.push(arg_reg); + } + // Save caller-saved registers and track which ones we saved let saved_regs = self.allocator.get_caller_saved_registers(); for reg in &saved_regs { @@ -547,12 +570,12 @@ impl CodeGenerator { } // Evaluate and push arguments in reverse order - let mut arg_regs = Vec::new(); - for arg in args.iter().rev() { - let (arg_reg, arg_code) = self.generate_expression(arg, true)?; - code.extend(arg_code); - code.push(format!("\tpush {}", arg_reg)); - arg_regs.push(arg_reg); + for (i, arg_reg) in arg_regs.iter().enumerate() { + code.push(format!( + "\tpush {} // push arg {}", + arg_reg, + args.len() - 1 - i + )); } if GLOBAL_METHODS.contains_key(name.name.as_str()) { @@ -564,10 +587,11 @@ impl CodeGenerator { return Err(CompilerError::Undefined(name.clone())); } - let result_reg = String::new(); + let result_reg: String; if use_result { - let (result_reg, result_alloc) = self.allocator.alloc_temp()?; + let (temp_result_reg, result_alloc) = self.allocator.alloc_temp()?; + result_reg = temp_result_reg; code.extend(result_alloc); code.push(format!("\tpop {}", result_reg)); @@ -579,6 +603,8 @@ impl CodeGenerator { } } } else { + result_reg = "zero".to_string(); + // Clean up arguments if args.len() > 0 { for _ in 0..(args.len()) { diff --git a/compiler/src/lexer.rs b/compiler/src/lexer.rs index 142343f..0c6c3f9 100644 --- a/compiler/src/lexer.rs +++ b/compiler/src/lexer.rs @@ -20,7 +20,7 @@ pub enum Token { // Identifiers and literals Identifier(String), String(String), - Integer(u32), + Integer(u64), Char(char), // Symbols @@ -31,13 +31,12 @@ pub enum Token { Semicolon, // ; Colon, // : Comma, // , - // Pipe, // | // Operators - Plus, // + - Minus, // - - Star, // * - Amphersand, + Plus, // + + Minus, // - + Star, // * + Amphersand, // & Slash, // / Assign, // = EqualEqual, // == @@ -80,7 +79,6 @@ impl Token { Token::Colon => "Colon", Token::Comma => "Comma", Token::RightArrow => "RightArrow", - // Token::Pipe => "Pipe", Token::Plus => "Plus", Token::Minus => "Minus", Token::Star => "Star", @@ -139,30 +137,258 @@ impl<'a> Lexer<'a> { } } + fn skip_line_comment(&mut self) { + // Skip the two slashes + self.advance(); // first / + self.advance(); // second / + + // Skip until newline or EOF + while let Some(c) = self.current { + if c == '\n' { + self.line += 1; + self.advance(); + break; + } + self.advance(); + } + } + + fn skip_block_comment(&mut self) -> Result<(), String> { + // Skip the /* + self.advance(); // / + self.advance(); // * + + let start_line = self.line; + + // Look for */ + while let Some(c) = self.current { + if c == '\n' { + self.line += 1; + } + + if c == '*' { + if let Some(&next) = self.peek() { + if next == '/' { + self.advance(); // * + self.advance(); // / + return Ok(()); + } + } + } + + self.advance(); + } + + Err(format!( + "Unterminated block comment starting at line {}", + start_line + )) + } + + fn skip_whitespace_and_comments(&mut self) { + loop { + self.skip_whitespace(); + + // Check for comments + if let Some('/') = self.current { + if let Some(&next) = self.peek() { + match next { + '/' => { + self.skip_line_comment(); + continue; + } + '*' => { + if let Err(e) = self.skip_block_comment() { + eprintln!("Lexer error: {}", e); + } + continue; + } + _ => break, + } + } + } + + break; + } + } + fn read_identifier(&mut self) -> String { let mut ident = String::new(); + + // Include the current character if it's valid + if let Some(c) = self.current { + if c.is_alphabetic() || c == '_' { + ident.push(c); + } + } + + // Read remaining characters while let Some(&c) = self.peek() { if c.is_alphanumeric() || c == '_' { - ident.push(c); self.advance(); + ident.push(c); } else { break; } } + ident } - fn read_number(&mut self) -> i64 { - let mut num_str = String::from(self.current.unwrap()); + fn keyword_or_identifier(&mut self) -> Token { + let ident = self.read_identifier(); + + match ident.as_str() { + "fn" => Token::Fn, + "if" => Token::If, + "else" => Token::Else, + "while" => Token::While, + "loop" => Token::Loop, + "break" => Token::Break, + "return" => Token::Return, + "continue" => Token::Continue, + "include" => Token::Include, + "let" => Token::Let, + "const" => Token::Const, + "static" => Token::Static, + _ => Token::Identifier(ident), + } + } + + fn read_number(&mut self) -> Result { + let current = self.current.unwrap(); + + // Check for hex (0x) or binary (0b) prefix + if current == '0' { + if let Some(&next_char) = self.peek() { + match next_char { + 'x' | 'X' => { + self.advance(); // consume '0' + self.advance(); // consume 'x' + return self.read_hex_number(); + } + 'b' | 'B' => { + self.advance(); // consume '0' + self.advance(); // consume 'b' + return self.read_binary_number(); + } + _ => {} + } + } + } + + // Read decimal number + self.read_decimal_number() + } + + fn read_decimal_number(&mut self) -> Result { + let mut num_str = String::new(); + + if let Some(c) = self.current { + num_str.push(c); + } + while let Some(&c) = self.peek() { if c.is_ascii_digit() { - num_str.push(c); self.advance(); + num_str.push(c); } else { break; } } - num_str.parse().unwrap() + + num_str + .parse::() + .map_err(|_| format!("Invalid decimal number: {}", num_str)) + } + + fn read_hex_number(&mut self) -> Result { + let mut num_str = String::new(); + + // Read current character if it's a hex digit + if let Some(c) = self.current { + if c.is_ascii_hexdigit() { + num_str.push(c); + } + } + + while let Some(&c) = self.peek() { + if c.is_ascii_hexdigit() { + self.advance(); + num_str.push(c); + } else { + break; + } + } + + if num_str.is_empty() { + return Err("Invalid hexadecimal number: no digits after 0x".to_string()); + } + + u64::from_str_radix(&num_str, 16) + .map_err(|_| format!("Invalid hexadecimal number: {}", num_str)) + } + + fn read_binary_number(&mut self) -> Result { + let mut num_str = String::new(); + + // Read current character if it's a binary digit + if let Some(c) = self.current { + if c == '0' || c == '1' { + num_str.push(c); + } + } + + while let Some(&c) = self.peek() { + if c == '0' || c == '1' { + self.advance(); + num_str.push(c); + } else { + break; + } + } + + if num_str.is_empty() { + return Err("Invalid binary number: no digits after 0b".to_string()); + } + + u64::from_str_radix(&num_str, 2) + .map_err(|_| format!("Invalid binary number: {}", num_str)) + } + + fn read_string(&mut self) -> Result { + self.advance(); // Skip the opening quote + let mut s = String::new(); + + while let Some(c) = self.current { + if c == '"' { + return Ok(s); + } + + // Handle escape sequences + if c == '\\' { + self.advance(); + if let Some(escaped) = self.current { + let escaped_char = match escaped { + 'n' => '\n', + 't' => '\t', + 'r' => '\r', + '\\' => '\\', + '"' => '"', + _ => escaped, // For now, just use the character as-is + }; + s.push(escaped_char); + } else { + return Err("Unexpected end of string after escape".to_string()); + } + } else { + s.push(c); + } + + self.advance(); + } + + Err("Unterminated string literal".to_string()) } fn match_next(&mut self, expected: char) -> bool { @@ -175,104 +401,140 @@ impl<'a> Lexer<'a> { } } - pub fn next_token(&mut self) -> Token { - self.skip_whitespace(); + fn scan_single_char_token(&mut self, c: char) -> Option { + match c { + '(' => Some(Token::LeftParen), + ')' => Some(Token::RightParen), + '{' => Some(Token::LeftBrace), + '}' => Some(Token::RightBrace), + ';' => Some(Token::Semicolon), + ':' => Some(Token::Colon), + ',' => Some(Token::Comma), + '&' => Some(Token::Amphersand), + '+' => Some(Token::Plus), + '*' => Some(Token::Star), + _ => None, + } + } - let token = match self.current { - Some('(') => Token::LeftParen, - Some(')') => Token::RightParen, - Some('{') => Token::LeftBrace, - Some('}') => Token::RightBrace, - Some(';') => Token::Semicolon, - Some(':') => Token::Colon, - Some(',') => Token::Comma, - Some('&') => Token::Amphersand, - // Some('|') => Token::Pipe, - Some('+') => Token::Plus, - Some('*') => Token::Star, - Some('/') => Token::Slash, - Some('-') => { - if self.match_next('>') { - Token::RightArrow - } else { - Token::Minus - } - } - Some('!') => { - if self.match_next('=') { - Token::BangEqual - } else { - Token::Bang - } - } - Some('=') => { - if self.match_next('=') { - Token::EqualEqual - } else { - Token::Assign - } - } - Some('<') => { - if self.match_next('=') { - Token::LessEqual - } else { - Token::Less - } - } - Some('>') => { - if self.match_next('=') { - Token::GreaterEqual - } else { - Token::Greater - } - } - Some('"') => { - self.advance(); // Skip the opening quote - let mut s = String::new(); - while let Some(c) = self.current { - if c == '"' { - break; + fn scan_operator(&mut self, c: char) -> Option { + match c { + '-' => Some(if self.match_next('>') { + Token::RightArrow + } else { + Token::Minus + }), + '!' => Some(if self.match_next('=') { + Token::BangEqual + } else { + Token::Bang + }), + '=' => Some(if self.match_next('=') { + Token::EqualEqual + } else { + Token::Assign + }), + '<' => Some(if self.match_next('=') { + Token::LessEqual + } else { + Token::Less + }), + '>' => Some(if self.match_next('=') { + Token::GreaterEqual + } else { + Token::Greater + }), + '/' => { + // Check if it's a comment or division + if let Some(&next) = self.peek() { + if next == '/' || next == '*' { + // It's a comment, don't consume it here + // Let skip_whitespace_and_comments handle it + None + } else { + Some(Token::Slash) } - s.push(c); - self.advance(); - } - Token::String(s) - } - Some(c) => { - if c.is_alphabetic() || c == '_' { - let mut ident = c.to_string(); - ident.push_str(&self.read_identifier()); - match ident.as_str() { - "fn" => Token::Fn, - "if" => Token::If, - "else" => Token::Else, - "while" => Token::While, - "loop" => Token::Loop, - "break" => Token::Break, - "return" => Token::Return, - "continue" => Token::Continue, - "include" => Token::Include, - "let" => Token::Let, - "const" => Token::Const, - "static" => Token::Static, - _ => Token::Identifier(ident), - } - } else if c.is_ascii_digit() { - Token::Integer(self.read_number() as u32) } else { - // Skip unknown characters for now - self.advance(); - return self.next_token(); + Some(Token::Slash) } } - None => Token::Eof, + _ => None, + } + } + + pub fn next_token(&mut self) -> Token { + self.skip_whitespace_and_comments(); + + let Some(c) = self.current else { + return Token::Eof; }; - if token != Token::Eof { + // Try single-character tokens first + if let Some(token) = self.scan_single_char_token(c) { self.advance(); + return token; } - token + // Try operators (may be multi-character) + if let Some(token) = self.scan_operator(c) { + self.advance(); + return token; + } + + // String literals + if c == '"' { + let token = match self.read_string() { + Ok(s) => Token::String(s), + Err(e) => { + eprintln!("Lexer error on line {}: {}", self.line, e); + // Skip to next quote or end + while let Some(ch) = self.current { + if ch == '"' || ch == '\n' { + break; + } + self.advance(); + } + Token::String(String::new()) + } + }; + self.advance(); + return token; + } + + // Identifiers and keywords + if c.is_alphabetic() || c == '_' { + let token = self.keyword_or_identifier(); + self.advance(); + return token; + } + + // Numbers (decimal, hex, binary) + if c.is_ascii_digit() { + let token = match self.read_number() { + Ok(num) => Token::Integer(num), + Err(e) => { + eprintln!("Lexer error on line {}: {}", self.line, e); + // Skip invalid number + while let Some(&ch) = self.peek() { + if !ch.is_alphanumeric() { + break; + } + self.advance(); + } + Token::Integer(0) + } + }; + self.advance(); + return token; + } + + // Unknown character - skip it + eprintln!( + "Lexer warning on line {}: Skipping unknown character '{}'", + self.line, c + ); + self.advance(); + self.next_token() } } @@ -318,6 +580,41 @@ mod tests { assert_eq!(lexer.next_token(), Token::Eof); } + #[test] + fn test_hex_numbers() { + let input = "0xFF 0x10 0xDEADBEEF 0x0"; + let mut lexer = Lexer::new(input); + + assert_eq!(lexer.next_token(), Token::Integer(0xFF)); + assert_eq!(lexer.next_token(), Token::Integer(0x10)); + assert_eq!(lexer.next_token(), Token::Integer(0xDEADBEEF)); + assert_eq!(lexer.next_token(), Token::Integer(0x0)); + assert_eq!(lexer.next_token(), Token::Eof); + } + + #[test] + fn test_binary_numbers() { + let input = "0b1010 0b0 0b11111111 0b1"; + let mut lexer = Lexer::new(input); + + assert_eq!(lexer.next_token(), Token::Integer(0b1010)); + assert_eq!(lexer.next_token(), Token::Integer(0b0)); + assert_eq!(lexer.next_token(), Token::Integer(0b11111111)); + assert_eq!(lexer.next_token(), Token::Integer(0b1)); + assert_eq!(lexer.next_token(), Token::Eof); + } + + #[test] + fn test_mixed_number_formats() { + let input = "42 0xFF 0b1010"; + let mut lexer = Lexer::new(input); + + assert_eq!(lexer.next_token(), Token::Integer(42)); + assert_eq!(lexer.next_token(), Token::Integer(255)); + assert_eq!(lexer.next_token(), Token::Integer(10)); + assert_eq!(lexer.next_token(), Token::Eof); + } + #[test] fn test_operators() { let input = "= == ! != < <= > >="; @@ -334,6 +631,19 @@ mod tests { assert_eq!(lexer.next_token(), Token::Eof); } + #[test] + fn test_string_with_escapes() { + let input = r#""hello\nworld" "tab\there""#; + let mut lexer = Lexer::new(input); + + assert_eq!( + lexer.next_token(), + Token::String("hello\nworld".to_string()) + ); + assert_eq!(lexer.next_token(), Token::String("tab\there".to_string())); + assert_eq!(lexer.next_token(), Token::Eof); + } + #[test] fn test_example_syntax() { let input = r#" @@ -349,25 +659,108 @@ mod tests { let mut lexer = Lexer::new(input); - // Skip whitespace and newlines - while let Some(c) = lexer.current { - if !c.is_whitespace() { - break; - } - lexer.advance(); - } - // Test the first few tokens assert_eq!(lexer.next_token(), Token::Identifier("main".to_string())); assert_eq!(lexer.next_token(), Token::Colon); assert_eq!(lexer.next_token(), Token::Identifier("Func".to_string())); assert_eq!(lexer.next_token(), Token::Assign); - // assert_eq!(lexer.next_token(), Token::Pipe); assert_eq!(lexer.next_token(), Token::Identifier("x".to_string())); assert_eq!(lexer.next_token(), Token::Colon); assert_eq!(lexer.next_token(), Token::Identifier("U32".to_string())); assert_eq!(lexer.next_token(), Token::Comma); + } - // The rest of the tokens would be tested similarly + #[test] + fn test_line_comments() { + let input = r#" + let x = 5; // this is a comment + // this is another comment + let y = 10; + "#; + + let mut lexer = Lexer::new(input); + + assert_eq!(lexer.next_token(), Token::Let); + assert_eq!(lexer.next_token(), Token::Identifier("x".to_string())); + assert_eq!(lexer.next_token(), Token::Assign); + assert_eq!(lexer.next_token(), Token::Integer(5)); + assert_eq!(lexer.next_token(), Token::Semicolon); + // Comment should be skipped + assert_eq!(lexer.next_token(), Token::Let); + assert_eq!(lexer.next_token(), Token::Identifier("y".to_string())); + assert_eq!(lexer.next_token(), Token::Assign); + assert_eq!(lexer.next_token(), Token::Integer(10)); + assert_eq!(lexer.next_token(), Token::Semicolon); + assert_eq!(lexer.next_token(), Token::Eof); + } + + #[test] + fn test_block_comments() { + let input = r#" + let x = 5; /* this is a + multiline block comment */ + let y = 10; + "#; + + let mut lexer = Lexer::new(input); + + assert_eq!(lexer.next_token(), Token::Let); + assert_eq!(lexer.next_token(), Token::Identifier("x".to_string())); + assert_eq!(lexer.next_token(), Token::Assign); + assert_eq!(lexer.next_token(), Token::Integer(5)); + assert_eq!(lexer.next_token(), Token::Semicolon); + // Block comment should be skipped + assert_eq!(lexer.next_token(), Token::Let); + assert_eq!(lexer.next_token(), Token::Identifier("y".to_string())); + assert_eq!(lexer.next_token(), Token::Assign); + assert_eq!(lexer.next_token(), Token::Integer(10)); + assert_eq!(lexer.next_token(), Token::Semicolon); + assert_eq!(lexer.next_token(), Token::Eof); + } + + #[test] + fn test_division_operator() { + let input = "x / y"; + let mut lexer = Lexer::new(input); + + assert_eq!(lexer.next_token(), Token::Identifier("x".to_string())); + assert_eq!(lexer.next_token(), Token::Slash); + assert_eq!(lexer.next_token(), Token::Identifier("y".to_string())); + assert_eq!(lexer.next_token(), Token::Eof); + } + + #[test] + fn test_mixed_comments_and_operators() { + let input = r#" + x / y // division + /* block comment */ z = 10 + a /= b // this won't work yet + "#; + + let mut lexer = Lexer::new(input); + + assert_eq!(lexer.next_token(), Token::Identifier("x".to_string())); + assert_eq!(lexer.next_token(), Token::Slash); + assert_eq!(lexer.next_token(), Token::Identifier("y".to_string())); + assert_eq!(lexer.next_token(), Token::Identifier("z".to_string())); + assert_eq!(lexer.next_token(), Token::Assign); + assert_eq!(lexer.next_token(), Token::Integer(10)); + assert_eq!(lexer.next_token(), Token::Identifier("a".to_string())); + assert_eq!(lexer.next_token(), Token::Slash); + assert_eq!(lexer.next_token(), Token::Assign); + assert_eq!(lexer.next_token(), Token::Identifier("b".to_string())); + assert_eq!(lexer.next_token(), Token::Eof); + } + + #[test] + fn test_nested_block_comment_attempt() { + // Note: This lexer doesn't support nested block comments + let input = "/* outer /* inner */ still in comment? */ x"; + let mut lexer = Lexer::new(input); + + // The comment ends at the first */ + assert_eq!(lexer.next_token(), Token::Identifier("still".to_string())); + assert_eq!(lexer.next_token(), Token::Identifier("in".to_string())); + assert_eq!(lexer.next_token(), Token::Identifier("comment".to_string())); } } diff --git a/compiler/src/parser.rs b/compiler/src/parser.rs index 755df29..2da5de2 100644 --- a/compiler/src/parser.rs +++ b/compiler/src/parser.rs @@ -247,6 +247,45 @@ impl Parser { return ParseResult::Accept(Statement::Continue); } + // handle writes to pointers! + if expect_tt!(self.peek_next()?, Star).accepted() { + self.next()?; + + let left = if expect_tt!(self.peek_next()?, Identifier).accepted() { + let identifier = self.parse_identifier()?; + + Expression::Variable { + name: identifier, + expr_type: None, + } + } else if expect_tt!(self.peek_next()?, LeftParen).accepted() { + self.next()?; + + let expr = self.parse_expression()?; + + let _ = expect_tt!(self.next()?, RightParen).accepted(); + + expr + } else { + return ParseResult::Reject(CompilerError::UnexpectedToken( + self.peek_next()?, + )); + }; + + let _ = expect_tt!(self.next()?, Assign)?; + + let right = self.parse_expression()?; + + // expect semicolon + expect_tt!(self.next()?, Semicolon)?; + + // return result + return ParseResult::Accept(Statement::PtrWrite { + ptr: left, + value: right, + }); + } + // handle let statements (declarations) if expect_tt!(self.peek_next()?, Let).accepted() { self.next(); @@ -573,6 +612,10 @@ pub enum Statement { varname: String, value: Expression, }, + PtrWrite { + ptr: Expression, + value: Expression, + }, Expression { expr: Expression, }, -- 2.47.3 From 48a74bfde2f59bdf99de2fc18f0ff3e54f8e2334 Mon Sep 17 00:00:00 2001 From: zxq5 Date: Tue, 3 Feb 2026 15:38:40 +0000 Subject: [PATCH 15/53] updated dsc example to reflect current feature set. --- resources/dsc/example.dsc | 112 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 107 insertions(+), 5 deletions(-) diff --git a/resources/dsc/example.dsc b/resources/dsc/example.dsc index cb07da2..6a7738f 100644 --- a/resources/dsc/example.dsc +++ b/resources/dsc/example.dsc @@ -1,8 +1,110 @@ fn main() -> u32 { - let x: u32 = 5; - let stringgg: str = "Hello world"; - let test: str = "test"; - println("hello world 2 electric boogaloo"); - printnum(213); + let x: u32 = 0; + let y: u32 = &x; + + let alloc: u32 = arena_create(512); + let ptr1: u32 = arena_alloc(alloc, 32); + let ptr2: u32 = arena_alloc(alloc, 32); + + print_hex(alloc); + print_newline(); + print_hex(ptr1); + print_newline(); + print_hex(ptr2); + print_newline(); + printnum(*ptr2); + print_newline(); + *ptr2 = 42; + + print_hex(ptr2); + print_newline(); + printnum(*ptr2); + print_newline(); + println("end"); + + return 0; +} + + + + + + +// Arena Allocator +// Supports multiple arenas that can be destroyed independently +// Much more practical than a simple bump allocator + +// Global heap management +static heap_start: u32 = 0x30000; +static heap_end: u32 = 0x40000; +static heap_current: u32 = 0x30000; + +// Arena structure (stored at the start of each arena): +// [0-3]: start_address (u32) +// [4-7]: current_position (u32) +// [8-11]: end_address (u32) +// Total header size: 12 bytes + +// Create a new arena with given size +// Returns pointer to arena handle (or 0 if failed) +fn arena_create(size: u32) -> u32 { + let total_size: u32 = size + 12; + let arena_ptr: u32 = heap_current; + let new_current: u32 = arena_ptr + total_size; + + // Check if we have space + if new_current > heap_end { + return 0; + } + + // Calculate arena data region + let data_start: u32 = arena_ptr + 12; + let data_end: u32 = arena_ptr + total_size; + + // Initialize arena header + // Note: In real implementation, you'd use pointer writes here + // For now, using placeholder comments: + *arena_ptr = data_start; // start_address + *(arena_ptr + 4) = data_start; // current_position + *(arena_ptr + 8) = data_end; // end_address + + heap_current = new_current; + + return arena_ptr; +} + +// Allocate from an arena +// Returns pointer to allocated memory (or 0 if failed) +fn arena_alloc(arena: u32, size: u32) -> u32 { + // Read current position from arena + let current: u32 = *(arena + 4); + let end: u32 = *(arena + 8); + + let new_current: u32 = current + size; + + // Check if arena has space + if new_current > end { + return 0; + } + + // Update current position in arena + *(arena + 4) = new_current; + + return current; +} + +// Destroy an arena (in bump allocator, this is a no-op) +// In a real allocator, you'd mark the memory as free +fn arena_destroy(arena: u32) { + // In a true allocator, mark memory as reusable + // For bump allocator, we can't reclaim memory + // unless we destroy ALL arenas and reset + return 0; +} + +// Reset entire heap (destroys ALL arenas) +fn reset_all() { + heap_current = heap_start; + return 0; } -- 2.47.3 From f25db6c8fd3a64d1e12d92b71e055c1d730efd1d Mon Sep 17 00:00:00 2001 From: zxq5 Date: Wed, 4 Feb 2026 01:56:15 +0000 Subject: [PATCH 16/53] updated assembler logging --- assembler/src/assembler/codegen.rs | 3 +-- assembler/src/assembler/lexer.rs | 2 +- assembler/src/assembler/mod.rs | 24 ++++++++---------------- 3 files changed, 10 insertions(+), 19 deletions(-) diff --git a/assembler/src/assembler/codegen.rs b/assembler/src/assembler/codegen.rs index 3e34281..4d85ffc 100644 --- a/assembler/src/assembler/codegen.rs +++ b/assembler/src/assembler/codegen.rs @@ -14,8 +14,7 @@ pub fn codegen(nodes: Vec) -> Result, AssembleError> { instructions.push(build_instruction(&node)?); } - println!("------------------------"); - log("Compilation Success ✅"); + log("Assembly Successful ✅"); Ok(instructions) } diff --git a/assembler/src/assembler/lexer.rs b/assembler/src/assembler/lexer.rs index 442d9b3..69ee4d2 100644 --- a/assembler/src/assembler/lexer.rs +++ b/assembler/src/assembler/lexer.rs @@ -65,7 +65,7 @@ pub fn lexer(mut program: String, module: u64) -> Result, AssembleErr } } - println!("{:#?}", tokens); + // println!("{:#?}", tokens); Ok(tokens) } diff --git a/assembler/src/assembler/mod.rs b/assembler/src/assembler/mod.rs index 966febb..a9d83ad 100644 --- a/assembler/src/assembler/mod.rs +++ b/assembler/src/assembler/mod.rs @@ -9,13 +9,9 @@ use std::{ thread, }; +pub use common::logging::log; use common::prelude::Instruction; -// TODO: Use an actual logging or tracing library for pretty (scoped) output. -fn log(message: &str) { - println!("\x1b[32mINFO:\x1b[0m {message}"); -} - // Module declarations #[macro_use] pub mod macros; @@ -138,12 +134,11 @@ fn assemble(src: &Path) -> Result, AssembleError> { create_sections(&mut nodes)?; resolve_symbols(&mut nodes)?; - println!("Generating assembly output..."); - for n in &nodes { - println!("{n}"); - } + log("Generating assembly output..."); let instructions = codegen(nodes)?; + + log("Compilation Successful"); Ok(instructions) } @@ -192,10 +187,7 @@ fn prepare_dependency( let deps = Parser::get_dependencies(&nodes, path)?; - log(&format!( - "{:20} {:20}", - "Expanding PseudoInstructions", filename - )); + log(&format!("{:20} {:20}", "Expanding Pseudo-ops", filename)); // add a section instruction nodes.insert( @@ -203,9 +195,9 @@ fn prepare_dependency( node!(None, Opcode::Segment, Token::Immediate(file_hash as u32)), ); - for n in &nodes { - println!("{n}"); - } + // for n in &nodes { + // println!("{n}"); + // } program.add_module(nodes); -- 2.47.3 From 14a04a524c1b6fbfed88ff02626a627ec0f5d83f Mon Sep 17 00:00:00 2001 From: zxq5 Date: Wed, 4 Feb 2026 01:56:58 +0000 Subject: [PATCH 17/53] added support for DSA libraries to compiler and made some optimisations. provided an API for the editor to use. --- common/src/lib.rs | 1 + compiler/Cargo.toml | 1 + compiler/bacon.toml | 4 +- compiler/src/codegen.rs | 50 ++++-- compiler/src/lexer.rs | 313 +++++++++++--------------------------- compiler/src/main.rs | 50 +----- compiler/src/parser.rs | 70 ++++----- compiler/src/registers.rs | 83 ++++++---- 8 files changed, 212 insertions(+), 360 deletions(-) diff --git a/common/src/lib.rs b/common/src/lib.rs index f1bc305..23c524e 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -13,6 +13,7 @@ )] pub mod instructions; +pub mod logging; pub mod prelude { //! A collection of types you should definitely import when working with this crate. diff --git a/compiler/Cargo.toml b/compiler/Cargo.toml index c228e0b..ba60012 100644 --- a/compiler/Cargo.toml +++ b/compiler/Cargo.toml @@ -6,3 +6,4 @@ authors.workspace = true [dependencies] chrono = "0.4.43" +common = { path = "../common" } diff --git a/compiler/bacon.toml b/compiler/bacon.toml index f016c40..3a445ff 100644 --- a/compiler/bacon.toml +++ b/compiler/bacon.toml @@ -84,8 +84,8 @@ command = [ "cargo", "run", "--color", "always", "--", - "../resources/dsc/example.dsc", - "../resources/dsa/output.dsa" + "../resources/dsa/example.dsc", + "../resources/dsa/example.dsa" # put launch parameters for your program behind a `--` separator ] need_stdout = true diff --git a/compiler/src/codegen.rs b/compiler/src/codegen.rs index 54d0ba3..6933e8a 100644 --- a/compiler/src/codegen.rs +++ b/compiler/src/codegen.rs @@ -25,14 +25,14 @@ pub struct CodeGenerator { static GLOBAL_METHODS: LazyLock> = LazyLock::new(|| { HashMap::from([ - ("print", "print::print"), - ("println", "print::println"), - ("printnum", "print::print_num"), - ("print_space", "print::print_whitespace"), - ("print_newline", "print::print_newline"), - ("print_char", "print::print_byte"), - ("print_word", "print::print_word"), - ("print_hex", "print::print_hex_word"), + // ("print", "print::print"), + // ("println", "print::println"), + // ("printnum", "print::print_num"), + // ("print_space", "print::print_whitespace"), + // ("print_newline", "print::print_newline"), + // ("print_char", "print::print_byte"), + // ("print_word", "print::print_word"), + // ("print_hex", "print::print_hex_word"), ]) }); @@ -403,6 +403,11 @@ impl CodeGenerator { ) -> Result<(String, Vec), CompilerError> { let mut code = Vec::new(); + // optimisation to prevent generating dead code! + if expr.is_pure() && !use_result { + return Ok((String::new(), code)); + } + match expr { Expression::StringLiteral(value) => { let (reg, alloc_code) = self.allocator.alloc_temp()?; @@ -563,10 +568,18 @@ impl CodeGenerator { arg_regs.push(arg_reg); } + // Save caller-saved registers and track which ones we saved + // old method, inefficient. + // let saved_regs = self.allocator.get_caller_saved_registers(); + // for reg in &saved_regs { + // code.push(format!("\tpush {}", reg)); + // } + // Save caller-saved registers and track which ones we saved let saved_regs = self.allocator.get_caller_saved_registers(); for reg in &saved_regs { - code.push(format!("\tpush {}", reg)); + // spill variables to stack + code.extend(self.allocator.spill_register(reg).unwrap()); } // Evaluate and push arguments in reverse order @@ -578,11 +591,16 @@ impl CodeGenerator { )); } - if GLOBAL_METHODS.contains_key(name.name.as_str()) { - code.push(format!("\tcall {}", GLOBAL_METHODS[name.name.as_str()])); - } else if self.symbols.contains(&name.name) { + // if GLOBAL_METHODS.contains_key(name.name.as_str()) { + // code.push(format!("\tcall {}", + // GLOBAL_METHODS[name.name.as_str()])); } else + if self.symbols.contains(&name.name) { // Call local function - code.push(format!("\tcall {}", name.name)); + code.push(format!("\tcall {}", name)); + } else if let Some(ns) = name.namespace.clone() + && self.imports.contains_key(&ns) + { + code.push(format!("\tcall {}", name)); } else { return Err(CompilerError::Undefined(name.clone())); } @@ -614,9 +632,9 @@ impl CodeGenerator { } // Restore caller-saved registers in reverse order (LIFO) - for reg in saved_regs.iter().rev() { - code.push(format!("\tpop {}", reg)); - } + // for reg in saved_regs.iter().rev() { + // code.push(format!("\tpop {}", reg)); + // } // Free argument registers for reg in arg_regs { diff --git a/compiler/src/lexer.rs b/compiler/src/lexer.rs index 0c6c3f9..5245bee 100644 --- a/compiler/src/lexer.rs +++ b/compiler/src/lexer.rs @@ -18,7 +18,7 @@ pub enum Token { Const, // Identifiers and literals - Identifier(String), + Identifier(Name), String(String), Integer(u64), Char(char), @@ -52,6 +52,24 @@ pub enum Token { Eof, } +#[derive(Debug, PartialEq, Clone)] +pub struct Name { + pub name: String, + pub namespace: Option, +} + +use std::fmt; + +impl fmt::Display for Name { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some(ref ns) = self.namespace { + write!(f, "{}::{}", ns, self.name) + } else { + write!(f, "{}", self.name) + } + } +} + impl Token { pub fn tt(&self) -> &str { match self { @@ -236,23 +254,67 @@ impl<'a> Lexer<'a> { } fn keyword_or_identifier(&mut self) -> Token { - let ident = self.read_identifier(); + let first_ident = self.read_identifier(); - match ident.as_str() { - "fn" => Token::Fn, - "if" => Token::If, - "else" => Token::Else, - "while" => Token::While, - "loop" => Token::Loop, - "break" => Token::Break, - "return" => Token::Return, - "continue" => Token::Continue, - "include" => Token::Include, - "let" => Token::Let, - "const" => Token::Const, - "static" => Token::Static, - _ => Token::Identifier(ident), + // Check if it's a keyword first (keywords can't have namespaces) + let keyword = match first_ident.as_str() { + "fn" => Some(Token::Fn), + "if" => Some(Token::If), + "else" => Some(Token::Else), + "while" => Some(Token::While), + "loop" => Some(Token::Loop), + "break" => Some(Token::Break), + "return" => Some(Token::Return), + "continue" => Some(Token::Continue), + "include" => Some(Token::Include), + "let" => Some(Token::Let), + "const" => Some(Token::Const), + "static" => Some(Token::Static), + _ => None, + }; + + if let Some(kw) = keyword { + return kw; } + + // Not a keyword - check for namespace separator (::) + // We need to peek TWO characters ahead without consuming anything + if let Some(&':') = self.peek() { + // We see one colon, but we need to check if there's another one after it + // We can't peek two ahead directly, so we need a different approach + + // Save the current position by using a temporary peekable iterator + // Actually, we can't do that easily. Instead, let's just check: + // If we see ':', temporarily advance and check the next char + + // Create a temporary check + let mut temp_chars = self.chars.clone(); + let first_peek = temp_chars.next(); // This is the ':' we already saw + let second_peek = temp_chars.peek(); + + if let Some(&':') = second_peek { + // It's :: - consume both colons + self.advance(); // consume first : + self.advance(); // consume second : + + // Read the second identifier (the actual name) + let second_ident = self.read_identifier(); + + // Return namespaced identifier + return Token::Identifier(Name { + namespace: Some(first_ident), + name: second_ident, + }); + } + // else: It's a single colon (type annotation) - DON'T consume it + // Just fall through and return the identifier + } + + // No namespace separator - just a regular identifier + Token::Identifier(Name { + namespace: None, + name: first_ident, + }) } fn read_number(&mut self) -> Result { @@ -408,7 +470,6 @@ impl<'a> Lexer<'a> { '{' => Some(Token::LeftBrace), '}' => Some(Token::RightBrace), ';' => Some(Token::Semicolon), - ':' => Some(Token::Colon), ',' => Some(Token::Comma), '&' => Some(Token::Amphersand), '+' => Some(Token::Plus), @@ -444,6 +505,11 @@ impl<'a> Lexer<'a> { } else { Token::Greater }), + ':' => { + // Single colon (for type annotations) + // Note: :: is handled in keyword_or_identifier for namespaces + Some(Token::Colon) + } '/' => { // Check if it's a comment or division if let Some(&next) = self.peek() { @@ -501,7 +567,7 @@ impl<'a> Lexer<'a> { return token; } - // Identifiers and keywords + // Identifiers and keywords (including namespaced identifiers) if c.is_alphabetic() || c == '_' { let token = self.keyword_or_identifier(); self.advance(); @@ -554,213 +620,8 @@ mod tests { use super::*; #[test] - fn test_keywords() { - let input = "if else loop break return continue"; - let mut lexer = Lexer::new(input); - - assert_eq!(lexer.next_token(), Token::If); - assert_eq!(lexer.next_token(), Token::Else); - assert_eq!(lexer.next_token(), Token::Loop); - assert_eq!(lexer.next_token(), Token::Break); - assert_eq!(lexer.next_token(), Token::Return); - assert_eq!(lexer.next_token(), Token::Continue); - assert_eq!(lexer.next_token(), Token::Eof); - } - - #[test] - fn test_identifiers_and_numbers() { - let input = "x y42 _test 123 45"; - let mut lexer = Lexer::new(input); - - assert_eq!(lexer.next_token(), Token::Identifier("x".to_string())); - assert_eq!(lexer.next_token(), Token::Identifier("y42".to_string())); - assert_eq!(lexer.next_token(), Token::Identifier("_test".to_string())); - assert_eq!(lexer.next_token(), Token::Integer(123)); - assert_eq!(lexer.next_token(), Token::Integer(45)); - assert_eq!(lexer.next_token(), Token::Eof); - } - - #[test] - fn test_hex_numbers() { - let input = "0xFF 0x10 0xDEADBEEF 0x0"; - let mut lexer = Lexer::new(input); - - assert_eq!(lexer.next_token(), Token::Integer(0xFF)); - assert_eq!(lexer.next_token(), Token::Integer(0x10)); - assert_eq!(lexer.next_token(), Token::Integer(0xDEADBEEF)); - assert_eq!(lexer.next_token(), Token::Integer(0x0)); - assert_eq!(lexer.next_token(), Token::Eof); - } - - #[test] - fn test_binary_numbers() { - let input = "0b1010 0b0 0b11111111 0b1"; - let mut lexer = Lexer::new(input); - - assert_eq!(lexer.next_token(), Token::Integer(0b1010)); - assert_eq!(lexer.next_token(), Token::Integer(0b0)); - assert_eq!(lexer.next_token(), Token::Integer(0b11111111)); - assert_eq!(lexer.next_token(), Token::Integer(0b1)); - assert_eq!(lexer.next_token(), Token::Eof); - } - - #[test] - fn test_mixed_number_formats() { - let input = "42 0xFF 0b1010"; - let mut lexer = Lexer::new(input); - - assert_eq!(lexer.next_token(), Token::Integer(42)); - assert_eq!(lexer.next_token(), Token::Integer(255)); - assert_eq!(lexer.next_token(), Token::Integer(10)); - assert_eq!(lexer.next_token(), Token::Eof); - } - - #[test] - fn test_operators() { - let input = "= == ! != < <= > >="; - let mut lexer = Lexer::new(input); - - assert_eq!(lexer.next_token(), Token::Assign); - assert_eq!(lexer.next_token(), Token::EqualEqual); - assert_eq!(lexer.next_token(), Token::Bang); - assert_eq!(lexer.next_token(), Token::BangEqual); - assert_eq!(lexer.next_token(), Token::Less); - assert_eq!(lexer.next_token(), Token::LessEqual); - assert_eq!(lexer.next_token(), Token::Greater); - assert_eq!(lexer.next_token(), Token::GreaterEqual); - assert_eq!(lexer.next_token(), Token::Eof); - } - - #[test] - fn test_string_with_escapes() { - let input = r#""hello\nworld" "tab\there""#; - let mut lexer = Lexer::new(input); - - assert_eq!( - lexer.next_token(), - Token::String("hello\nworld".to_string()) - ); - assert_eq!(lexer.next_token(), Token::String("tab\there".to_string())); - assert_eq!(lexer.next_token(), Token::Eof); - } - - #[test] - fn test_example_syntax() { - let input = r#" - main: Func = | x: U32, y: U32 | { - res = add(x, y); - print(res); - - if res > 10 { - print("res is greater than 10"); - } - } - "#; - - let mut lexer = Lexer::new(input); - - // Test the first few tokens - assert_eq!(lexer.next_token(), Token::Identifier("main".to_string())); - assert_eq!(lexer.next_token(), Token::Colon); - assert_eq!(lexer.next_token(), Token::Identifier("Func".to_string())); - assert_eq!(lexer.next_token(), Token::Assign); - assert_eq!(lexer.next_token(), Token::Identifier("x".to_string())); - assert_eq!(lexer.next_token(), Token::Colon); - assert_eq!(lexer.next_token(), Token::Identifier("U32".to_string())); - assert_eq!(lexer.next_token(), Token::Comma); - } - - #[test] - fn test_line_comments() { - let input = r#" - let x = 5; // this is a comment - // this is another comment - let y = 10; - "#; - - let mut lexer = Lexer::new(input); - - assert_eq!(lexer.next_token(), Token::Let); - assert_eq!(lexer.next_token(), Token::Identifier("x".to_string())); - assert_eq!(lexer.next_token(), Token::Assign); - assert_eq!(lexer.next_token(), Token::Integer(5)); - assert_eq!(lexer.next_token(), Token::Semicolon); - // Comment should be skipped - assert_eq!(lexer.next_token(), Token::Let); - assert_eq!(lexer.next_token(), Token::Identifier("y".to_string())); - assert_eq!(lexer.next_token(), Token::Assign); - assert_eq!(lexer.next_token(), Token::Integer(10)); - assert_eq!(lexer.next_token(), Token::Semicolon); - assert_eq!(lexer.next_token(), Token::Eof); - } - - #[test] - fn test_block_comments() { - let input = r#" - let x = 5; /* this is a - multiline block comment */ - let y = 10; - "#; - - let mut lexer = Lexer::new(input); - - assert_eq!(lexer.next_token(), Token::Let); - assert_eq!(lexer.next_token(), Token::Identifier("x".to_string())); - assert_eq!(lexer.next_token(), Token::Assign); - assert_eq!(lexer.next_token(), Token::Integer(5)); - assert_eq!(lexer.next_token(), Token::Semicolon); - // Block comment should be skipped - assert_eq!(lexer.next_token(), Token::Let); - assert_eq!(lexer.next_token(), Token::Identifier("y".to_string())); - assert_eq!(lexer.next_token(), Token::Assign); - assert_eq!(lexer.next_token(), Token::Integer(10)); - assert_eq!(lexer.next_token(), Token::Semicolon); - assert_eq!(lexer.next_token(), Token::Eof); - } - - #[test] - fn test_division_operator() { - let input = "x / y"; - let mut lexer = Lexer::new(input); - - assert_eq!(lexer.next_token(), Token::Identifier("x".to_string())); - assert_eq!(lexer.next_token(), Token::Slash); - assert_eq!(lexer.next_token(), Token::Identifier("y".to_string())); - assert_eq!(lexer.next_token(), Token::Eof); - } - - #[test] - fn test_mixed_comments_and_operators() { - let input = r#" - x / y // division - /* block comment */ z = 10 - a /= b // this won't work yet - "#; - - let mut lexer = Lexer::new(input); - - assert_eq!(lexer.next_token(), Token::Identifier("x".to_string())); - assert_eq!(lexer.next_token(), Token::Slash); - assert_eq!(lexer.next_token(), Token::Identifier("y".to_string())); - assert_eq!(lexer.next_token(), Token::Identifier("z".to_string())); - assert_eq!(lexer.next_token(), Token::Assign); - assert_eq!(lexer.next_token(), Token::Integer(10)); - assert_eq!(lexer.next_token(), Token::Identifier("a".to_string())); - assert_eq!(lexer.next_token(), Token::Slash); - assert_eq!(lexer.next_token(), Token::Assign); - assert_eq!(lexer.next_token(), Token::Identifier("b".to_string())); - assert_eq!(lexer.next_token(), Token::Eof); - } - - #[test] - fn test_nested_block_comment_attempt() { - // Note: This lexer doesn't support nested block comments - let input = "/* outer /* inner */ still in comment? */ x"; - let mut lexer = Lexer::new(input); - - // The comment ends at the first */ - assert_eq!(lexer.next_token(), Token::Identifier("still".to_string())); - assert_eq!(lexer.next_token(), Token::Identifier("in".to_string())); - assert_eq!(lexer.next_token(), Token::Identifier("comment".to_string())); + fn test_basic() { + // Placeholder test + assert!(true); } } diff --git a/compiler/src/main.rs b/compiler/src/main.rs index 854cd9d..c5eb367 100644 --- a/compiler/src/main.rs +++ b/compiler/src/main.rs @@ -1,15 +1,6 @@ -#![feature(try_trait_v2)] +use std::path::Path; -use std::{fs, path::Path}; - -pub mod lexer; -pub mod parser; -use parser::Parser; -pub mod codegen; -mod registers; -mod semantic_analyser; - -use crate::{codegen::CodeGenerator, parser::ParseResult, semantic_analyser::Analyser}; +use compiler; fn main() { // read from input file: syntax "c_compiler [output.dsa]" @@ -26,40 +17,5 @@ fn main() { "output.dsa" }; - // read input - let input = std::fs::read_to_string(input_file).expect("Failed to read input file"); - - let lexer = lexer::Lexer::new(&input); - let tokens = lexer.collect::>(); - println!("{tokens:?}"); - - let mut parser = Parser::new(tokens); - let ast = match parser.parse() { - ParseResult::Accept(ast) => ast, - ParseResult::Reject(e) => { - eprintln!("Error: {e:?}"); - return; - } - ParseResult::Deny => { - panic!("Parser denied parsing") - } - }; - println!("{ast:#?}"); - - let analyser = Analyser::new(); - analyser.analyse(ast.clone()).unwrap(); - - // Code Gen - let mut generator = CodeGenerator::new(ast); - let result = match generator.generate() { - Ok(code) => code, - Err(e) => { - eprintln!("Parsing error: {:?}", e); - return; - } - }; - - println!("{result}"); - std::fs::write(output_file, &result).expect("Failed to write output"); - println!("Result written to {}", output_file); + compiler::compile_file(Path::new(input_file), Path::new(output_file)).unwrap(); } diff --git a/compiler/src/parser.rs b/compiler/src/parser.rs index 2da5de2..6b56d55 100644 --- a/compiler/src/parser.rs +++ b/compiler/src/parser.rs @@ -1,4 +1,4 @@ -use crate::lexer::Token; +use crate::lexer::{Name, Token}; use crate::{expect_tt, expect_value}; use core::fmt; use std::ops::{ControlFlow, FromResidual, Try}; @@ -62,7 +62,7 @@ impl Parser { let _ = expect_tt!(self.next()?, Semicolon)?; return ParseResult::Accept(Declaration::Dependency(Dependency { - name, + name: name.name, path, })); } @@ -135,7 +135,7 @@ impl Parser { // expect vald block if expect_tt!(self.peek_next()?, LeftBrace).accepted() { ParseResult::Accept(Declaration::Function { - name, + name: name.name, params, return_type, body: self.parse_block()?, @@ -252,7 +252,7 @@ impl Parser { self.next()?; let left = if expect_tt!(self.peek_next()?, Identifier).accepted() { - let identifier = self.parse_identifier()?; + let identifier = expect_value!(self.next()?, Identifier)?; Expression::Variable { name: identifier, @@ -322,11 +322,7 @@ impl Parser { let name = expect_value!(self.peek_next()?, Identifier); if name.accepted() { let varname = name?; - - println!("expr acc"); - if expect_tt!(self.peek(1)?, LeftParen).accepted() { - println!("func call acc"); let expr = self.parse_expression()?; // a function call expr let _ = expect_tt!(self.next()?, Semicolon)?; return ParseResult::Accept(Statement::Expression { expr }); @@ -339,7 +335,10 @@ impl Parser { let _ = expect_tt!(self.next()?, Semicolon); - return ParseResult::Accept(Statement::Assign { varname, value }); + return ParseResult::Accept(Statement::Assign { + varname: varname.name, + value, + }); } ParseResult::Reject(CompilerError::UnexpectedToken(self.peek_next()?)) @@ -432,7 +431,7 @@ impl Parser { ParseResult::Accept(Expression::StringLiteral(value)) } Token::Identifier(_) => { - let name = self.parse_identifier()?; + let name = expect_value!(self.next()?, Identifier)?; if matches!(self.peek_next()?, Token::LeftParen) { // Function call @@ -475,12 +474,15 @@ impl Parser { let type_id = self.parse_type()?; - ParseResult::Accept(Variable { name, type_id }) + ParseResult::Accept(Variable { + name: name.name, + type_id, + }) } fn parse_type(&mut self) -> ParseResult { // get the type name incl namespace - let typename = self.parse_identifier()?; + let typename = expect_value!(self.next()?, Identifier)?; match typename.name.as_str() { "u32" => ParseResult::Accept(TypeId::U32), @@ -496,27 +498,6 @@ impl Parser { } } - fn parse_identifier(&mut self) -> ParseResult { - let primary = expect_value!(self.next()?, Identifier)?; - - if expect_tt!(self.peek_next()?, Colon).accepted() { - let _ = expect_tt!(self.next()?, Colon)?; - let _ = expect_tt!(self.next()?, Colon)?; - - let secondary = expect_value!(self.next()?, Identifier)?; - - ParseResult::Accept(Name { - namespace: Some(primary), - name: secondary, - }) - } else { - ParseResult::Accept(Name { - namespace: None, - name: primary, - }) - } - } - fn next(&mut self) -> ParseResult { if self.idx >= self.tokens.len() { ParseResult::Reject(CompilerError::UnexpectedEndOfInput) @@ -571,12 +552,6 @@ pub struct Dependency { pub path: String, } -#[derive(Debug, Clone)] -pub struct Name { - pub name: String, - pub namespace: Option, -} - #[derive(Debug, Clone)] pub enum TypeId { U8, @@ -674,6 +649,23 @@ pub enum Expression { CharLiteral(char), } +impl Expression { + pub fn is_pure(&self) -> bool { + match self { + Expression::Number(_) => true, + Expression::StringLiteral(_) => true, + Expression::CharLiteral(_) => true, + Expression::Call { name, args } => false, /* TODO: will require checking */ + // if the associated function + // body is pure + Expression::Binary { left, right, .. } => left.is_pure() && right.is_pure(), + Expression::Unary { op, operand } => operand.is_pure(), + Expression::Empty => true, + Expression::Variable { name, expr_type } => true, + } + } +} + #[derive(Debug, Clone, PartialEq)] pub enum BinaryOperator { Add, diff --git a/compiler/src/registers.rs b/compiler/src/registers.rs index 910f065..e07949c 100644 --- a/compiler/src/registers.rs +++ b/compiler/src/registers.rs @@ -117,7 +117,13 @@ impl RegisterAllocator { // Load from bpr + offset (offset is negative) code.push(format!("\tsubi bpr {} {}", -(offset + 4), reg)); - code.push(format!("\tldw {}, {}", reg, reg)); + code.push(format!( + "\tldw {}, {} // bpr{}: {}", + reg, + reg, + offset - 4, + var_name + )); // Update location to register self.variable_locations @@ -164,43 +170,57 @@ impl RegisterAllocator { match location { Location::Register(dest_reg) => { if dest_reg != source_reg { - code.push(format!("\tmov {}, {}", source_reg, dest_reg)); + code.push(format!( + "\tmov {}, {} // var {}", + source_reg, dest_reg, var_name + )); } } Location::Stack(offset) => { - code.push(format!("\tstw {}, bpr, {}", source_reg, offset)); + code.push(format!( + "\tstw {}, bpr, {} // var {}", + source_reg, offset, var_name + )); } } } else { // Variable doesn't exist yet, we can just use the same reg. - self.variable_locations.insert( - var_name.to_string(), - Location::Register(source_reg.to_string()), - ); - self.register_contents - .insert(source_reg.to_string(), var_name.to_string()); - self.in_use.insert(source_reg.to_string(), true); + // self.variable_locations.insert( + // var_name.to_string(), + // Location::Register(source_reg.to_string()), + // ); + // self.register_contents + // .insert(source_reg.to_string(), var_name.to_string()); + // self.in_use.insert(source_reg.to_string(), true); - // this is not needed for now as if we're storing a var we already have a temp - // register allocated. - // if let Some(free_reg) = self.find_free_register() { - // if &free_reg != source_reg { - // code.push(format!("\tmov {}, {}", source_reg, free_reg)); - // } - // self.variable_locations - // .insert(var_name.to_string(), - // Location::Register(free_reg.clone())); - // self.register_contents - // .insert(free_reg.clone(), var_name.to_string()); - // self.in_use.insert(free_reg, true); - // } else { - // // No free registers - allocate on stack - // code.push(format!("\tstw {}, bpr, {}", source_reg, self.stack_offset)); - // self.variable_locations - // .insert(var_name.to_string(), Location::Stack(self.stack_offset)); - // self.stack_offset -= 4; // Move to next stack slot - // } + let source_reg = source_reg.to_string(); + + // if we can avoid a move, absolutely do that. + if self.available_registers.contains(&source_reg) { + self.variable_locations + .insert(var_name.to_string(), Location::Register(source_reg.clone())); + self.register_contents + .insert(source_reg.clone(), var_name.to_string()); + self.in_use.insert(source_reg, true); + } else if let Some(free_reg) = self.find_free_register() { + code.push(format!("\tmov {}, {}", source_reg, free_reg)); + self.variable_locations + .insert(var_name.to_string(), Location::Register(free_reg.clone())); + self.register_contents + .insert(free_reg.clone(), var_name.to_string()); + self.in_use.insert(free_reg, true); + } else { + // No free registers - allocate on stack + // code.push(format!("\tstw {}, bpr, {}", source_reg, self.stack_offset)); + // self.variable_locations + // .insert(var_name.to_string(), Location::Stack(self.stack_offset)); + // self.stack_offset -= 4; // Move to next stack slot + // + todo!( + "we should spill other registers and keep this variable on the stack as it's more recent!" + ); + } } code @@ -213,7 +233,10 @@ impl RegisterAllocator { if let Some(var_name) = self.register_contents.get(reg).cloned() { // PUSH register to stack (spr decrements automatically) - code.push(format!("\tpush {}", reg)); + code.push(format!( + "\tpush {} // bpr{}: {}", + reg, self.stack_offset, var_name + )); // Track that we pushed one word self.stack_offset -= 4; -- 2.47.3 From f4933b55fb90cc75e391e936b511c76b2d77500f Mon Sep 17 00:00:00 2001 From: zxq5 Date: Wed, 4 Feb 2026 01:57:18 +0000 Subject: [PATCH 18/53] forgot to commit this --- compiler/src/lib.rs | 74 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 compiler/src/lib.rs diff --git a/compiler/src/lib.rs b/compiler/src/lib.rs new file mode 100644 index 0000000..c94ab1a --- /dev/null +++ b/compiler/src/lib.rs @@ -0,0 +1,74 @@ +#![feature(try_trait_v2)] + +use std::path::Path; + +use common::logging::log; + +use crate::{ + codegen::CodeGenerator, + parser::{ParseResult, Parser}, + semantic_analyser::Analyser, +}; + +mod codegen; +mod lexer; +mod parser; +mod registers; +mod semantic_analyser; + +pub fn compile_file( + input_path: &Path, + output_path: &Path, +) -> Result<(), Box> { + let input = std::fs::read_to_string(input_path).expect("Failed to read input file"); + + log("Tokenising Input..."); + + let lexer = lexer::Lexer::new(&input); + let tokens = lexer.collect::>(); + // println!("{tokens:?}"); + + log(&format!("Parsing {} Tokens...", tokens.len())); + + let mut parser = Parser::new(tokens); + let ast = match parser.parse() { + ParseResult::Accept(ast) => ast, + ParseResult::Reject(e) => { + eprintln!("Error: {e:?}"); + return Err("Parsing error".into()); + } + ParseResult::Deny => { + panic!("Parser denied parsing") + } + }; + // println!("{ast:#?}"); + + log("Analyzing AST..."); + log("Checking Type Information..."); + + let analyser = Analyser::new(); + analyser.analyse(ast.clone()).unwrap(); + + log("Generating Code..."); + + // Code Gen + let mut generator = CodeGenerator::new(ast); + let result = match generator.generate() { + Ok(code) => code, + Err(e) => { + eprintln!("Parsing error: {:?}", e); + return Err("Code generation error".into()); + } + }; + + // println!("{result}"); + std::fs::write(output_path, &result).expect("Failed to write output"); + + log(&format!( + "Compilation Successful ✅ \n\tSource: {}\n\tOutput: {}\n", + input_path.display(), + output_path.display(), + )); + + Ok(()) +} -- 2.47.3 From dd20401ad6c9c0d0155dade2632bf9eb1128d392 Mon Sep 17 00:00:00 2001 From: zxq5 Date: Wed, 4 Feb 2026 01:57:40 +0000 Subject: [PATCH 19/53] added basic logging to common TODO: improve logging --- common/src/logging.rs | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 common/src/logging.rs diff --git a/common/src/logging.rs b/common/src/logging.rs new file mode 100644 index 0000000..e14cb68 --- /dev/null +++ b/common/src/logging.rs @@ -0,0 +1,4 @@ +// TODO: Use an actual logging or tracing library for pretty (scoped) output. +pub fn log(message: &str) { + println!("\x1b[32mINFO:\x1b[0m {message}"); +} -- 2.47.3 From 889ee8ef713f305c3bdbae94a74cbbba9cd59715 Mon Sep 17 00:00:00 2001 From: zxq5 Date: Wed, 4 Feb 2026 01:58:03 +0000 Subject: [PATCH 20/53] wrote dsa/dsc code examples including an allocator --- resources/dsa/build/bf.dsb | Bin 0 -> 4 bytes resources/dsa/build/test.dsb | Bin 0 -> 4 bytes resources/dsa/example.dsa | 121 +++++++++++++++++++++++ resources/dsa/example.dsc | 32 ++++++ resources/dsa/lib/memory/arena_alloc.dsa | 100 +++++++++++++++++++ resources/dsa/lib/memory/arena_alloc.dsc | 77 +++++++++++++++ resources/dsa/main.dsc | 9 ++ 7 files changed, 339 insertions(+) create mode 100644 resources/dsa/build/bf.dsb create mode 100644 resources/dsa/build/test.dsb create mode 100644 resources/dsa/example.dsa create mode 100644 resources/dsa/example.dsc create mode 100644 resources/dsa/lib/memory/arena_alloc.dsa create mode 100644 resources/dsa/lib/memory/arena_alloc.dsc create mode 100644 resources/dsa/main.dsc diff --git a/resources/dsa/build/bf.dsb b/resources/dsa/build/bf.dsb new file mode 100644 index 0000000000000000000000000000000000000000..2b831a14e46872a7943b9adcac710ba835e160b8 GIT binary patch literal 4 LcmXpjXJ7#U0!09_ literal 0 HcmV?d00001 diff --git a/resources/dsa/build/test.dsb b/resources/dsa/build/test.dsb new file mode 100644 index 0000000000000000000000000000000000000000..2b831a14e46872a7943b9adcac710ba835e160b8 GIT binary patch literal 4 LcmXpjXJ7#U0!09_ literal 0 HcmV?d00001 diff --git a/resources/dsa/example.dsa b/resources/dsa/example.dsa new file mode 100644 index 0000000..07913c1 --- /dev/null +++ b/resources/dsa/example.dsa @@ -0,0 +1,121 @@ + +// GENERATED BY DSC COMPILER +// Generated at 2026-02-04 01:55:11 + +// Imports +include arena: "./lib/memory/arena_alloc.dsa" +include print: "./lib/io/print.dsa" + +// Globals & Reserved Memory + + +// Entry Point +dw stack: 0x10000 +db message: "Process Exited with code:" +_init: + ldw stack, bpr + mov bpr, spr + push zero + call main + call print::print_newline + lwi message, rg0 + push rg0 + call print::print + pop zero + call print::print_hex_word + pop zero + hlt + + +// Return +_ret: + mov bpr, spr + pop bpr + return + +// Compiled Code Starts... +main: + push bpr + mov spr, bpr + + lli 0, rg0 + push rg0 // bpr-4: x + subi bpr 4 rg1 + lli 512, rg0 + push rg1 // bpr-8: y + push rg0 // push arg 0 + call arena::new + pop rg2 + lli 32, rg0 + push rg2 // bpr-12: alloc + push rg0 // push arg 1 + push rg2 // push arg 0 + call arena::alloc + pop rg3 + pop zero + lli 32, rg0 + subi bpr 12 rg2 + ldw rg2, rg2 // bpr-20: alloc + push rg2 // bpr-16: alloc + push rg3 // bpr-20: ptr1 + push rg0 // push arg 1 + push rg2 // push arg 0 + call arena::alloc + pop rg4 + pop zero + subi bpr 16 rg0 + ldw rg0, rg0 // bpr-24: alloc + push rg0 // bpr-24: alloc + push rg4 // bpr-28: ptr2 + push rg0 // push arg 0 + call print::print_hex_word + pop zero + call print::print_newline + subi bpr 20 rg0 + ldw rg0, rg0 // bpr-28: ptr1 + push rg0 // bpr-32: ptr1 + push rg0 // push arg 0 + call print::print_hex_word + pop zero + call print::print_newline + subi bpr 28 rg0 + ldw rg0, rg0 // bpr-36: ptr2 + push rg0 // bpr-36: ptr2 + push rg0 // push arg 0 + call print::print_hex_word + pop zero + call print::print_newline + subi bpr 36 rg0 + ldw rg0, rg0 // bpr-44: ptr2 + ldw rg0, rg2 + push rg0 // bpr-40: ptr2 + push rg2 // push arg 0 + call print::print_num + pop zero + call print::print_newline + lli 42, rg2 + subi bpr 40 rg5 + ldw rg5, rg5 // bpr-48: ptr2 + stw rg2, rg5 + push rg5 // bpr-44: ptr2 + push rg5 // push arg 0 + call print::print_hex_word + pop zero + call print::print_newline + subi bpr 44 rg2 + ldw rg2, rg2 // bpr-52: ptr2 + ldw rg2, rg5 + push rg2 // bpr-48: ptr2 + push rg5 // push arg 0 + call print::print_num + pop zero + call print::print_newline + db str_12: "end" + lwi str_12, rg5 + push rg5 // push arg 0 + call print::println + pop zero + lli 0, rg5 + stw rg5, bpr, 8 + jmp _ret + diff --git a/resources/dsa/example.dsc b/resources/dsa/example.dsc new file mode 100644 index 0000000..0b49b0c --- /dev/null +++ b/resources/dsa/example.dsc @@ -0,0 +1,32 @@ +include print: "./lib/io/print.dsa"; +include arena: "./lib/memory/arena_alloc.dsa"; + +fn main() -> u32 { + + let x: u32 = 0; + let y: u32 = &x; + + let alloc: u32 = arena::new(512); + let ptr1: u32 = arena::alloc(alloc, 32); + let ptr2: u32 = arena::alloc(alloc, 32); + + print::print_hex_word(alloc); + print::print_newline(); + print::print_hex_word(ptr1); + print::print_newline(); + print::print_hex_word(ptr2); + print::print_newline(); + print::print_num(*ptr2); + print::print_newline(); + *ptr2 = 42; + + print::print_hex_word(ptr2); + print::print_newline(); + print::print_num(*ptr2); + print::print_newline(); + print::println("end"); + + return 0; +} + + diff --git a/resources/dsa/lib/memory/arena_alloc.dsa b/resources/dsa/lib/memory/arena_alloc.dsa new file mode 100644 index 0000000..31bd892 --- /dev/null +++ b/resources/dsa/lib/memory/arena_alloc.dsa @@ -0,0 +1,100 @@ +dw heap_start: 196608 +dw heap_end: 262144 +dw heap_current: 196608 + +new: + push bpr + mov spr, bpr + + ldw bpr, rg0, 8 + lli 12, rg1 + add rg0, rg1, rg2 + ldw heap_current, rg1 + add rg1, rg2, rg3 + ldw heap_end, rg4 + cmp rg3, rg4 + lli 0, rg5 + jle _cmp_end_2 + lli 1, rg5 +_cmp_end_2: + cmp rg5, zero + jeq _else_4 +_then_3: + lli 0, rg4 + stw rg4, bpr, 8 + jmp _ret + jmp _end_5 +_else_4: + nop +_end_5: + lli 12, rg4 + add rg1, rg4, rg5 + add rg1, rg2, rg4 + stw rg5, rg1 + lli 4, rg6 + add rg1, rg6, rg7 + stw rg5, rg7 + lli 8, rg6 + add rg1, rg6, rg7 + stw rg4, rg7 + stw rg3, heap_current + stw rg1, bpr, 8 + jmp _ret + +alloc: + push bpr + mov spr, bpr + + ldw bpr, rg0, 8 + ldw bpr, rg1, 12 + lli 4, rg2 + add rg0, rg2, rg3 + ldw rg3, rg2 + lli 8, rg3 + add rg0, rg3, rg4 + ldw rg4, rg3 + add rg2, rg1, rg4 + cmp rg4, rg3 + lli 0, rg5 + jle _cmp_end_6 + lli 1, rg5 +_cmp_end_6: + cmp rg5, zero + jeq _else_8 +_then_7: + lli 0, rg5 + stw rg5, bpr, 8 + jmp _ret + jmp _end_9 +_else_8: + nop +_end_9: + lli 4, rg5 + add rg0, rg5, rg6 + stw rg4, rg6 + stw rg2, bpr, 8 + jmp _ret + +destroy: + push bpr + mov spr, bpr + + ldw bpr, rg0, 8 + lli 0, rg1 + stw rg1, bpr, 8 + jmp _ret + +reset_all: + push bpr + mov spr, bpr + + ldw heap_start, rg0 + stw rg0, heap_current + lli 0, rg0 + stw rg0, bpr, 8 + jmp _ret + +_ret: + mov bpr, spr + pop bpr + return diff --git a/resources/dsa/lib/memory/arena_alloc.dsc b/resources/dsa/lib/memory/arena_alloc.dsc new file mode 100644 index 0000000..c84415f --- /dev/null +++ b/resources/dsa/lib/memory/arena_alloc.dsc @@ -0,0 +1,77 @@ +// Arena Allocator +// Supports multiple arenas that can be destroyed independently +// Much more practical than a simple bump allocator + +// Global heap management +static heap_start: u32 = 0x30000; +static heap_end: u32 = 0x40000; +static heap_current: u32 = 0x30000; + +// Arena structure (stored at the start of each arena): +// [0-3]: start_address (u32) +// [4-7]: current_position (u32) +// [8-11]: end_address (u32) +// Total header size: 12 bytes + +// Create a new arena with given size +// Returns pointer to arena handle (or 0 if failed) +fn arena_create(size: u32) -> u32 { + let total_size: u32 = size + 12; + let arena_ptr: u32 = heap_current; + let new_current: u32 = arena_ptr + total_size; + + // Check if we have space + if new_current > heap_end { + return 0; + } + + // Calculate arena data region + let data_start: u32 = arena_ptr + 12; + let data_end: u32 = arena_ptr + total_size; + + // Initialize arena header + // Note: In real implementation, you'd use pointer writes here + // For now, using placeholder comments: + *arena_ptr = data_start; // start_address + *(arena_ptr + 4) = data_start; // current_position + *(arena_ptr + 8) = data_end; // end_address + + heap_current = new_current; + + return arena_ptr; +} + +// Allocate from an arena +// Returns pointer to allocated memory (or 0 if failed) +fn arena_alloc(arena: u32, size: u32) -> u32 { + // Read current position from arena + let current: u32 = *(arena + 4); + let end: u32 = *(arena + 8); + + let new_current: u32 = current + size; + + // Check if arena has space + if new_current > end { + return 0; + } + + // Update current position in arena + *(arena + 4) = new_current; + + return current; +} + +// Destroy an arena (in bump allocator, this is a no-op) +// In a real allocator, you'd mark the memory as free +fn arena_destroy(arena: u32) { + // In a true allocator, mark memory as reusable + // For bump allocator, we can't reclaim memory + // unless we destroy ALL arenas and reset + return 0; +} + +// Reset entire heap (destroys ALL arenas) +fn reset_all() { + heap_current = heap_start; + return 0; +} diff --git a/resources/dsa/main.dsc b/resources/dsa/main.dsc new file mode 100644 index 0000000..0e0b119 --- /dev/null +++ b/resources/dsa/main.dsc @@ -0,0 +1,9 @@ +include print: "./lib/io/print.dsa"; +include fib: "./lib/maths/fib.dsa"; + +fn main() -> u32 { + let x: u32 = 6; + + let y: u32 = fib::fib_n(x); + print::print_num(y); +} \ No newline at end of file -- 2.47.3 From 7780f5804f5be2cc20483dfd6f831d3a89fcf341 Mon Sep 17 00:00:00 2001 From: zxq5 Date: Wed, 4 Feb 2026 01:58:37 +0000 Subject: [PATCH 21/53] deleted old files and modified some dsa source files --- resources/dsa/lib/error/handlers.dsa | 4 +- resources/dsa/lib/maths/fib.dsa | 17 +-- resources/dsa/lib/memory/alloc.dsa | 37 ----- resources/dsa/main.dsa | 82 +++++------ resources/dsa/output.dsa | 197 +++++++++++++++++++++++---- resources/dsb/bf.dsb | Bin 4 -> 0 bytes resources/dsb/test.dsb | Bin 4 -> 0 bytes resources/dsc/example.dsc | 110 --------------- 8 files changed, 223 insertions(+), 224 deletions(-) delete mode 100644 resources/dsa/lib/memory/alloc.dsa delete mode 100644 resources/dsb/bf.dsb delete mode 100644 resources/dsb/test.dsb delete mode 100644 resources/dsc/example.dsc diff --git a/resources/dsa/lib/error/handlers.dsa b/resources/dsa/lib/error/handlers.dsa index 418cef2..931ecde 100644 --- a/resources/dsa/lib/error/handlers.dsa +++ b/resources/dsa/lib/error/handlers.dsa @@ -11,7 +11,7 @@ setup_idt: mov bpr, spr pop bpr - irt + return setup_hard_fault_handler: push bpr @@ -22,7 +22,7 @@ setup_hard_fault_handler: mov bpr, spr pop bpr - irt + return dw hard_fault_err: "FATAL: Illegal Instruction or Memory Access!" handle_hard_fault: diff --git a/resources/dsa/lib/maths/fib.dsa b/resources/dsa/lib/maths/fib.dsa index c6be26f..a73476e 100644 --- a/resources/dsa/lib/maths/fib.dsa +++ b/resources/dsa/lib/maths/fib.dsa @@ -5,27 +5,20 @@ fib_n: mov spr, bpr ldw bpr, rg0, 8 // load arg - mov rg1, rg2 - lwi 1, rg1 + lwi 0, rg1 + lwi 1, rg2 -start: +_start: add rg1, rg2, rg3 - pusha 4 - push rg1 - call print::print_hex_byte - call print::print_newline - pop zero - popa 4 - mov rg2, rg1 mov rg3, rg2 dec rg0 cmp rg0, zero - jgt start + jgt _start - stw rg1, bpr, 8 + stw rg3, bpr, 8 mov bpr, spr pop bpr return diff --git a/resources/dsa/lib/memory/alloc.dsa b/resources/dsa/lib/memory/alloc.dsa deleted file mode 100644 index 29a0243..0000000 --- a/resources/dsa/lib/memory/alloc.dsa +++ /dev/null @@ -1,37 +0,0 @@ -dw global_arena_start: 0x30000 -dw global_arena_current: 0x30000 -dw global_arena_end: 0x40000 - -arena_alloc: - // Just like bump allocator - push bpr - mov spr, bpr - - ldw bpr, rg0, 8 // size argument - ldw global_arena_current, rg1 - - add rg1, rg0, rg2 // new_current = current + size - ldw global_arena_end, rg3 - - cmp rg2, rg3 - jgt out_of_memory - - stw rg2, global_arena_current - mov rg1, acc // return old current - stw acc, bpr, 8 - - mov bpr, spr - pop bpr - return - -arena_reset: - // Reset to start - push bpr - mov spr, bpr - - ldw global_arena_start, rg0 - stw rg0, global_arena_current - - mov bpr, spr - pop bpr - return diff --git a/resources/dsa/main.dsa b/resources/dsa/main.dsa index 0f8daa2..2029c7a 100644 --- a/resources/dsa/main.dsa +++ b/resources/dsa/main.dsa @@ -1,45 +1,51 @@ -include fib: "./lib/maths/fib.dsa" -include maths: "./lib/maths/core.dsa" -include print: "./lib/io/print.dsa" -dw idt: 0xFFFF0000 +// GENERATED BY DSC COMPILER +// Generated at 2026-02-04 01:44:06 + +// Imports +include print: "./lib/io/print.dsa" +include fib: "./lib/maths/fib.dsa" + +// Globals & Reserved Memory + + +// Entry Point dw stack: 0x10000 -init: - // setup interrupt handlers - ldw idt, idr - lwi handle_hard_fault, rg0 - stw rg0, idr, 4 - // set up a stack. +db message: "Process Exited with code:" +_init: ldw stack, bpr mov bpr, spr - -dw string: "hello world" -start: - lwi 100, rg0 - lwi 10, rg1 - - push rg1 - push rg0 - call maths::new_divide - pop rg0 - pop rg1 - hlt - - pop rg0 - pop zero - push rg0 - call print::print_num - pop zero - - hlt - -// fault handler in case we fail DSA. -dw hard_fault_err: "FATAL: Illegal Instruction or Memory Access!" -handle_hard_fault: - call print::clear - call print::reset - lwi hard_fault_err, rg0 + push zero + call main + call print::print_newline + lwi message, rg0 push rg0 call print::print pop zero - hlt \ No newline at end of file + call print::print_hex_word + pop zero + hlt + + +// Return +_ret: + mov bpr, spr + pop bpr + return + +// Compiled Code Starts... +main: + push bpr + mov spr, bpr + + lli 6, rg0 + push rg0 // bpr-4: x + push rg0 // push arg 0 + call fib::fib_n + pop rg1 + push rg1 // bpr-8: y + push rg1 // push arg 0 + call print::print_num + pop zero + jmp _ret + diff --git a/resources/dsa/output.dsa b/resources/dsa/output.dsa index 23b3407..0b7fc2e 100644 --- a/resources/dsa/output.dsa +++ b/resources/dsa/output.dsa @@ -1,12 +1,14 @@ // GENERATED BY DSC COMPILER -// Generated at 2026-02-03 02:08:02 +// Generated at 2026-02-03 23:37:16 // Imports include print: "./lib/io/print.dsa" // Globals & Reserved Memory - +dw heap_start: 196608 +dw heap_end: 262144 +dw heap_current: 196608 // Entry Point dw stack: 0x10000 @@ -37,31 +39,176 @@ main: push bpr mov spr, bpr - lli 5, rg0 - db str_1: "Hello world" - lwi str_1, rg1 - db str_2: "test" - lwi str_2, rg2 - push rg0 - push rg1 - push rg2 - db str_3: "hello world 2 electric boogaloo" - lwi str_3, rg3 - push rg3 - call print::println - pop zero + lli 0, rg0 + push rg0 // bpr-4: x + subi bpr 4 rg1 + lli 512, rg0 + push rg1 // bpr-8: y + push rg0 // push arg 0 + call arena_create pop rg2 - pop rg1 - pop rg0 - push rg0 - push rg1 - push rg2 - lli 213, rg3 - push rg3 + lli 32, rg0 + push rg2 // bpr-12: alloc + push rg0 // push arg 1 + push rg2 // push arg 0 + call arena_alloc + pop rg3 + pop zero + lli 32, rg0 + subi bpr 12 rg2 + ldw rg2, rg2 // bpr-20: alloc + push rg3 // bpr-16: ptr1 + push rg2 // bpr-20: alloc + push rg0 // push arg 1 + push rg2 // push arg 0 + call arena_alloc + pop rg4 + pop zero + subi bpr 20 rg0 + ldw rg0, rg0 // bpr-28: alloc + push rg4 // bpr-24: ptr2 + push rg0 // bpr-28: alloc + push rg0 // push arg 0 + call print::print_hex_word + pop zero + call print::print_newline + subi bpr 16 rg0 + ldw rg0, rg0 // bpr-24: ptr1 + push rg0 // bpr-32: ptr1 + push rg0 // push arg 0 + call print::print_hex_word + pop zero + call print::print_newline + subi bpr 24 rg0 + ldw rg0, rg0 // bpr-32: ptr2 + push rg0 // bpr-36: ptr2 + push rg0 // push arg 0 + call print::print_hex_word + pop zero + call print::print_newline + subi bpr 36 rg0 + ldw rg0, rg0 // bpr-44: ptr2 + ldw rg0, rg2 + push rg0 // bpr-40: ptr2 + push rg2 // push arg 0 call print::print_num pop zero - pop rg2 - pop rg1 - pop rg0 + call print::print_newline + lli 42, rg2 + subi bpr 40 rg5 + ldw rg5, rg5 // bpr-48: ptr2 + stw rg2, rg5 + push rg5 // bpr-44: ptr2 + push rg5 // push arg 0 + call print::print_hex_word + pop zero + call print::print_newline + subi bpr 44 rg2 + ldw rg2, rg2 // bpr-52: ptr2 + ldw rg2, rg5 + push rg2 // bpr-48: ptr2 + push rg5 // push arg 0 + call print::print_num + pop zero + call print::print_newline + db str_1: "end" + lwi str_1, rg5 + push rg5 // push arg 0 + call print::println + pop zero + lli 0, rg5 + stw rg5, bpr, 8 + jmp _ret + +arena_create: + push bpr + mov spr, bpr + + ldw bpr, rg0, 8 + lli 12, rg1 + add rg0, rg1, rg2 + ldw heap_current, rg1 + add rg1, rg2, rg3 + ldw heap_end, rg4 + cmp rg3, rg4 + lli 0, rg5 + jle _cmp_end_2 + lli 1, rg5 +_cmp_end_2: + cmp rg5, zero + jeq _else_4 +_then_3: + lli 0, rg4 + stw rg4, bpr, 8 + jmp _ret + jmp _end_5 +_else_4: + nop +_end_5: + lli 12, rg4 + add rg1, rg4, rg5 + add rg1, rg2, rg4 + stw rg5, rg1 + lli 4, rg6 + add rg1, rg6, rg7 + stw rg5, rg7 + lli 8, rg6 + add rg1, rg6, rg7 + stw rg4, rg7 + stw rg3, heap_current + stw rg1, bpr, 8 + jmp _ret + +arena_alloc: + push bpr + mov spr, bpr + + ldw bpr, rg0, 8 + ldw bpr, rg1, 12 + lli 4, rg2 + add rg0, rg2, rg3 + ldw rg3, rg2 + lli 8, rg3 + add rg0, rg3, rg4 + ldw rg4, rg3 + add rg2, rg1, rg4 + cmp rg4, rg3 + lli 0, rg5 + jle _cmp_end_6 + lli 1, rg5 +_cmp_end_6: + cmp rg5, zero + jeq _else_8 +_then_7: + lli 0, rg5 + stw rg5, bpr, 8 + jmp _ret + jmp _end_9 +_else_8: + nop +_end_9: + lli 4, rg5 + add rg0, rg5, rg6 + stw rg4, rg6 + stw rg2, bpr, 8 + jmp _ret + +arena_destroy: + push bpr + mov spr, bpr + + ldw bpr, rg0, 8 + lli 0, rg1 + stw rg1, bpr, 8 + jmp _ret + +reset_all: + push bpr + mov spr, bpr + + ldw heap_start, rg0 + stw rg0, heap_current + lli 0, rg0 + stw rg0, bpr, 8 jmp _ret diff --git a/resources/dsb/bf.dsb b/resources/dsb/bf.dsb deleted file mode 100644 index 2b831a14e46872a7943b9adcac710ba835e160b8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4 LcmXpjXJ7#U0!09_ diff --git a/resources/dsb/test.dsb b/resources/dsb/test.dsb deleted file mode 100644 index 2b831a14e46872a7943b9adcac710ba835e160b8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4 LcmXpjXJ7#U0!09_ diff --git a/resources/dsc/example.dsc b/resources/dsc/example.dsc deleted file mode 100644 index 6a7738f..0000000 --- a/resources/dsc/example.dsc +++ /dev/null @@ -1,110 +0,0 @@ -fn main() -> u32 { - - let x: u32 = 0; - let y: u32 = &x; - - let alloc: u32 = arena_create(512); - let ptr1: u32 = arena_alloc(alloc, 32); - let ptr2: u32 = arena_alloc(alloc, 32); - - print_hex(alloc); - print_newline(); - print_hex(ptr1); - print_newline(); - print_hex(ptr2); - print_newline(); - printnum(*ptr2); - print_newline(); - *ptr2 = 42; - - print_hex(ptr2); - print_newline(); - printnum(*ptr2); - print_newline(); - println("end"); - - return 0; -} - - - - - - -// Arena Allocator -// Supports multiple arenas that can be destroyed independently -// Much more practical than a simple bump allocator - -// Global heap management -static heap_start: u32 = 0x30000; -static heap_end: u32 = 0x40000; -static heap_current: u32 = 0x30000; - -// Arena structure (stored at the start of each arena): -// [0-3]: start_address (u32) -// [4-7]: current_position (u32) -// [8-11]: end_address (u32) -// Total header size: 12 bytes - -// Create a new arena with given size -// Returns pointer to arena handle (or 0 if failed) -fn arena_create(size: u32) -> u32 { - let total_size: u32 = size + 12; - let arena_ptr: u32 = heap_current; - let new_current: u32 = arena_ptr + total_size; - - // Check if we have space - if new_current > heap_end { - return 0; - } - - // Calculate arena data region - let data_start: u32 = arena_ptr + 12; - let data_end: u32 = arena_ptr + total_size; - - // Initialize arena header - // Note: In real implementation, you'd use pointer writes here - // For now, using placeholder comments: - *arena_ptr = data_start; // start_address - *(arena_ptr + 4) = data_start; // current_position - *(arena_ptr + 8) = data_end; // end_address - - heap_current = new_current; - - return arena_ptr; -} - -// Allocate from an arena -// Returns pointer to allocated memory (or 0 if failed) -fn arena_alloc(arena: u32, size: u32) -> u32 { - // Read current position from arena - let current: u32 = *(arena + 4); - let end: u32 = *(arena + 8); - - let new_current: u32 = current + size; - - // Check if arena has space - if new_current > end { - return 0; - } - - // Update current position in arena - *(arena + 4) = new_current; - - return current; -} - -// Destroy an arena (in bump allocator, this is a no-op) -// In a real allocator, you'd mark the memory as free -fn arena_destroy(arena: u32) { - // In a true allocator, mark memory as reusable - // For bump allocator, we can't reclaim memory - // unless we destroy ALL arenas and reset - return 0; -} - -// Reset entire heap (destroys ALL arenas) -fn reset_all() { - heap_current = heap_start; - return 0; -} -- 2.47.3 From fa8aa1cd290c37917113bc87934b1e916eef97f4 Mon Sep 17 00:00:00 2001 From: zxq5 Date: Wed, 4 Feb 2026 01:58:55 +0000 Subject: [PATCH 22/53] integrated compiler in DSA editor --- emulator/Cargo.toml | 1 + emulator/src/emulator/ui/editor.rs | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/emulator/Cargo.toml b/emulator/Cargo.toml index 23f7142..95df15a 100644 --- a/emulator/Cargo.toml +++ b/emulator/Cargo.toml @@ -16,6 +16,7 @@ required-features = ["config"] [dependencies] common = { path = "../common" } assembler = { path = "../assembler" } +compiler = { path = "../compiler" } dsa_editor = { path = "../dsa_editor" } egui = "0.31.1" dirs = "6.0.0" diff --git a/emulator/src/emulator/ui/editor.rs b/emulator/src/emulator/ui/editor.rs index e49ad85..e1d4b4b 100644 --- a/emulator/src/emulator/ui/editor.rs +++ b/emulator/src/emulator/ui/editor.rs @@ -451,6 +451,29 @@ impl Editor { .flat_map(|i| i.encode().to_be_bytes().to_vec()) .collect(); } + Some("dsc") => { + let output_path = Path::new(path).with_extension("dsa"); + if let Err(e) = compiler::compile_file(path, &output_path) { + self.error = Some(format!("Compiler error: {}", e)); + } + + let mut compiler = CompilerEngine::new(); + compiler.start_compilation(&output_path); + + // Or block until done + let instructions = match compiler.wait_for_result() { + Ok(instructions) => instructions, + Err(e) => { + self.error = Some(format!("Assembler error: {}", e)); + return; + } + }; + + self.output = instructions + .iter() + .flat_map(|i| i.encode().to_be_bytes().to_vec()) + .collect(); + } Some("dsb") => { if let Ok(bytes) = fs::read(path) { self.output = bytes; -- 2.47.3 From cb65a928c88f817a39d3f02b0eda416383efc530 Mon Sep 17 00:00:00 2001 From: zxq5 Date: Wed, 4 Feb 2026 01:59:43 +0000 Subject: [PATCH 23/53] fixed bug where stack inspector shows incorrect addresses --- emulator/src/emulator/ui/stack_inspector.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/emulator/src/emulator/ui/stack_inspector.rs b/emulator/src/emulator/ui/stack_inspector.rs index b89f4f0..557538a 100644 --- a/emulator/src/emulator/ui/stack_inspector.rs +++ b/emulator/src/emulator/ui/stack_inspector.rs @@ -51,7 +51,6 @@ impl Component for StackInspector { ui.label("Address"); ui.label("Value"); ui.end_row(); - for (i, value) in state.stack_view.chunks(4).take(32).enumerate() { @@ -59,9 +58,9 @@ impl Component for StackInspector { "Could not read 4 byte instruction or data! Something is wrong.", )); ui.label(format!( - "{} [{}]", - i, - state.reg_file.get(Register::Spr).expect("SPR should never be invalid") - i as u32 * 4 + "+{} [{}]", + i*4, + state.reg_file.get(Register::Spr).expect("SPR should never be invalid") + i as u32 * 4 )); ui.label(format!("0x{value:08X} ({value})")); ui.end_row(); -- 2.47.3 From a1099249e9801e0c4d3218be8d84809391576c7f Mon Sep 17 00:00:00 2001 From: zxq5 Date: Wed, 4 Feb 2026 01:59:50 +0000 Subject: [PATCH 24/53] updated roadmap --- resources/ideas/DSA_Project_Roadmap.md | 30 ++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/resources/ideas/DSA_Project_Roadmap.md b/resources/ideas/DSA_Project_Roadmap.md index a9604c4..9ad612a 100644 --- a/resources/ideas/DSA_Project_Roadmap.md +++ b/resources/ideas/DSA_Project_Roadmap.md @@ -268,6 +268,8 @@ - [ ] Error recovery mechanisms - [ ] Comprehensive parser tests - [ ] Syntax error message quality testing +- [ ] Implement C frontend by moving lexer/parser from `c_compiler` to the new `compiler` project structure +- [ ] Evaluate possible memory management strategies (e.g., keep all variables on the stack vs spill only when calling functions) --- @@ -530,6 +532,20 @@ #### 4.1.3 Symbol Table Loader in Emulator +### Pre-Debugger Editor Integration Tasks + +- **Integrate compiler into editor** + - Add a build command that invokes the full compiler pipeline (lexer → parser → codegen). + - Show compilation output and errors in the console panel. + +- **DSC language support** + - Enable syntax highlighting and auto‑completion for DSC files within the editor. + - Provide a dedicated “Build DSC” command that uses the integrated compiler. + +- **Editor diagnostics** + - Wire compiler error messages to the editor’s gutter so users can click to jump to source lines. + + **Estimate: 2 days** **Dependencies:** 4.1.2 **Deliverable:** Symbol loading in emulator crate @@ -679,17 +695,19 @@ --- -#### 4.3.4 Integrate Build Tools in Editor +#### 4.3.4 Integrate Build Tools and Compiler into Editor -**Estimate: 1 day** -**Dependencies:** 4.3.1, 3.1.2 -**Deliverable:** Integrated build experience +Estimate: 1 day +Dependencies: 4.3.1, 3.1.2, 2.1.2 +Deliverable: Integrated build experience with compiler support -- [ ] Build button/command in UI -- [ ] Show build output in console panel +- [ ] Build button/command in UI that invokes the full compiler pipeline +- [ ] Show build output and compilation errors in console panel - [ ] Error navigation (click to jump to source) - [ ] Hot reload on successful build - [ ] Build status indicator +- [ ] Hook DSC language support into editor for syntax highlighting and auto‑completion +- [ ] Provide dedicated DSC build command that uses the new compiler integration --- -- 2.47.3 From 8d130a870cd80ea051dcc9509546025c303b6bd7 Mon Sep 17 00:00:00 2001 From: zxq5 Date: Thu, 5 Feb 2026 01:07:59 +0000 Subject: [PATCH 25/53] deleted the c compiler --- c_compiler/Cargo.toml | 8 - c_compiler/code.c | 14 - c_compiler/compiler.py | 926 ------------------------------------ c_compiler/example.c | 12 - c_compiler/example.dsc | 25 - c_compiler/output.dsa | 5 - c_compiler/src/assembly.rs | 106 ----- c_compiler/src/codegen.rs | 599 ----------------------- c_compiler/src/lexer.rs | 335 ------------- c_compiler/src/main.rs | 74 --- c_compiler/src/parser.rs | 610 ------------------------ c_compiler/src/registers.rs | 344 -------------- compiler/src/codegen.rs | 756 ----------------------------- compiler/src/lexer.rs | 627 ------------------------ 14 files changed, 4441 deletions(-) delete mode 100644 c_compiler/Cargo.toml delete mode 100644 c_compiler/code.c delete mode 100644 c_compiler/compiler.py delete mode 100644 c_compiler/example.c delete mode 100644 c_compiler/example.dsc delete mode 100644 c_compiler/output.dsa delete mode 100644 c_compiler/src/assembly.rs delete mode 100644 c_compiler/src/codegen.rs delete mode 100644 c_compiler/src/lexer.rs delete mode 100644 c_compiler/src/main.rs delete mode 100644 c_compiler/src/parser.rs delete mode 100644 c_compiler/src/registers.rs delete mode 100644 compiler/src/codegen.rs delete mode 100644 compiler/src/lexer.rs diff --git a/c_compiler/Cargo.toml b/c_compiler/Cargo.toml deleted file mode 100644 index ef733ea..0000000 --- a/c_compiler/Cargo.toml +++ /dev/null @@ -1,8 +0,0 @@ -[package] -name = "c_compiler" -version.workspace = true -edition.workspace = true -authors.workspace = true - -[dependencies] -chrono = "0.4.42" diff --git a/c_compiler/code.c b/c_compiler/code.c deleted file mode 100644 index d069fa2..0000000 --- a/c_compiler/code.c +++ /dev/null @@ -1,14 +0,0 @@ -int var_x = 5; - -int factorial(int n) { - if (n <= 1) { - return 1; - } - return n * factorial(n - 1); -} - -int main() { - int result = var_x + factorial(5); - print(result); - return 0; -} diff --git a/c_compiler/compiler.py b/c_compiler/compiler.py deleted file mode 100644 index 627278c..0000000 --- a/c_compiler/compiler.py +++ /dev/null @@ -1,926 +0,0 @@ -#!/usr/bin/env python3 -""" -Simple C to DSA Assembly Compiler -Supports a subset of C including: -- int variables and functions -- Arithmetic operations (+, -, *, /) -- Comparisons (==, !=, <, >, <=, >=) -- If/else statements -- While loops -- Function calls -- Return statements -""" - -import re -import sys -from typing import List, Dict, Optional, Tuple -from dataclasses import dataclass -from enum import Enum -from pprint import pprint -import json - - -class TokenType(Enum): - # Keywords - INT = "int" - IF = "if" - ELSE = "else" - WHILE = "while" - RETURN = "return" - - # Identifiers and literals - IDENTIFIER = "IDENTIFIER" - NUMBER = "NUMBER" - - # Operators - PLUS = "+" - MINUS = "-" - STAR = "*" - SLASH = "/" - ASSIGN = "=" - EQ = "==" - NE = "!=" - LT = "<" - GT = ">" - LE = "<=" - GE = ">=" - - # Delimiters - LPAREN = "(" - RPAREN = ")" - LBRACE = "{" - RBRACE = "}" - SEMICOLON = ";" - COMMA = "," - - EOF = "EOF" - - -@dataclass -class Token: - type: TokenType - value: str - line: int - col: int - - -class Lexer: - def __init__(self, source: str): - self.source = source - self.pos = 0 - self.line = 1 - self.col = 1 - self.tokens = [] - - def error(self, msg: str): - raise SyntaxError(f"Lexer error at line {self.line}, col {self.col}: {msg}") - - def peek(self, offset: int = 0) -> Optional[str]: - pos = self.pos + offset - return self.source[pos] if pos < len(self.source) else None - - def advance(self) -> Optional[str]: - if self.pos >= len(self.source): - return None - char = self.source[self.pos] - self.pos += 1 - if char == "\n": - self.line += 1 - self.col = 1 - else: - self.col += 1 - return char - - def skip_whitespace(self): - while self.peek() and self.peek() in " \t\n\r": - self.advance() - - def skip_comment(self): - if self.peek() == "/" and self.peek(1) == "/": - while self.peek() and self.peek() != "\n": - self.advance() - self.advance() # skip newline - - def read_number(self) -> str: - num = "" - while self.peek() and self.peek().isdigit(): - num += self.advance() - return num - - def read_identifier(self) -> str: - ident = "" - while self.peek() and (self.peek().isalnum() or self.peek() == "_"): - ident += self.advance() - return ident - - def tokenize(self) -> List[Token]: - keywords = { - "int": TokenType.INT, - "if": TokenType.IF, - "else": TokenType.ELSE, - "while": TokenType.WHILE, - "return": TokenType.RETURN, - } - - while self.pos < len(self.source): - self.skip_whitespace() - self.skip_comment() - - if self.pos >= len(self.source): - break - - line, col = self.line, self.col - char = self.peek() - - # Numbers - if char.isdigit(): - num = self.read_number() - self.tokens.append(Token(TokenType.NUMBER, num, line, col)) - - # Identifiers and keywords - elif char.isalpha() or char == "_": - ident = self.read_identifier() - token_type = keywords.get(ident, TokenType.IDENTIFIER) - self.tokens.append(Token(token_type, ident, line, col)) - - # Two-character operators - elif char == "=" and self.peek(1) == "=": - self.advance() - self.advance() - self.tokens.append(Token(TokenType.EQ, "==", line, col)) - elif char == "!" and self.peek(1) == "=": - self.advance() - self.advance() - self.tokens.append(Token(TokenType.NE, "!=", line, col)) - elif char == "<" and self.peek(1) == "=": - self.advance() - self.advance() - self.tokens.append(Token(TokenType.LE, "<=", line, col)) - elif char == ">" and self.peek(1) == "=": - self.advance() - self.advance() - self.tokens.append(Token(TokenType.GE, ">=", line, col)) - - # Single-character operators - elif char == "+": - self.advance() - self.tokens.append(Token(TokenType.PLUS, "+", line, col)) - elif char == "-": - self.advance() - self.tokens.append(Token(TokenType.MINUS, "-", line, col)) - elif char == "*": - self.advance() - self.tokens.append(Token(TokenType.STAR, "*", line, col)) - elif char == "/": - self.advance() - self.tokens.append(Token(TokenType.SLASH, "/", line, col)) - elif char == "=": - self.advance() - self.tokens.append(Token(TokenType.ASSIGN, "=", line, col)) - elif char == "<": - self.advance() - self.tokens.append(Token(TokenType.LT, "<", line, col)) - elif char == ">": - self.advance() - self.tokens.append(Token(TokenType.GT, ">", line, col)) - elif char == "(": - self.advance() - self.tokens.append(Token(TokenType.LPAREN, "(", line, col)) - elif char == ")": - self.advance() - self.tokens.append(Token(TokenType.RPAREN, ")", line, col)) - elif char == "{": - self.advance() - self.tokens.append(Token(TokenType.LBRACE, "{", line, col)) - elif char == "}": - self.advance() - self.tokens.append(Token(TokenType.RBRACE, "}", line, col)) - elif char == ";": - self.advance() - self.tokens.append(Token(TokenType.SEMICOLON, ";", line, col)) - elif char == ",": - self.advance() - self.tokens.append(Token(TokenType.COMMA, ",", line, col)) - else: - self.error(f"Unexpected character: {char}") - - self.tokens.append(Token(TokenType.EOF, "", self.line, self.col)) - return self.tokens - - -# AST Node classes -@dataclass -class ASTNode: - pass - - -@dataclass -class Program(ASTNode): - declarations: List["Declaration"] - - -@dataclass -class Declaration(ASTNode): - pass - - -@dataclass -class FunctionDecl(Declaration): - name: str - params: List[str] - body: "CompoundStmt" - - -@dataclass -class VarDecl(Declaration): - name: str - init: Optional["Expression"] = None - - -@dataclass -class Statement(ASTNode): - pass - - -@dataclass -class CompoundStmt(Statement): - statements: List[Statement] - - -@dataclass -class ExprStmt(Statement): - expr: Optional["Expression"] - - -@dataclass -class IfStmt(Statement): - condition: "Expression" - then_stmt: Statement - else_stmt: Optional[Statement] = None - - -@dataclass -class WhileStmt(Statement): - condition: "Expression" - body: Statement - - -@dataclass -class ReturnStmt(Statement): - expr: Optional["Expression"] - - -@dataclass -class Expression(ASTNode): - pass - - -@dataclass -class BinaryOp(Expression): - op: str - left: Expression - right: Expression - - -@dataclass -class UnaryOp(Expression): - op: str - operand: Expression - - -@dataclass -class AssignExpr(Expression): - name: str - value: Expression - - -@dataclass -class VarExpr(Expression): - name: str - - -@dataclass -class NumberExpr(Expression): - value: int - - -@dataclass -class CallExpr(Expression): - name: str - args: List[Expression] - - -class Parser: - def __init__(self, tokens: List[Token]): - self.tokens = tokens - self.pos = 0 - - def error(self, msg: str): - token = self.current() - raise SyntaxError(f"Parser error at line {token.line}, col {token.col}: {msg}") - - def current(self) -> Token: - return self.tokens[self.pos] if self.pos < len(self.tokens) else self.tokens[-1] - - def peek(self, offset: int = 0) -> Token: - pos = self.pos + offset - return self.tokens[pos] if pos < len(self.tokens) else self.tokens[-1] - - def advance(self) -> Token: - token = self.current() - if self.pos < len(self.tokens) - 1: - self.pos += 1 - return token - - def expect(self, token_type: TokenType) -> Token: - token = self.current() - if token.type != token_type: - self.error(f"Expected {token_type.value}, got {token.type.value}") - return self.advance() - - def parse(self) -> Program: - declarations = [] - while self.current().type != TokenType.EOF: - declarations.append(self.parse_declaration()) - return Program(declarations) - - def parse_declaration(self) -> Declaration: - self.expect(TokenType.INT) - name = self.expect(TokenType.IDENTIFIER).value - - if self.current().type == TokenType.LPAREN: - # Function declaration - self.advance() - params = [] - - if self.current().type != TokenType.RPAREN: - self.expect(TokenType.INT) - params.append(self.expect(TokenType.IDENTIFIER).value) - - while self.current().type == TokenType.COMMA: - self.advance() - self.expect(TokenType.INT) - params.append(self.expect(TokenType.IDENTIFIER).value) - - self.expect(TokenType.RPAREN) - body = self.parse_compound_stmt() - return FunctionDecl(name, params, body) - else: - # Variable declaration - init = None - if self.current().type == TokenType.ASSIGN: - self.advance() - init = self.parse_expression() - self.expect(TokenType.SEMICOLON) - return VarDecl(name, init) - - def parse_compound_stmt(self) -> CompoundStmt: - self.expect(TokenType.LBRACE) - statements = [] - - while self.current().type != TokenType.RBRACE: - statements.append(self.parse_statement()) - - self.expect(TokenType.RBRACE) - return CompoundStmt(statements) - - def parse_statement(self) -> Statement: - token = self.current() - - if token.type == TokenType.LBRACE: - return self.parse_compound_stmt() - elif token.type == TokenType.IF: - return self.parse_if_stmt() - elif token.type == TokenType.WHILE: - return self.parse_while_stmt() - elif token.type == TokenType.RETURN: - return self.parse_return_stmt() - elif token.type == TokenType.INT: - # Local variable declaration - self.advance() - name = self.expect(TokenType.IDENTIFIER).value - init = None - if self.current().type == TokenType.ASSIGN: - self.advance() - init = self.parse_expression() - self.expect(TokenType.SEMICOLON) - return ExprStmt(AssignExpr(name, init) if init else None) - else: - expr = ( - self.parse_expression() - if self.current().type != TokenType.SEMICOLON - else None - ) - self.expect(TokenType.SEMICOLON) - return ExprStmt(expr) - - def parse_if_stmt(self) -> IfStmt: - self.expect(TokenType.IF) - self.expect(TokenType.LPAREN) - condition = self.parse_expression() - self.expect(TokenType.RPAREN) - then_stmt = self.parse_statement() - - else_stmt = None - if self.current().type == TokenType.ELSE: - self.advance() - else_stmt = self.parse_statement() - - return IfStmt(condition, then_stmt, else_stmt) - - def parse_while_stmt(self) -> WhileStmt: - self.expect(TokenType.WHILE) - self.expect(TokenType.LPAREN) - condition = self.parse_expression() - self.expect(TokenType.RPAREN) - body = self.parse_statement() - return WhileStmt(condition, body) - - def parse_return_stmt(self) -> ReturnStmt: - self.expect(TokenType.RETURN) - expr = None - if self.current().type != TokenType.SEMICOLON: - expr = self.parse_expression() - self.expect(TokenType.SEMICOLON) - return ReturnStmt(expr) - - def parse_expression(self) -> Expression: - return self.parse_assignment() - - def parse_assignment(self) -> Expression: - expr = self.parse_comparison() - - if self.current().type == TokenType.ASSIGN: - if not isinstance(expr, VarExpr): - self.error("Invalid assignment target") - self.advance() - value = self.parse_assignment() - return AssignExpr(expr.name, value) - - return expr - - def parse_comparison(self) -> Expression: - expr = self.parse_additive() - - while self.current().type in [ - TokenType.EQ, - TokenType.NE, - TokenType.LT, - TokenType.GT, - TokenType.LE, - TokenType.GE, - ]: - op = self.advance().value - right = self.parse_additive() - expr = BinaryOp(op, expr, right) - - return expr - - def parse_additive(self) -> Expression: - expr = self.parse_multiplicative() - - while self.current().type in [TokenType.PLUS, TokenType.MINUS]: - op = self.advance().value - right = self.parse_multiplicative() - expr = BinaryOp(op, expr, right) - - return expr - - def parse_multiplicative(self) -> Expression: - expr = self.parse_unary() - - while self.current().type in [TokenType.STAR, TokenType.SLASH]: - op = self.advance().value - right = self.parse_unary() - expr = BinaryOp(op, expr, right) - - return expr - - def parse_unary(self) -> Expression: - if self.current().type in [TokenType.PLUS, TokenType.MINUS]: - op = self.advance().value - operand = self.parse_unary() - return UnaryOp(op, operand) - - return self.parse_primary() - - def parse_primary(self) -> Expression: - token = self.current() - - if token.type == TokenType.NUMBER: - self.advance() - return NumberExpr(int(token.value)) - - elif token.type == TokenType.IDENTIFIER: - name = self.advance().value - - if self.current().type == TokenType.LPAREN: - # Function call - self.advance() - args = [] - - if self.current().type != TokenType.RPAREN: - args.append(self.parse_expression()) - while self.current().type == TokenType.COMMA: - self.advance() - args.append(self.parse_expression()) - - self.expect(TokenType.RPAREN) - return CallExpr(name, args) - else: - return VarExpr(name) - - elif token.type == TokenType.LPAREN: - self.advance() - expr = self.parse_expression() - self.expect(TokenType.RPAREN) - return expr - - else: - self.error(f"Unexpected token: {token.type.value}") - - -class CodeGenerator: - def __init__(self): - self.output = [] - self.label_counter = 0 - self.string_counter = 0 - self.functions = {} - self.current_function = None - self.local_vars = {} - self.global_vars = {} - self.register_pool = [f"rg{i:x}" for i in range(16)] - self.used_registers = set() - - def new_label(self, prefix: str = "L") -> str: - label = f"{prefix}{self.label_counter}" - self.label_counter += 1 - return label - - def allocate_register(self) -> str: - for reg in self.register_pool: - if reg not in self.used_registers: - self.used_registers.add(reg) - return reg - raise RuntimeError("Out of registers") - - def free_register(self, reg: str): - self.used_registers.discard(reg) - - def emit(self, code: str): - self.output.append(code) - - def generate(self, program: Program) -> str: - # Emit data section - self.emit("// Global variables") - for decl in program.declarations: - if isinstance(decl, VarDecl): - self.global_vars[decl.name] = f"var_{decl.name}" - if decl.init: - if isinstance(decl.init, NumberExpr): - self.emit(f"dw var_{decl.name}: {decl.init.value}") - else: - self.emit(f"dw var_{decl.name}: 0") - else: - self.emit(f"dw var_{decl.name}: 0") - - self.emit("") - self.emit("// Entry point") - self.emit("dw stack_bottom: 0x10000") - self.emit("") - self.emit("init:") - self.emit(" ldw stack_bottom, spr") - self.emit(" mov spr, bpr") - - self.emit(" push zero") - self.emit(" call main") - self.emit(" pop rg0") - self.emit(" hlt") - self.emit("") - - # Emit functions - for decl in program.declarations: - if isinstance(decl, FunctionDecl): - self.generate_function(decl) - - return "\n".join(self.output) - - def generate_function(self, func: FunctionDecl): - self.current_function = func.name - self.functions[func.name] = func - self.local_vars = {} - - # Map parameters to stack offsets - # Parameters start at bpr+8 (after return addr at bpr+4) - for i, param in enumerate(func.params): - self.local_vars[param] = 8 + (i * 4) - - self.emit(f"{func.name}:") - self.emit(" push bpr") - self.emit(" mov spr, bpr") - self.emit("") - - # Generate function body - self.generate_compound_stmt(func.body) - - # Default return if no explicit return - self.emit("// default return") - self.emit(f"{func.name}_end:") - self.emit(" mov bpr, spr") - self.emit(" pop bpr") - self.emit(" return") - self.emit("") - - def generate_compound_stmt(self, stmt: CompoundStmt): - for s in stmt.statements: - self.generate_statement(s) - - def generate_statement(self, stmt: Statement): - if isinstance(stmt, CompoundStmt): - self.generate_compound_stmt(stmt) - elif isinstance(stmt, ExprStmt): - if stmt.expr: - reg = self.generate_expression(stmt.expr) - self.free_register(reg) - elif isinstance(stmt, IfStmt): - self.generate_if_stmt(stmt) - elif isinstance(stmt, WhileStmt): - self.generate_while_stmt(stmt) - elif isinstance(stmt, ReturnStmt): - self.generate_return_stmt(stmt) - - def generate_if_stmt(self, stmt: IfStmt): - else_label = self.new_label("else") - end_label = self.new_label("endif") - - # Evaluate condition - cond_reg = self.generate_expression(stmt.condition) - self.emit(f" cmp {cond_reg}, zero") - self.free_register(cond_reg) - - if stmt.else_stmt: - self.emit(f" jeq {else_label}") - else: - self.emit(f" jeq {end_label}") - - # Then branch - self.generate_statement(stmt.then_stmt) - - if stmt.else_stmt: - self.emit(f" jmp {end_label}") - self.emit(f"{else_label}:") - self.generate_statement(stmt.else_stmt) - - self.emit(f"{end_label}:") - - def generate_while_stmt(self, stmt: WhileStmt): - start_label = self.new_label("while_start") - end_label = self.new_label("while_end") - - self.emit(f"{start_label}:") - - # Evaluate condition - cond_reg = self.generate_expression(stmt.condition) - self.emit(f" cmp {cond_reg}, zero") - self.free_register(cond_reg) - self.emit(f" jeq {end_label}") - - # Loop body - self.generate_statement(stmt.body) - self.emit(f" jmp {start_label}") - - self.emit(f"{end_label}:") - - def generate_return_stmt(self, stmt: ReturnStmt): - if stmt.expr: - reg = self.generate_expression(stmt.expr) - # Store return value at spr+8 according to calling convention - self.emit(f" stw {reg}, spr, 8") - self.free_register(reg) - self.emit(f" jmp {self.current_function}_end") - - def generate_expression(self, expr: Expression) -> str: - if isinstance(expr, NumberExpr): - reg = self.allocate_register() - if expr.value <= 0xFFFF and expr.value >= 0: - self.emit(f" lli {expr.value}, {reg}") - if expr.value > 0xFF: - self.emit(f" lui {expr.value >> 16}, {reg}") - else: - self.emit(f" lli {expr.value & 0xFFFF}, {reg}") - self.emit(f" lui {(expr.value >> 16) & 0xFFFF}, {reg}") - return reg - - elif isinstance(expr, VarExpr): - reg = self.allocate_register() - if expr.name in self.local_vars: - offset = self.local_vars[expr.name] - self.emit(f" ldw bpr, {reg}, {offset}") - elif expr.name in self.global_vars: - label = self.global_vars[expr.name] - self.emit(f" ldw {label}, {reg}") - else: - raise RuntimeError(f"Undefined variable: {expr.name}") - return reg - - elif isinstance(expr, AssignExpr): - value_reg = self.generate_expression(expr.value) - - if expr.name in self.local_vars: - offset = self.local_vars[expr.name] - self.emit(f" stw {value_reg}, bpr, {offset}") - elif expr.name in self.global_vars: - label = self.global_vars[expr.name] - self.emit(f" stw {value_reg}, {label}") - else: - # New local variable - allocate after params and return value space - # Start local variables at offset -4 from bpr (growing downward) - offset = -(len([v for v in self.local_vars.values() if v < 0]) + 1) * 4 - self.local_vars[expr.name] = offset - self.emit(f" stw {value_reg}, bpr, {offset}") - - return value_reg - - elif isinstance(expr, BinaryOp): - return self.generate_binary_op(expr) - - elif isinstance(expr, UnaryOp): - operand_reg = self.generate_expression(expr.operand) - result_reg = self.allocate_register() - - if expr.op == "-": - self.emit(f" lwi 0, {result_reg}") - self.emit(f" sub {result_reg}, {operand_reg}, {result_reg}") - else: # + - self.emit(f" mov {operand_reg}, {result_reg}") - - self.free_register(operand_reg) - return result_reg - - elif isinstance(expr, CallExpr): - # First, make space for return value (must be pushed BEFORE arguments) - temp_reg = self.allocate_register() - - # Then push arguments in reverse order - arg_regs = [] - for arg in reversed(expr.args): - reg = self.generate_expression(arg) - self.emit(f" push {reg}") - arg_regs.append(reg) - - # Call function - self.emit(f" call {expr.name}") - - # Get return value (it's now on top of stack) - self.emit(f" pop {temp_reg}") - - # Clean up remaining args - for i in range(len(arg_regs) - 1): - self.emit(f" pop zero") - - # Free the arg registers - for reg in arg_regs: - self.free_register(reg) - - return temp_reg - - else: - raise RuntimeError(f"Unknown expression type: {type(expr)}") - - def generate_binary_op(self, expr: BinaryOp) -> str: - # For operations that might contain function calls, we need to be careful - # about register allocation. Evaluate left, save it, evaluate right. - left_reg = self.generate_expression(expr.left) - - # If right side contains a function call, we need to save left_reg - # For now, always save to be safe - saved_reg = self.allocate_register() - self.emit(f" mov {left_reg}, {saved_reg}") - self.free_register(left_reg) - - right_reg = self.generate_expression(expr.right) - result_reg = self.allocate_register() - - if expr.op == "+": - self.emit(f" add {left_reg}, {right_reg}, {result_reg}") - elif expr.op == "-": - self.emit(f" sub {left_reg}, {right_reg}, {result_reg}") - elif expr.op == "*": - # Simple multiplication using loop - temp_label = self.new_label("mult") - end_label = self.new_label("mult_end") - self.emit(f" lli 0, {result_reg}") - self.emit(f"{temp_label}:") - self.emit(f" cmp {right_reg}, zero") - self.emit(f" jeq {end_label}") - self.emit(f" add {result_reg}, {left_reg}, {result_reg}") - self.emit(f" dec {right_reg}") - self.emit(f" jmp {temp_label}") - self.emit(f"{end_label}:") - elif expr.op == "/": - # Simple division using loop - temp_label = self.new_label("div") - end_label = self.new_label("div_end") - self.emit(f" lli 0, {result_reg}") - self.emit(f"{temp_label}:") - self.emit(f" cmp {left_reg}, {right_reg}") - self.emit(f" jlt {end_label}") - self.emit(f" sub {left_reg}, {right_reg}, {left_reg}") - self.emit(f" inc {result_reg}") - self.emit(f" jmp {temp_label}") - self.emit(f"{end_label}:") - elif expr.op in ["==", "!=", "<", ">", "<=", ">="]: - self.emit(f" cmp {left_reg}, {right_reg}") - - # Result is 1 if condition true, 0 otherwise - self.emit(f" lli 0, {result_reg}") - true_label = self.new_label("cmp_true") - end_label = self.new_label("cmp_end") - - if expr.op == "==": - self.emit(f" jeq {true_label}") - elif expr.op == "!=": - self.emit(f" jne {true_label}") - elif expr.op == "<": - self.emit(f" jlt {true_label}") - elif expr.op == ">": - self.emit(f" jgt {true_label}") - elif expr.op == "<=": - self.emit(f" jle {true_label}") - elif expr.op == ">=": - self.emit(f" jge {true_label}") - - self.emit(f" jmp {end_label}") - self.emit(f"{true_label}:") - self.emit(f" lli 1, {result_reg}") - self.emit(f"{end_label}:") - - self.free_register(left_reg) - self.free_register(right_reg) - return result_reg - - -def compile_c_to_asm(source: str) -> str: - """Compile C source code to DSA assembly.""" - lexer = Lexer(source) - tokens = lexer.tokenize() - - parser = Parser(tokens) - ast = parser.parse() - - codegen = CodeGenerator() - assembly = codegen.generate(ast) - - return assembly - - -def main(): - if len(sys.argv) < 2: - print("Usage: python compiler.py [output.dsa]") - sys.exit(1) - - input_file = sys.argv[1] - output_file = sys.argv[2] if len(sys.argv) > 2 else input_file.replace(".c", ".dsa") - - with open(input_file, "r") as f: - source = f.read() - - try: - assembly = compile_c_to_asm(source) - - with open(output_file, "w") as f: - f.write(assembly) - - print(f"Successfully compiled {input_file} to {output_file}") - except (SyntaxError, RuntimeError) as e: - print(f"Compilation error: {e}") - sys.exit(1) - - -if __name__ == "__main__": - main() - # # Example usage - # if len(sys.argv) > 1: - # example_c = sys.argv[1] - - # else: - # example_c = """ - # int factorial(int n) { - # if (n <= 1) { - # return 1; - # } - # return n * factorial(n - 1); - # } - - # int main() { - # int result; - # result = factorial(5); - # return result; - # } - # """ - - # print("Example C program:") - # print(example_c) - # print("\n" + "="*60 + "\n") - # print("Generated DSA assembly:") - # print(compile_c_to_asm(example_c)) diff --git a/c_compiler/example.c b/c_compiler/example.c deleted file mode 100644 index 1182420..0000000 --- a/c_compiler/example.c +++ /dev/null @@ -1,12 +0,0 @@ -int factorial(int n) { - if (n <= 1) { - return 1; - } - return n * factorial(n - 1); -} - -int main() { - int res = factorial(3); - printnum(res); - return 0; -} diff --git a/c_compiler/example.dsc b/c_compiler/example.dsc deleted file mode 100644 index 232535d..0000000 --- a/c_compiler/example.dsc +++ /dev/null @@ -1,25 +0,0 @@ -include print: "lib/io/print.dsa" - -int factorial(int n) { - if (n <= 1) { - return 1; - } - return n * factorial(n - 1); -} - -int add_(int a, int b) { - return a + b; -} - -int greater(int a, int b) { - if (a + a > b + b) { - return a; - } else { - return b + a; - } -} - -int main() { - printnum(-5); - return 0; -} diff --git a/c_compiler/output.dsa b/c_compiler/output.dsa deleted file mode 100644 index 54e84c0..0000000 --- a/c_compiler/output.dsa +++ /dev/null @@ -1,5 +0,0 @@ -// Imports -include maths: "./lib/maths/core.dsa" - -// Reserved Memory - diff --git a/c_compiler/src/assembly.rs b/c_compiler/src/assembly.rs deleted file mode 100644 index 4b46a4f..0000000 --- a/c_compiler/src/assembly.rs +++ /dev/null @@ -1,106 +0,0 @@ -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -#[non_exhaustive] -pub enum Register { - // general purpose registers - Rg0, - Rg1, - Rg2, - Rg3, - Rg4, - Rg5, - Rg6, - Rg7, - Rg8, - Rg9, - Rga, - Rgb, - Rgc, - Rgd, - Rge, - Rgf, - - // special purpose registers - Acc, - Spr, - Bpr, - Ret, - Idr, - Mmr, - Zero, - NoReg, - - // system registers - can't be written to by instructions. - Mar, - Mdr, - Sts, - Cir, - Pcx, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[repr(u8)] -#[non_exhaustive] -/// A list of all current instructions in the DSA Assembly language. -pub enum Instruction { - // No-op - Nop = 0x0, - - // Data transfer instructions - Mov(Register, Register) = 0x1, - Movs(Register, Register) = 0x2, - - Ldb(Register, Register, Option) = 0x3, - Ldbs(Register, Register, Option) = 0x4, - Ldh(Register, Register, Option) = 0x5, - Ldhs(Register, Register, Option) = 0x6, - Ldw(Register, Register, Option) = 0x7, - - Stb(Register, Register, Option) = 0x8, - Sth(Register, Register, Option) = 0x9, - Stw(Register, Register, Option) = 0xA, - - Lli(u16, Register) = 0xB, - Lui(u16, Register) = 0xC, - - // Jump Instructions - Jump(u16, Register) = 0xD, - JumpEq(u16, Register) = 0xE, - JumpNeq(u16, Register) = 0xF, - JumpGt(u16, Register) = 0x10, - JumpGe(u16, Register) = 0x11, - JumpLt(u16, Register) = 0x12, - JumpLe(u16, Register) = 0x13, - - // Comparison - Compare(Register, Register) = 0x14, - - // // Arithmetic - // Add(args::RTypeArgs) = 0x19, - // Sub(args::RTypeArgs) = 0x1A, - // Increment(args::RTypeArgs) = 0x15, - // Decrement(args::RTypeArgs) = 0x16, - // ShiftLeft(args::RTypeArgs) = 0x17, - // ShiftRight(args::RTypeArgs) = 0x18, - - // // Logical - // And(args::RTypeArgs) = 0x1B, - // Or(args::RTypeArgs) = 0x1C, - // Not(args::RTypeArgs) = 0x1D, - // Xor(args::RTypeArgs) = 0x1E, - // Nand(args::RTypeArgs) = 0x1F, - // Nor(args::RTypeArgs) = 0x20, - // Xnor(args::RTypeArgs) = 0x21, - - // // Misc - // Interrupt(Interrupt) = 0x22, - // IntReturn = 0x23, - // Halt = 0x24, - - // // Immediate Arithmetic - // AddImmediate(args::ITypeArgs) = 0x25, - // SubImmediate(args::ITypeArgs) = 0x26, - - // Fake Instructions - Data(u32) = 0x3E, - Segment(u32) = 0x3F, -} diff --git a/c_compiler/src/codegen.rs b/c_compiler/src/codegen.rs deleted file mode 100644 index ee3a598..0000000 --- a/c_compiler/src/codegen.rs +++ /dev/null @@ -1,599 +0,0 @@ -use std::collections::HashMap; -use std::hash::Hash; -use std::sync::LazyLock; -use std::sync::atomic::AtomicU32; -use std::time::SystemTime; - -use chrono::{DateTime, Local}; - -use crate::registers::RegisterAllocator; -use crate::{block, cmd, comment, dsa}; - -use crate::parser::{ - BinaryOperator, ConstExpr, Declaration, Expression, Parameter, Program, Statement, - UnaryOperator, -}; - -pub struct CodeGenerator { - ast: Program, - imports: HashMap, - globals: Vec, - functions: Vec, - symbols: Vec, - allocator: RegisterAllocator, -} - -static GLOBAL_METHODS: LazyLock> = LazyLock::new(|| { - HashMap::from([("print", "print::print"), ("printnum", "print::print_num")]) -}); - -fn import(name: &str, path: &str) -> String { - format!("include {name}: \"{}\"", path) -} - -impl CodeGenerator { - const RET: &'static str = "\tjmp _ret"; - - pub fn new(ast: Program) -> Self { - CodeGenerator { - ast, - imports: HashMap::new(), - globals: Vec::new(), - functions: Vec::new(), - symbols: Vec::new(), - allocator: RegisterAllocator::new(), - } - } - - pub fn include(&mut self, name: &str, path: &str) { - self.imports.insert(name.to_string(), path.to_string()); - } - - pub fn generate(&mut self) -> Result { - // always include the print library for debugging! - self.include("print", "./lib/io/print.dsa"); - - for block in self.ast.clone().declarations { - match block { - Declaration::Variable { name, .. } => self.symbols.push(name), - Declaration::Function { name, .. } => self.symbols.push(name), - Declaration::Import { name, .. } => self.symbols.push(name), - } - } - - for block in self.ast.clone().declarations { - self.generate_block(block.clone())?; - } - - self.generate_layout() - } - - fn generate_layout(&mut self) -> Result { - let datetime: DateTime = SystemTime::now().into(); - Ok(dsa![ - "", - comment!("GENERATED BY DSA-C COMPILER"), - comment!(format!( - "Generated at {}", - datetime.format("%Y-%m-%d %H:%M:%S") - )), - "", - // imports - comment!("Imports"), - self.imports - .iter() - .map(|(k, v)| import(k, v)) - .collect::>() - .join("\n"), - "", - // reserved memory - comment!("Globals & Reserved Memory"), - self.globals.join("\n"), - "", - // entry point - comment!("Entry Point"), - "dw stack: 0x10000", - "db message: \"Process Exited with code:\"", - block! [ "_init" - dsa![ldw stack, bpr], - dsa![mov bpr, spr], - dsa![push zero], - dsa![call main], - dsa![call print::print_newline], - dsa![lwi message, rg0], - dsa![push rg0], - dsa![call print::print], - dsa![pop zero], - dsa![call print::print_hex_word], - dsa![pop zero], - dsa![hlt] - ], - "", - comment!("Function return boilerplate"), - block! [ "_ret" - dsa![mov bpr, spr], - dsa![pop bpr], - dsa![return] - ], - // block! [ "main" - // dsa![push bpr], - // dsa![mov spr, bpr], - // dsa![lwi 67, rg1], - // dsa![stw rg1, spr, 8], - // dsa![mov bpr, spr], - // dsa![pop bpr], - // dsa![return] - // ], - "", - self.functions.join("\n"), - ]) - } - - fn generate_global(&mut self, name: &str, init: Option) { - self.globals.push(format!( - "dw {}: {}", - name, - init.unwrap_or(ConstExpr::Number(0)) - )) - } - - fn generate_block(&mut self, block: Declaration) -> Result<(), String> { - match block { - Declaration::Variable { name, init } => self.generate_global(&name, init), - Declaration::Function { - name, - return_type, - params, - body, - } => { - let func = self.generate_function(&name, ¶ms, &body).join("\n"); - - self.functions.push(format!("{func}\n")); - } - Declaration::Import { name, path } => { - self.imports.insert(name, path); - } - }; - - Ok(()) - } - - // Example: Generate code for a function - fn generate_function( - &mut self, - name: &str, - params: &[Parameter], - body: &[Statement], - ) -> Vec { - let mut code = Vec::new(); - - // Reset allocator for new function - self.allocator.reset(); - - // Function prologue - code.push(format!("{}:", name)); - code.push("\tpush bpr".to_string()); - code.push("\tmov spr, bpr".to_string()); - code.push(String::new()); - - // Allocate parameters to registers or stack locations - for (i, param) in params.iter().enumerate() { - let offset = 8 + (i as i32 * 4); // Parameters start at bpr+8 - // Track that this parameter is at a stack location - let (reg, load_code) = self.allocator.alloc_var(¶m.name).unwrap(); - code.extend(load_code); - code.push(format!("\tldw bpr, {}, {}", reg, offset)); - } - - // Generate code for function body - for stmt in body { - let stmt_code = self.generate_statement(stmt).unwrap(); - code.extend(stmt_code); - } - - // automatically return at function end - if let Some(x) = code.last() - && x == Self::RET - { - } else { - code.push(Self::RET.to_string()); - } - - code - } - - // Example: Generate code for a statement - fn generate_statement(&mut self, stmt: &Statement) -> Result, String> { - let mut code = Vec::new(); - - match stmt { - Statement::Assign { - name, - declare_type, - value, - } => { - if let Some(expr) = value { - // Evaluate expression - let (result_reg, expr_code) = self.generate_expression(expr)?; - code.extend(expr_code); - - // Store result in variable - let store_code = self.allocator.store_var(name, &result_reg); - code.extend(store_code); - - // Free temporary register - self.allocator.free_temp(&result_reg); - } else { - // Just declaring variable without initialization - self.allocator.alloc_var(name)?; - } - } - - Statement::Return { expr } => { - if let Some(e) = expr { - let (result_reg, expr_code) = self.generate_expression(e)?; - code.extend(expr_code); - code.push(format!("\tstw {}, bpr, 8", result_reg)); - code.push(format!("\tjmp _ret")); - self.allocator.free_temp(&result_reg); - } - } - - Statement::If { - condition, - then_stmt, - else_stmt, - } => { - // Generate condition - let (cond_reg, cond_code) = self.generate_expression(condition)?; - code.extend(cond_code); - - // Compare with zero - code.push(format!("\tcmp {}, zero", cond_reg)); - self.allocator.free_temp(&cond_reg); - - // Generate unique labels - let then_label = format!("_then_{}", self.get_unique_label()); - let else_label = format!("_else_{}", self.get_unique_label()); - let end_label = format!("_end_{}", self.get_unique_label()); - - // Jump to else if condition is false (equal to zero) - code.push(format!("\tjeq {}", else_label)); - - // Then block - code.push(format!("{}:", then_label)); - for s in then_stmt { - code.extend(self.generate_statement(s)?); - } - - if then_stmt.len() == 0 { - code.push("\tnop".to_string()); - } - - code.push(format!("\tjmp {}", end_label)); - - // Else block - code.push(format!("{}:", else_label)); - for s in else_stmt { - code.extend(self.generate_statement(s)?); - } - - if else_stmt.len() == 0 { - code.push("\tnop".to_string()); - } - - code.push(format!("{}:", end_label)); - } - - Statement::While { condition, body } => { - let loop_start = format!("_while_start_{}", self.get_unique_label()); - let loop_end = format!("_while_end_{}", self.get_unique_label()); - - code.push(format!("{}:", loop_start)); - - // Generate condition - let (cond_reg, cond_code) = self.generate_expression(condition)?; - code.extend(cond_code); - - code.push(format!("\tcmp {}, zero", cond_reg)); - self.allocator.free_temp(&cond_reg); - - code.push(format!("\tjeq {}", loop_end)); - - // Loop body - for s in body { - code.extend(self.generate_statement(s)?); - } - - code.push(format!("\tjmp {}", loop_start)); - code.push(format!("{}:", loop_end)); - } - - Statement::Expression { expr } => { - let (result_reg, expr_code) = self.generate_expression(expr)?; - code.extend(expr_code); - self.allocator.free_temp(&result_reg); - } - - Statement::Block(statements) => { - for s in statements { - code.extend(self.generate_statement(s)?); - } - } - } - - Ok(code) - } - - // Example: Generate code for an expression - // Returns (register containing result, assembly code) - fn generate_expression( - &mut self, - expr: &Expression, - ) -> Result<(String, Vec), String> { - let mut code = Vec::new(); - - match expr { - Expression::Number { value } => { - let (reg, alloc_code) = self.allocator.alloc_temp()?; - code.extend(alloc_code); - - // Load immediate value - code.push(format!("\tlli {}, {}", value & 0xFFFF, reg)); - if *value > 0xFFFF || *value < 0 { - code.push(format!("\tlui {}, {}", (value >> 16) & 0xFFFF, reg)); - } - - Ok((reg, code)) - } - - Expression::Variable { name, .. } => { - let (reg, load_code) = self.allocator.load_var(name)?; - code.extend(load_code); - Ok((reg, code)) - } - - Expression::Binary { op, left, right } => { - // Evaluate left operand - let (left_reg, left_code) = self.generate_expression(left)?; - code.extend(left_code); - - // Evaluate right operand - let (right_reg, right_code) = self.generate_expression(right)?; - code.extend(right_code); - - // Allocate result register - let (result_reg, result_alloc) = self.allocator.alloc_temp()?; - code.extend(result_alloc); - - // Generate operation - match op { - BinaryOperator::Add => { - code.push(format!( - "\tadd {}, {}, {}", - left_reg, right_reg, result_reg - )); - } - BinaryOperator::Sub => { - code.push(format!( - "\tsub {}, {}, {}", - left_reg, right_reg, result_reg - )); - } - BinaryOperator::Mul => { - self.include("maths", "./lib/maths/core.dsa"); - // Call multiply function - code.push(format!("\tpush {}", right_reg)); - code.push(format!("\tpush {}", left_reg)); - code.push("\tcall maths::multiply".to_string()); - code.push(format!("\tpop {}", result_reg)); - code.push("\tpop zero".to_string()); - } - // Comparison operators - return 1 (true) or 0 (false) - BinaryOperator::Eq => { - code.push(format!("\tcmp {}, {}", left_reg, right_reg)); - code.push(format!("\tlli 0, {}", result_reg)); - let end_label = format!("_cmp_end_{}", self.get_unique_label()); - code.push(format!("\tjne {}", end_label)); // If not equal, skip setting to 1 - code.push(format!("\tlli 1, {}", result_reg)); - code.push(format!("{}:", end_label)); - } - BinaryOperator::Ne => { - code.push(format!("\tcmp {}, {}", left_reg, right_reg)); - code.push(format!("\tlli 0, {}", result_reg)); - let end_label = format!("_cmp_end_{}", self.get_unique_label()); - code.push(format!("\tjeq {}", end_label)); // If equal, skip setting to 1 - code.push(format!("\tlli 1, {}", result_reg)); - code.push(format!("{}:", end_label)); - } - BinaryOperator::Lt => { - code.push(format!("\tcmp {}, {}", left_reg, right_reg)); - code.push(format!("\tlli 0, {}", result_reg)); - let end_label = format!("_cmp_end_{}", self.get_unique_label()); - code.push(format!("\tjge {}", end_label)); // If greater or equal, skip setting to 1 - code.push(format!("\tlli 1, {}", result_reg)); - code.push(format!("{}:", end_label)); - } - BinaryOperator::Le => { - code.push(format!("\tcmp {}, {}", left_reg, right_reg)); - code.push(format!("\tlli 0, {}", result_reg)); - let end_label = format!("_cmp_end_{}", self.get_unique_label()); - code.push(format!("\tjgt {}", end_label)); // If greater than, skip setting to 1 - code.push(format!("\tlli 1, {}", result_reg)); - code.push(format!("{}:", end_label)); - } - BinaryOperator::Gt => { - code.push(format!("\tcmp {}, {}", left_reg, right_reg)); - code.push(format!("\tlli 0, {}", result_reg)); - let end_label = format!("_cmp_end_{}", self.get_unique_label()); - code.push(format!("\tjle {}", end_label)); // If less or equal, skip setting to 1 - code.push(format!("\tlli 1, {}", result_reg)); - code.push(format!("{}:", end_label)); - } - BinaryOperator::Ge => { - code.push(format!("\tcmp {}, {}", left_reg, right_reg)); - code.push(format!("\tlli 0, {}", result_reg)); - let end_label = format!("_cmp_end_{}", self.get_unique_label()); - code.push(format!("\tjlt {}", end_label)); // If less than, skip setting to 1 - code.push(format!("\tlli 1, {}", result_reg)); - code.push(format!("{}:", end_label)); - } - _ => return Err(format!("Unsupported binary operator: {:?}", op)), - } - - // Free operand registers (allocator will protect variables) - self.allocator.free_temp(&left_reg); - self.allocator.free_temp(&right_reg); - - Ok((result_reg, code)) - } - - Expression::Call { name, args } => { - // Save caller-saved registers and track which ones we saved - let saved_regs = self.allocator.get_caller_saved_registers(); - for reg in &saved_regs { - code.push(format!("\tpush {}", reg)); - } - - // Evaluate and push arguments in reverse order - let mut arg_regs = Vec::new(); - for arg in args.iter().rev() { - let (arg_reg, arg_code) = self.generate_expression(arg)?; - code.extend(arg_code); - code.push(format!("\tpush {}", arg_reg)); - arg_regs.push(arg_reg); - } - - if GLOBAL_METHODS.contains_key(name.as_str()) { - code.push(format!("\tcall {}", GLOBAL_METHODS[name.as_str()])); - } else if self.symbols.contains(name) { - // Call local function - code.push(format!("\tcall {}", name)); - } else { - return Err(format!("undefined function {name}")); - } - - // Result is in rg0, allocate a register and move it - let (result_reg, result_alloc) = self.allocator.alloc_temp()?; - - code.extend(result_alloc); - code.push(format!("\tpop {}", result_reg)); - - // Clean up arguments - if args.len() > 1 { - for _ in 0..(args.len() - 1) { - code.push("\tpop zero".to_string()); - } - } - - // Restore caller-saved registers in reverse order (LIFO) - for reg in saved_regs.iter().rev() { - code.push(format!("\tpop {}", reg)); - } - - // Free argument registers - for reg in arg_regs { - self.allocator.free_temp(®); - } - - Ok((result_reg, code)) - } - - Expression::Unary { op, operand } => { - let (operand_reg, operand_code) = self.generate_expression(operand)?; - code.extend(operand_code); - - let (result_reg, result_alloc) = self.allocator.alloc_temp()?; - code.extend(result_alloc); - - match op { - UnaryOperator::Minus => { - // Negate: result = 0 - operand - code.push(format!("\tsub zero, {}, {}", operand_reg, result_reg)); - } - UnaryOperator::Plus => { - // Just move - code.push(format!("\tmov {}, {}", operand_reg, result_reg)); - } - } - - self.allocator.free_temp(&operand_reg); - Ok((result_reg, code)) - } - - Expression::Empty => Ok(("zero".to_string(), code)), - } - } - - // Helper for generating unique labels - fn get_unique_label(&mut self) -> String { - // You'd implement a counter here - static COUNTER: AtomicU32 = AtomicU32::new(0); - - let val = COUNTER.fetch_add(1, std::sync::atomic::Ordering::SeqCst); - (val + 1).to_string() - } -} - -/// Build a single string from any number of arguments. -/// Each argument must implement `Display` or be convertible to a string. -#[macro_export] -macro_rules! dsa { - ($($arg:expr),* $(,)?) => {{ - // Start with an empty String – we’ll grow it as we go. - use std::fmt::Write; - let mut s = ::std::string::String::new(); - $( - // `write!` is cheaper than `format!` for each element - // because it re‑uses the same buffer. - - write!(s, "{}\n", $arg).expect("write to String failed"); - )* - s - }}; -} - -// ──────────────────────── dsa! ──────────────────────── -// A tiny helper that just turns its token‑stream into a string. -// The trailing comma is kept – it’s part of the syntax you want. -#[macro_export] -macro_rules! cmd { - ($($tokens:tt)*) => {{ - // We’ll just stringify the tokens and return a String. - format!("{}", concat!(stringify!($tokens), "\n")) - }}; -} - -// ──────────────────────── block! ──────────────────────── -// Usage: -// -// let asm = block![ "name" -// dsa![mov rg0, rg1], -// dsa![add rg1, rg1] -// ]; -// -// `asm` is a `&'static str` containing: -// -// name: -// mov rg0, rg1 -// add rg1, rg1 -// -#[macro_export] -macro_rules! block { - // The first token must be a string literal – that’s the label. - ($label:literal $(dsa![$($ins:tt)*]),* ) => {{ - // Build a single string at compile time. - const CODE: &str = concat!( - $label, ":\n", - // Each `dsa!` call yields a string like `"mov rg0, rg1"`. - // We add a newline after each one to get the desired layout. - $(concat!("\t", stringify!($($ins)*), "\n")),* - ); - CODE - }}; -} - -#[macro_export] -macro_rules! comment { - ($text:expr) => {{ format!("// {}", $text) }}; -} diff --git a/c_compiler/src/lexer.rs b/c_compiler/src/lexer.rs deleted file mode 100644 index 60cf402..0000000 --- a/c_compiler/src/lexer.rs +++ /dev/null @@ -1,335 +0,0 @@ -// ============================================================================ -// Token Types -// ============================================================================ - -#[derive(Debug, Clone, PartialEq)] -pub enum TokenType { - // Keywords - Int, - If, - Else, - While, - Return, - Include, - - // Identifiers and literals - Identifier(String), - Number(i32), - String(String), - Char(char), - - // Operators - Plus, - Minus, - Star, - Slash, - Assign, - Eq, - Ne, - Lt, - Gt, - Le, - Ge, - - // Delimiters - LParen, - RParen, - LBrace, - RBrace, - Semicolon, - Comma, - Colon, - Namespace, - - Eof, -} - -pub enum Type { - Int32, - Int16, - Int8, - Uint32, - Uint16, - Uint8, - Char, -} - -#[derive(Debug, Clone)] -pub struct Token { - pub token_type: TokenType, - pub line: usize, - pub col: usize, -} - -impl Token { - pub fn new(token_type: TokenType, line: usize, col: usize) -> Self { - Self { - token_type, - line, - col, - } - } -} - -// ============================================================================ -// Lexer -// ============================================================================ - -pub struct Lexer { - source: Vec, - pos: usize, - line: usize, - col: usize, -} - -impl Lexer { - pub fn new(source: &str) -> Self { - Self { - source: source.chars().collect(), - pos: 0, - line: 1, - col: 1, - } - } - - fn error(&self, msg: &str) -> String { - format!( - "Lexer error at line {}, col {}: {}", - self.line, self.col, msg - ) - } - - fn peek(&self, offset: usize) -> Option { - self.source.get(self.pos + offset).copied() - } - - fn advance(&mut self) -> Option { - if self.pos >= self.source.len() { - return None; - } - let ch = self.source[self.pos]; - self.pos += 1; - if ch == '\n' { - self.line += 1; - self.col = 1; - } else { - self.col += 1; - } - Some(ch) - } - - fn skip_whitespace(&mut self) { - while let Some(ch) = self.peek(0) { - if ch.is_whitespace() { - self.advance(); - } else { - break; - } - } - } - - fn skip_comment(&mut self) { - if self.peek(0) == Some('/') && self.peek(1) == Some('/') { - while let Some(ch) = self.peek(0) { - if ch == '\n' { - break; - } - self.advance(); - } - } - } - - fn read_number(&mut self) -> i32 { - let mut num_str = String::new(); - while let Some(ch) = self.peek(0) { - if ch.is_ascii_digit() { - num_str.push(ch); - self.advance(); - } else { - break; - } - } - num_str.parse().unwrap_or(0) - } - - fn read_identifier(&mut self) -> String { - let mut ident = String::new(); - while let Some(ch) = self.peek(0) { - if ch.is_alphanumeric() || ch == '_' { - ident.push(ch); - self.advance(); - } else { - break; - } - } - ident - } - - fn read_string(&mut self) -> Result { - let mut string = String::new(); - self.advance(); // Consume the opening quote - - while let Some(ch) = self.peek(0) { - if ch == '"' { - self.advance(); // Consume the closing quote - return Ok(string); - } else if ch == '\\' { - self.advance(); // Consume the backslash - if let Some(escaped_char) = self.peek(0) { - string.push(escaped_char); - self.advance(); - } - } else { - string.push(ch); - self.advance(); - } - } - - Err(String::from("Unexpected EOF")) - } - - fn read_char(&mut self) -> Result { - self.advance(); // Consume the opening quote - - if let Some(ch) = self.peek(0) { - self.advance(); - if self.peek(0) == Some('\'') { - self.advance(); - return Ok(ch); - } else { - Err(String::from("expected closing quote")) - } - } else { - Err(String::from("expected character")) - } - } - - pub fn tokenize(&mut self) -> Result, String> { - let mut tokens = Vec::new(); - - loop { - self.skip_whitespace(); - self.skip_comment(); - - if self.pos >= self.source.len() { - break; - } - - let line = self.line; - let col = self.col; - let ch = self.peek(0).unwrap(); - - let token_type = if ch.is_ascii_digit() { - let num = self.read_number(); - TokenType::Number(num) - } else if ch == '"' { - let string = self.read_string()?; - TokenType::String(string) - } else if ch == '\'' { - let char = self.read_char()?; - TokenType::Char(char) - } else if ch.is_alphabetic() || ch == '_' { - let ident = self.read_identifier(); - match ident.as_str() { - "int" => TokenType::Int, - "if" => TokenType::If, - "else" => TokenType::Else, - "while" => TokenType::While, - "return" => TokenType::Return, - "include" => TokenType::Include, - _ => TokenType::Identifier(ident), - } - } else { - match ch { - ':' if self.peek(1) == Some(':') => { - self.advance(); - self.advance(); - TokenType::Namespace - } - ':' => { - self.advance(); - TokenType::Colon - } - '=' if self.peek(1) == Some('=') => { - self.advance(); - self.advance(); - TokenType::Eq - } - '!' if self.peek(1) == Some('=') => { - self.advance(); - self.advance(); - TokenType::Ne - } - '<' if self.peek(1) == Some('=') => { - self.advance(); - self.advance(); - TokenType::Le - } - '>' if self.peek(1) == Some('=') => { - self.advance(); - self.advance(); - TokenType::Ge - } - '+' => { - self.advance(); - TokenType::Plus - } - '-' => { - self.advance(); - TokenType::Minus - } - '*' => { - self.advance(); - TokenType::Star - } - '/' => { - self.advance(); - TokenType::Slash - } - '=' => { - self.advance(); - TokenType::Assign - } - '<' => { - self.advance(); - TokenType::Lt - } - '>' => { - self.advance(); - TokenType::Gt - } - '(' => { - self.advance(); - TokenType::LParen - } - ')' => { - self.advance(); - TokenType::RParen - } - '{' => { - self.advance(); - TokenType::LBrace - } - '}' => { - self.advance(); - TokenType::RBrace - } - ';' => { - self.advance(); - TokenType::Semicolon - } - ',' => { - self.advance(); - TokenType::Comma - } - _ => return Err(self.error(&format!("Unexpected character: {}", ch))), - } - }; - - tokens.push(Token::new(token_type, line, col)); - } - - tokens.push(Token::new(TokenType::Eof, self.line, self.col)); - Ok(tokens) - } -} diff --git a/c_compiler/src/main.rs b/c_compiler/src/main.rs deleted file mode 100644 index 06cbfed..0000000 --- a/c_compiler/src/main.rs +++ /dev/null @@ -1,74 +0,0 @@ -use std::fmt; - -use crate::{codegen::CodeGenerator, lexer::Lexer, parser::Parser}; - -// mod assembly; -pub mod codegen; -pub mod lexer; -pub mod parser; -mod registers; - -// ============================================================================ -// Main & Tests -// ============================================================================ - -fn main() { - // read from input file: syntax "c_compiler [output.dsa]" - let args: Vec = std::env::args().collect(); - if args.len() < 2 { - eprintln!("Usage: c_compiler [output.dsa]"); - return; - } - - let input_file = &args[1]; - let output_file = if args.len() > 2 { - &args[2] - } else { - "output.dsa" - }; - - // read input - let input = std::fs::read_to_string(input_file).expect("Failed to read input file"); - - // Lexing - let mut lexer = Lexer::new(&input); - let tokens = match lexer.tokenize() { - Ok(tokens) => tokens, - Err(e) => { - eprintln!("Lexing error: {}", e); - return; - } - }; - - println!("Tokens:"); - for token in &tokens { - println!(" {:?}", token.token_type); - } - println!(); - - // Parsing - let mut parser = Parser::new(tokens); - let ast = match parser.parse() { - Ok(ast) => ast, - Err(e) => { - eprintln!("Parsing error: {}", e); - return; - } - }; - - println!("AST:"); - println!("{:#?}", ast); - - // Code Gen - let mut generator = CodeGenerator::new(ast); - let result = match generator.generate() { - Ok(code) => code, - Err(e) => { - eprintln!("Parsing error: {}", e); - return; - } - }; - - std::fs::write(output_file, &result).expect("Failed to write output"); - println!("Result written to {}", output_file); -} diff --git a/c_compiler/src/parser.rs b/c_compiler/src/parser.rs deleted file mode 100644 index 86f2b00..0000000 --- a/c_compiler/src/parser.rs +++ /dev/null @@ -1,610 +0,0 @@ -// ============================================================================ -// AST Node Types -// ============================================================================ - -use std::fmt; - -use crate::lexer::{Token, TokenType}; - -#[derive(Debug, Clone)] -pub struct Program { - pub declarations: Vec, -} - -#[derive(Debug, Clone)] -pub enum Declaration { - Function { - name: String, - return_type: Type, - params: Vec, - body: Block, - }, - Variable { - name: String, - init: Option, - }, - Import { - name: String, - path: String, - }, -} - -#[derive(Debug, Clone)] -pub struct Parameter { - pub name: String, - pub param_type: Type, -} - -#[derive(Debug, Clone)] -pub enum Type { - Int, - Long, - Float, - Double, - Char, - Void, - Ptr(Box), - Array(Box, usize), - Struct(String), -} - -pub type Block = Vec; - -#[derive(Debug, Clone)] -pub enum Statement { - Block(Block), - Assign { - // left side - name: String, - declare_type: Option, - - // right side - value: Option>, - }, - Expression { - expr: Expression, - }, - If { - condition: Expression, - then_stmt: Block, - else_stmt: Block, - }, - While { - condition: Expression, - body: Vec, - }, - Return { - expr: Option, - }, -} - -#[derive(Debug, Clone)] -pub enum ConstExpr { - Number(i32), - String(String), -} - -impl fmt::Display for ConstExpr { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - ConstExpr::Number(n) => write!(f, "{}", n), - ConstExpr::String(s) => write!(f, "\"{}\"", s), - } - } -} - -#[derive(Debug, Clone)] -pub enum Expression { - Empty, - Binary { - op: BinaryOperator, - left: Box, - right: Box, - }, - Unary { - op: UnaryOperator, - operand: Box, - }, - Variable { - name: String, - expr_type: Option, - }, - Number { - value: i32, - }, - Call { - name: String, - args: Vec, - }, -} - -#[derive(Debug, Clone, PartialEq)] -pub enum BinaryOperator { - Add, - Sub, - Mul, - Div, - Eq, - Ne, - Lt, - Gt, - Le, - Ge, -} - -impl fmt::Display for BinaryOperator { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - BinaryOperator::Add => write!(f, "+"), - BinaryOperator::Sub => write!(f, "-"), - BinaryOperator::Mul => write!(f, "*"), - BinaryOperator::Div => write!(f, "/"), - BinaryOperator::Eq => write!(f, "=="), - BinaryOperator::Ne => write!(f, "!="), - BinaryOperator::Lt => write!(f, "<"), - BinaryOperator::Gt => write!(f, ">"), - BinaryOperator::Le => write!(f, "<="), - BinaryOperator::Ge => write!(f, ">="), - } - } -} - -#[derive(Debug, Clone, PartialEq)] -pub enum UnaryOperator { - Plus, - Minus, -} - -impl fmt::Display for UnaryOperator { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - UnaryOperator::Plus => write!(f, "+"), - UnaryOperator::Minus => write!(f, "-"), - } - } -} - -// ============================================================================ -// Parser -// ============================================================================ - -pub struct Parser { - tokens: Vec, - pos: usize, -} - -impl Parser { - pub fn new(tokens: Vec) -> Self { - Self { tokens, pos: 0 } - } - - fn error(&self, msg: &str) -> String { - let token = self.current(); - format!( - "Parser error at line {}, col {}: {}", - token.line, token.col, msg - ) - } - - fn current(&self) -> &Token { - self.tokens - .get(self.pos) - .unwrap_or_else(|| self.tokens.last().unwrap()) - } - - fn peek(&self, offset: usize) -> &Token { - self.tokens - .get(self.pos + offset) - .unwrap_or_else(|| self.tokens.last().unwrap()) - } - - fn advance(&mut self) -> &Token { - if self.pos < self.tokens.len() - 1 { - self.pos += 1; - } - self.current() - } - - fn expect(&mut self, expected: TokenType) -> Result { - let token = self.current().clone(); - if std::mem::discriminant(&token.token_type) != std::mem::discriminant(&expected) - { - return Err(self.error(&format!( - "Expected {:?}, got {:?}", - expected, token.token_type - ))); - } - self.advance(); - Ok(token) - } - - pub fn parse(&mut self) -> Result { - let mut declarations = Vec::new(); - - while !matches!(self.current().token_type, TokenType::Eof) { - declarations.push(self.parse_declaration()?); - } - - Ok(Program { declarations }) - } - - fn parse_declaration(&mut self) -> Result { - // check for an import - if let TokenType::Include = self.current().token_type { - self.advance(); - - let name = - if let TokenType::Identifier(id) = self.current().clone().token_type { - Some(id) - } else { - None - } - .ok_or(String::from("Expected identifier"))?; - - self.advance(); - self.expect(TokenType::Colon)?; - - let path = if let TokenType::String(id) = self.current().clone().token_type { - Some(id) - } else { - None - } - .ok_or(String::from("Expected string literal"))?; - - self.advance(); - return Ok(Declaration::Import { name, path }); - } - - self.expect(TokenType::Int)?; - - let name = match &self.current().token_type { - TokenType::Identifier(s) => s.clone(), - _ => return Err(self.error("Expected identifier")), - }; - self.advance(); - - match &self.current().token_type { - TokenType::LParen => { - // Function declaration - self.advance(); - let mut params = Vec::::new(); - - if !matches!(self.current().token_type, TokenType::RParen) { - self.expect(TokenType::Int)?; - - match &self.current().token_type { - TokenType::Identifier(s) => { - params.push(Parameter { - name: s.clone(), - param_type: Type::Int, - }); - self.advance(); - } - _ => return Err(self.error("Expected parameter name")), - } - - while matches!(self.current().token_type, TokenType::Comma) { - self.advance(); - self.expect(TokenType::Int)?; - - match &self.current().token_type { - TokenType::Identifier(s) => { - params.push(Parameter { - name: s.clone(), - param_type: Type::Int, - }); - self.advance(); - } - _ => return Err(self.error("Expected parameter name")), - } - } - } - - self.expect(TokenType::RParen)?; - let body = self.parse_block()?; - - Ok(Declaration::Function { - name, - params, - body, - return_type: Type::Int, - }) - } - _ => { - // Variable declaration - let init = if matches!(self.current().token_type, TokenType::Assign) { - self.advance(); - - if let TokenType::Number(n) = self.current().token_type { - self.advance(); - Some(ConstExpr::Number(n)) - } else { - return Err(self - .error("Expected constant in global variable declaration")); - } - } else { - None - }; - - self.expect(TokenType::Semicolon)?; - Ok(Declaration::Variable { name, init }) - } - } - } - - fn parse_block(&mut self) -> Result { - self.expect(TokenType::LBrace)?; - let mut statements = Vec::new(); - - while !matches!(self.current().token_type, TokenType::RBrace) { - statements.push(self.parse_statement()?); - } - - self.expect(TokenType::RBrace)?; - Ok(statements) - } - - fn parse_statement(&mut self) -> Result { - match &self.current().token_type { - TokenType::LBrace => Ok(Statement::Block(self.parse_block()?)), - TokenType::If => self.parse_if_stmt(), - TokenType::While => self.parse_while_stmt(), - TokenType::Return => self.parse_return_stmt(), - TokenType::Identifier(name) => { - let name = name.clone(); - - // peek ahead for open paren (func call expr) - if matches!(self.peek(1).token_type, TokenType::LParen) { - let expr = self.parse_expression()?; // a function call expr - self.expect(TokenType::Semicolon)?; - return Ok(Statement::Expression { expr }); - } - - self.advance(); // advance past identifier - - // assignment expression - if matches!(self.current().token_type, TokenType::Assign) { - self.advance(); - let expr = self.parse_expression()?; - - self.expect(TokenType::Semicolon)?; - Ok(Statement::Assign { - name, - value: Some(Box::new(expr)), - declare_type: None, - }) - } - // var expression - else { - self.expect(TokenType::Semicolon)?; - Ok(Statement::Expression { - expr: Expression::Variable { - name, - expr_type: None, - }, - }) - } - } - TokenType::Int => { - // Local variable declaration - self.advance(); - let name = match &self.current().token_type { - TokenType::Identifier(s) => s.clone(), - _ => return Err(self.error("Expected variable name")), - }; - self.advance(); - - let init = if matches!(self.current().token_type, TokenType::Assign) { - self.advance(); - Some(self.parse_expression()?) - } else { - None - }; - - self.expect(TokenType::Semicolon)?; - - // Convert to assignment expression statement - let expr = if let Some(init_expr) = init { - Statement::Assign { - name, - value: Some(Box::new(init_expr)), - declare_type: Some(Type::Int), - } - } else { - Statement::Assign { - name, - value: None, - declare_type: Some(Type::Int), - } - }; - - Ok(expr) - } - _ => { - let expr = if matches!(self.current().token_type, TokenType::Semicolon) { - Expression::Empty - } else { - self.parse_expression()? - }; - - self.expect(TokenType::Semicolon)?; - Ok(Statement::Expression { expr }) - } - } - } - - fn parse_if_stmt(&mut self) -> Result { - self.expect(TokenType::If)?; - self.expect(TokenType::LParen)?; - let condition = self.parse_expression()?; - self.expect(TokenType::RParen)?; - let then_stmt = self.parse_block()?; - - let else_stmt = if matches!(self.current().token_type, TokenType::Else) { - self.advance(); - self.parse_block()? - } else { - Vec::new() - }; - - Ok(Statement::If { - condition, - then_stmt, - else_stmt, - }) - } - - fn parse_while_stmt(&mut self) -> Result { - self.expect(TokenType::While)?; - self.expect(TokenType::LParen)?; - let condition = self.parse_expression()?; - self.expect(TokenType::RParen)?; - let body = self.parse_block()?; - - Ok(Statement::While { condition, body }) - } - - fn parse_return_stmt(&mut self) -> Result { - self.expect(TokenType::Return)?; - - let expr = if matches!(self.current().token_type, TokenType::Semicolon) { - None - } else { - Some(self.parse_expression()?) - }; - - self.expect(TokenType::Semicolon)?; - Ok(Statement::Return { expr }) - } - - fn parse_expression(&mut self) -> Result { - self.parse_comparison() - } - - fn parse_comparison(&mut self) -> Result { - let mut expr = self.parse_additive()?; - - while let Some(op) = match &self.current().token_type { - TokenType::Eq => Some(BinaryOperator::Eq), - TokenType::Ne => Some(BinaryOperator::Ne), - TokenType::Lt => Some(BinaryOperator::Lt), - TokenType::Gt => Some(BinaryOperator::Gt), - TokenType::Le => Some(BinaryOperator::Le), - TokenType::Ge => Some(BinaryOperator::Ge), - _ => None, - } { - self.advance(); - let right = Box::new(self.parse_additive()?); - expr = Expression::Binary { - op, - left: Box::new(expr), - right, - }; - } - - Ok(expr) - } - - fn parse_additive(&mut self) -> Result { - let mut expr = self.parse_multiplicative()?; - - while let Some(op) = match &self.current().token_type { - TokenType::Plus => Some(BinaryOperator::Add), - TokenType::Minus => Some(BinaryOperator::Sub), - _ => None, - } { - self.advance(); - let right = Box::new(self.parse_multiplicative()?); - expr = Expression::Binary { - op, - left: Box::new(expr), - right, - }; - } - - Ok(expr) - } - - fn parse_multiplicative(&mut self) -> Result { - let mut expr = self.parse_unary()?; - - while let Some(op) = match &self.current().token_type { - TokenType::Star => Some(BinaryOperator::Mul), - TokenType::Slash => Some(BinaryOperator::Div), - _ => None, - } { - self.advance(); - let right = Box::new(self.parse_unary()?); - expr = Expression::Binary { - op, - left: Box::new(expr), - right, - }; - } - - Ok(expr) - } - - fn parse_unary(&mut self) -> Result { - let op = match &self.current().token_type { - TokenType::Plus => Some(UnaryOperator::Plus), - TokenType::Minus => Some(UnaryOperator::Minus), - _ => None, - }; - - if let Some(op) = op { - self.advance(); - let operand = Box::new(self.parse_unary()?); - return Ok(Expression::Unary { op, operand }); - } - - self.parse_primary() - } - - fn parse_primary(&mut self) -> Result { - match &self.current().token_type.clone() { - TokenType::Number(n) => { - let value = *n; - self.advance(); - Ok(Expression::Number { value }) - } - TokenType::Identifier(name) => { - let name = name.clone(); - self.advance(); - - if matches!(self.current().token_type, TokenType::LParen) { - // Function call - self.advance(); - let mut args = Vec::new(); - - if !matches!(self.current().token_type, TokenType::RParen) { - args.push(self.parse_expression()?); - - while matches!(self.current().token_type, TokenType::Comma) { - self.advance(); - args.push(self.parse_expression()?); - } - } - - self.expect(TokenType::RParen)?; - Ok(Expression::Call { name, args }) - } else { - Ok(Expression::Variable { - name, - expr_type: None, - }) - } - } - TokenType::LParen => { - self.advance(); - let expr = self.parse_expression()?; - self.expect(TokenType::RParen)?; - Ok(expr) - } - _ => Err(self.error(&format!( - "Unexpected token: {:?}", - self.current().token_type - ))), - } - } -} diff --git a/c_compiler/src/registers.rs b/c_compiler/src/registers.rs deleted file mode 100644 index 1d042e9..0000000 --- a/c_compiler/src/registers.rs +++ /dev/null @@ -1,344 +0,0 @@ -use std::collections::HashMap; - -/// Register allocator for DSA assembly generation -/// Manages general-purpose registers (rg0-rgf) and handles stack spilling -pub struct RegisterAllocator { - /// Available general-purpose registers - available_registers: Vec, - - /// Maps variable names to their current location (register or stack offset) - variable_locations: HashMap, - - /// Maps registers to the variables they currently hold - register_contents: HashMap, - - /// Current stack offset for local variables (relative to bpr) - /// Starts at -4 (going downward from base pointer) - stack_offset: i32, - - /// Track which registers are currently in use - in_use: HashMap, -} - -#[derive(Debug, Clone)] -pub enum Location { - Register(String), - Stack(i32), // offset from bpr -} - -impl RegisterAllocator { - pub fn new() -> Self { - // Initialize with available GP registers (rg0-rgf = 16 registers) - let registers = vec![ - "rg0", "rg1", "rg2", "rg3", "rg4", "rg5", "rg6", "rg7", "rg8", "rg9", "rga", - "rgb", "rgc", "rgd", "rge", "rgf", - ] - .into_iter() - .map(String::from) - .collect(); - - RegisterAllocator { - available_registers: registers, - variable_locations: HashMap::new(), - register_contents: HashMap::new(), - stack_offset: -4, // Start at -4 (first local below saved bpr) - in_use: HashMap::new(), - } - } - - /// Allocate a temporary register for expression evaluation - /// Returns the register name and optionally assembly code to save it - pub fn alloc_temp(&mut self) -> Result<(String, Vec), String> { - let mut code = Vec::new(); - - // Try to find an unused register - for reg in &self.available_registers { - if !self.in_use.get(reg).unwrap_or(&false) { - self.in_use.insert(reg.clone(), true); - return Ok((reg.clone(), code)); - } - } - - // All registers in use - need to spill one - // Choose the first register with a variable we can spill - // Find a register to spill - let reg_to_spill = self - .available_registers - .iter() - .find(|reg| self.register_contents.contains_key(*reg)) - .cloned(); - - if let Some(reg) = reg_to_spill { - // Spill this variable to stack - let spill_code = self.spill_register(®)?; - code.extend(spill_code); - - self.in_use.insert(reg.clone(), true); - return Ok((reg, code)); - } - - Err("No registers available and nothing to spill".to_string()) - } - - /// Free a temporary register after use - /// NOTE: This will NOT free registers that contain variables! - /// Variables persist throughout their scope and must not be freed - pub fn free_temp(&mut self, reg: &str) { - // Check if this register contains a variable - if self.register_contents.contains_key(reg) { - // This register holds a variable - don't free it! - // Variables are only freed when they go out of scope via free_var() - return; - } - - // This is a true temporary - safe to free - self.in_use.insert(reg.to_string(), false); - } - - /// Allocate a register for a named variable - /// Returns the register and any necessary assembly code - pub fn alloc_var(&mut self, var_name: &str) -> Result<(String, Vec), String> { - // Check if variable already has a location - if let Some(location) = self.variable_locations.get(var_name).cloned() { - match location { - Location::Register(reg) => { - return Ok((reg.clone(), Vec::new())); - } - Location::Stack(offset) => { - // Variable is on stack, load it into a register - let (reg, mut code) = self.alloc_temp()?; - code.push(format!("\tldw bpr, {}, {}", reg, offset)); - - // Update location to register - self.variable_locations - .insert(var_name.to_string(), Location::Register(reg.clone())); - self.register_contents - .insert(reg.clone(), var_name.to_string()); - - return Ok((reg, code)); - } - } - } - - // Variable doesn't have a location yet, allocate a new register - let (reg, code) = self.alloc_temp()?; - self.variable_locations - .insert(var_name.to_string(), Location::Register(reg.clone())); - self.register_contents - .insert(reg.clone(), var_name.to_string()); - - Ok((reg, code)) - } - - /// Get the current location of a variable - pub fn get_var_location(&self, var_name: &str) -> Option<&Location> { - self.variable_locations.get(var_name) - } - - /// Load a variable into a register (allocating if necessary) - /// Returns the register and assembly code to load it - pub fn load_var(&mut self, var_name: &str) -> Result<(String, Vec), String> { - self.alloc_var(var_name) - } - - /// Store a value from a register into a variable - /// Updates tracking and returns any necessary assembly code - pub fn store_var(&mut self, var_name: &str, source_reg: &str) -> Vec { - let mut code = Vec::new(); - - // Check if variable already has a location - if let Some(location) = self.variable_locations.get(var_name) { - match location { - Location::Register(dest_reg) => { - if dest_reg != source_reg { - code.push(format!("\tmov {}, {}", source_reg, dest_reg)); - } - } - Location::Stack(offset) => { - code.push(format!("\tstw {}, bpr, {}", source_reg, offset)); - } - } - } else { - // Variable doesn't exist yet - try to allocate a register - if let Some(free_reg) = self.find_free_register() { - if &free_reg != source_reg { - code.push(format!("\tmov {}, {}", source_reg, free_reg)); - } - self.variable_locations - .insert(var_name.to_string(), Location::Register(free_reg.clone())); - self.register_contents - .insert(free_reg.clone(), var_name.to_string()); - self.in_use.insert(free_reg, true); - } else { - // No free registers - allocate on stack - code.push(format!("\tstw {}, bpr, {}", source_reg, self.stack_offset)); - self.variable_locations - .insert(var_name.to_string(), Location::Stack(self.stack_offset)); - self.stack_offset -= 4; // Move to next stack slot - } - } - - code - } - - /// Spill a register to the stack - /// Returns assembly code to perform the spill - fn spill_register(&mut self, reg: &str) -> Result, String> { - let mut code = Vec::new(); - - if let Some(var_name) = self.register_contents.get(reg).cloned() { - // Store register content to stack - code.push(format!("\tstw {}, bpr, {}", reg, self.stack_offset)); - - // Update variable location - self.variable_locations - .insert(var_name.clone(), Location::Stack(self.stack_offset)); - - // Remove from register tracking - self.register_contents.remove(reg); - - // Move to next stack slot - self.stack_offset -= 4; - } - - Ok(code) - } - - /// Find a free register (not currently in use) - fn find_free_register(&self) -> Option { - for reg in &self.available_registers { - if !self.in_use.get(reg).unwrap_or(&false) { - return Some(reg.clone()); - } - } - None - } - - /// Spill all registers to stack (useful before function calls) - pub fn spill_all(&mut self) -> Vec { - let mut code = Vec::new(); - - let regs_to_spill: Vec = self.register_contents.keys().cloned().collect(); - - for reg in regs_to_spill { - if let Ok(spill_code) = self.spill_register(®) { - code.extend(spill_code); - } - } - - code - } - - /// Get the total stack space needed for local variables - pub fn get_stack_size(&self) -> i32 { - -self.stack_offset // Convert negative offset to positive size - } - - /// Reset allocator for a new function - pub fn reset(&mut self) { - self.variable_locations.clear(); - self.register_contents.clear(); - self.stack_offset = -4; - self.in_use.clear(); - } - - /// Mark a variable as dead (no longer needed) - /// Frees its register if it's in one - pub fn free_var(&mut self, var_name: &str) { - if let Some(Location::Register(reg)) = self.variable_locations.get(var_name) { - let reg = reg.clone(); - self.register_contents.remove(®); - self.in_use.insert(reg, false); - } - self.variable_locations.remove(var_name); - } - - /// Get list of registers that contain variables and are in use - /// These need to be saved before function calls - pub fn get_caller_saved_registers(&self) -> Vec { - self.register_contents - .iter() - .filter(|(reg, _)| *self.in_use.get(*reg).unwrap_or(&false)) - .map(|(reg, _)| reg.clone()) - .collect() - } - - /// Save caller-saved registers before a function call - /// Returns assembly code to save them - pub fn save_caller_saved(&mut self) -> Vec { - let mut code = Vec::new(); - - // For simplicity, save all currently used registers - // In a more sophisticated compiler, you'd only save registers that are live - for (reg, var_name) in self.register_contents.clone() { - if *self.in_use.get(®).unwrap_or(&false) { - code.push(format!("\tpush {}", reg)); - } - } - - code - } - - /// Restore caller-saved registers after a function call - /// Returns assembly code to restore them - pub fn restore_caller_saved(&mut self, saved_regs: &[String]) -> Vec { - let mut code = Vec::new(); - - // Restore in reverse order (LIFO) - for reg in saved_regs.iter().rev() { - code.push(format!("\tpop {}", reg)); - } - - code - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_basic_allocation() { - let mut allocator = RegisterAllocator::new(); - - let (reg1, code1) = allocator.alloc_temp().unwrap(); - assert_eq!(code1.len(), 0); // No spill needed - assert_eq!(reg1, "rg0"); - - let (reg2, code2) = allocator.alloc_temp().unwrap(); - assert_eq!(code2.len(), 0); - assert_eq!(reg2, "rg1"); - - allocator.free_temp(®1); - - let (reg3, code3) = allocator.alloc_temp().unwrap(); - assert_eq!(code3.len(), 0); - assert_eq!(reg3, "rg0"); // Reuses freed register - } - - #[test] - fn test_variable_allocation() { - let mut allocator = RegisterAllocator::new(); - - let (reg, _) = allocator.alloc_var("x").unwrap(); - assert_eq!(reg, "rg0"); - - // Requesting same variable again should return same register - let (reg2, _) = allocator.alloc_var("x").unwrap(); - assert_eq!(reg2, "rg0"); - } - - #[test] - fn test_stack_allocation() { - let mut allocator = RegisterAllocator::new(); - - // Allocate all 16 registers - for i in 0..16 { - allocator.alloc_var(&format!("var{}", i)).unwrap(); - } - - // Next allocation should spill to stack - let (reg, code) = allocator.alloc_var("var16").unwrap(); - assert!(code.len() > 0); // Should have spill code - } -} diff --git a/compiler/src/codegen.rs b/compiler/src/codegen.rs deleted file mode 100644 index 6933e8a..0000000 --- a/compiler/src/codegen.rs +++ /dev/null @@ -1,756 +0,0 @@ -use std::collections::HashMap; -use std::hash::Hash; -use std::sync::LazyLock; -use std::sync::atomic::AtomicU32; -use std::time::SystemTime; - -use chrono::{DateTime, Local}; - -use crate::registers::{Location, RegisterAllocator}; -use crate::{block, cmd, comment, dsa}; - -use crate::parser::{ - BinaryOperator, CompilerError, ConstExpr, Declaration, Dependency, Expression, - Program, Statement, UnaryOperator, Variable, -}; - -pub struct CodeGenerator { - ast: Program, - imports: HashMap, - globals: Vec, - functions: Vec, - symbols: Vec, - allocator: RegisterAllocator, -} - -static GLOBAL_METHODS: LazyLock> = LazyLock::new(|| { - HashMap::from([ - // ("print", "print::print"), - // ("println", "print::println"), - // ("printnum", "print::print_num"), - // ("print_space", "print::print_whitespace"), - // ("print_newline", "print::print_newline"), - // ("print_char", "print::print_byte"), - // ("print_word", "print::print_word"), - // ("print_hex", "print::print_hex_word"), - ]) -}); - -fn import(name: &str, path: &str) -> String { - format!("include {name}: \"{}\"", path) -} - -impl CodeGenerator { - const RET: &'static str = "\tjmp _ret"; - - pub fn new(ast: Program) -> Self { - CodeGenerator { - ast, - imports: HashMap::new(), - globals: Vec::new(), - functions: Vec::new(), - symbols: Vec::new(), - allocator: RegisterAllocator::new(), - } - } - - pub fn include(&mut self, name: &str, path: &str) { - self.imports.insert(name.to_string(), path.to_string()); - } - - fn is_global(&self, name: &str) -> bool { - // Check if this variable is in the globals list - self.globals - .iter() - .any(|g| g.contains(&format!("dw {}:", name))) - } - - pub fn generate(&mut self) -> Result { - // always include the print library for debugging! - self.include("print", "./lib/io/print.dsa"); - - for block in self.ast.clone().declarations { - match block { - Declaration::Variable { - var: Variable { name, .. }, - .. - } => self.symbols.push(name), - Declaration::Function { name, .. } => self.symbols.push(name), - Declaration::Dependency(Dependency { name, .. }) => { - self.symbols.push(name) - } - } - } - - for block in self.ast.clone().declarations { - self.generate_block(block.clone())?; - } - - self.generate_layout() - } - - fn generate_layout(&mut self) -> Result { - let datetime: DateTime = SystemTime::now().into(); - Ok(dsa![ - "", - comment!("GENERATED BY DSC COMPILER"), - comment!(format!( - "Generated at {}", - datetime.format("%Y-%m-%d %H:%M:%S") - )), - "", - // imports - comment!("Imports"), - self.imports - .iter() - .map(|(k, v)| import(k, v)) - .collect::>() - .join("\n"), - "", - // reserved memory - comment!("Globals & Reserved Memory"), - self.globals.join("\n"), - "", - // entry point - comment!("Entry Point"), - "dw stack: 0x10000", - "db message: \"Process Exited with code:\"", - block! [ "_init" - dsa![ldw stack, bpr], - dsa![mov bpr, spr], - dsa![push zero], - dsa![call main], - dsa![call print::print_newline], - dsa![lwi message, rg0], - dsa![push rg0], - dsa![call print::print], - dsa![pop zero], - dsa![call print::print_hex_word], - dsa![pop zero], - dsa![hlt] - ], - "", - comment!("Return"), - block! [ "_ret" - dsa![mov bpr, spr], - dsa![pop bpr], - dsa![return] - ], - comment!("Compiled Code Starts..."), - // block! [ "main" - // dsa![push bpr], - // dsa![mov spr, bpr], - // dsa![lwi 67, rg1], - // dsa![stw rg1, spr, 8], - // dsa![mov bpr, spr], - // dsa![pop bpr], - // dsa![return] - // ], - self.functions.join("\n"), - ]) - } - - fn generate_global(&mut self, name: &str, init: Option) { - self.globals.push(format!( - "dw {}: {}", - name, - init.unwrap_or(ConstExpr::Number(0)) - )) - } - - fn generate_block(&mut self, block: Declaration) -> Result<(), CompilerError> { - match block { - Declaration::Variable { var, init, .. } => { - self.generate_global(&var.name, init) - } - Declaration::Function { - name, - return_type, - params, - body, - } => { - let func = self.generate_function(&name, ¶ms, &body).join("\n"); - - self.functions.push(format!("{func}\n")); - } - Declaration::Dependency(Dependency { name, path }) => { - self.imports.insert(name, path); - } - }; - - Ok(()) - } - - // Example: Generate code for a function - fn generate_function( - &mut self, - name: &str, - params: &[Variable], - body: &[Statement], - ) -> Vec { - let mut code = Vec::new(); - - // Reset allocator for new function - self.allocator.reset(); - - // Function prologue - code.push(format!("{}:", name)); - code.push("\tpush bpr".to_string()); - code.push("\tmov spr, bpr".to_string()); - code.push(String::new()); - - // Allocate parameters to registers or stack locations - for (i, param) in params.iter().enumerate() { - let offset = 8 + (i as i32 * 4); // Parameters start at bpr+8 - // Track that this parameter is at a stack location - let (reg, load_code) = self.allocator.alloc_var(¶m.name).unwrap(); - code.extend(load_code); - code.push(format!("\tldw bpr, {}, {}", reg, offset)); - } - - // Generate code for function body - for stmt in body { - let stmt_code = self.generate_statement(stmt).unwrap(); - code.extend(stmt_code); - } - - // automatically return at function end - if let Some(x) = code.last() - && x == Self::RET - { - } else { - code.push(Self::RET.to_string()); - } - - code - } - - // Example: Generate code for a statement - fn generate_statement( - &mut self, - stmt: &Statement, - ) -> Result, CompilerError> { - let mut code = Vec::new(); - - match stmt { - Statement::Declaration { var, value } => { - if let Some(expr) = value { - // Evaluate expression - let (result_reg, expr_code) = self.generate_expression(expr, true)?; - code.extend(expr_code); - - // Store result in variable - let store_code = self.allocator.store_var(&var.name, &result_reg); - code.extend(store_code); - - // Free temporary register - self.allocator.free_temp(&result_reg); - } else { - // Just declaring variable without initialization - self.allocator.alloc_var(&var.name)?; - } - } - - Statement::Break => unimplemented!(), - Statement::Continue => unimplemented!(), - - Statement::PtrWrite { ptr, value } => { - let (result_reg, expr_code) = self.generate_expression(value, true)?; - code.extend(expr_code); - - let (ptr_reg, ptr_code) = self.generate_expression(ptr, true)?; - code.extend(ptr_code); - - code.push(format!("\tstw {}, {}", result_reg, ptr_reg)); - - self.allocator.free_temp(&result_reg); - self.allocator.free_temp(&ptr_reg); - } - - Statement::Assign { varname, value } => { - // Evaluate expression - let (result_reg, expr_code) = self.generate_expression(value, true)?; - code.extend(expr_code); - - // Check if this is a global variable - if self.is_global(varname) { - // Store to global label - code.push(format!("\tstw {}, {}", result_reg, varname)); - } else { - // Store result in local variable - let store_code = self.allocator.store_var(varname, &result_reg); - code.extend(store_code); - } - - // Free temporary register - self.allocator.free_temp(&result_reg); - } - - Statement::Return(expr) => { - if let Some(e) = expr { - let (result_reg, expr_code) = self.generate_expression(e, true)?; - code.extend(expr_code); - code.push(format!("\tstw {}, bpr, 8", result_reg)); - code.push(format!("\tjmp _ret")); - self.allocator.free_temp(&result_reg); - } - } - - Statement::If { - condition, - then_stmt, - else_stmt, - } => { - // Generate condition - let (cond_reg, cond_code) = self.generate_expression(condition, true)?; - code.extend(cond_code); - - // Compare with zero - code.push(format!("\tcmp {}, zero", cond_reg)); - self.allocator.free_temp(&cond_reg); - - // Generate unique labels - let then_label = format!("_then_{}", self.get_unique_label()); - let else_label = format!("_else_{}", self.get_unique_label()); - let end_label = format!("_end_{}", self.get_unique_label()); - - // Jump to else if condition is false (equal to zero) - code.push(format!("\tjeq {}", else_label)); - - // Then block - code.push(format!("{}:", then_label)); - for s in then_stmt { - code.extend(self.generate_statement(s)?); - } - - if then_stmt.len() == 0 { - code.push("\tnop".to_string()); - } - - code.push(format!("\tjmp {}", end_label)); - - // Else block - code.push(format!("{}:", else_label)); - for s in else_stmt { - code.extend(self.generate_statement(s)?); - } - - if else_stmt.len() == 0 { - code.push("\tnop".to_string()); - } - - code.push(format!("{}:", end_label)); - } - - Statement::While { condition, body } => { - let loop_start = format!("_while_start_{}", self.get_unique_label()); - let loop_end = format!("_while_end_{}", self.get_unique_label()); - - code.push(format!("{}:", loop_start)); - - // Generate condition - let (cond_reg, cond_code) = self.generate_expression(condition, true)?; - code.extend(cond_code); - - code.push(format!("\tcmp {}, zero", cond_reg)); - self.allocator.free_temp(&cond_reg); - - code.push(format!("\tjeq {}", loop_end)); - - // Loop body - for s in body { - code.extend(self.generate_statement(s)?); - } - - code.push(format!("\tjmp {}", loop_start)); - code.push(format!("{}:", loop_end)); - } - - Statement::Loop(body) => { - let loop_start = format!("_loop_start_{}", self.get_unique_label()); - - code.push(format!("{}:", loop_start)); - - for s in body { - code.extend(self.generate_statement(s)?); - } - - code.push(format!("\tjmp {}", loop_start)); - } - - Statement::Expression { expr } => { - let (result_reg, expr_code) = self.generate_expression(expr, false)?; - code.extend(expr_code); - self.allocator.free_temp(&result_reg); - } - - Statement::Block(statements) => { - for s in statements { - code.extend(self.generate_statement(s)?); - } - } - } - - Ok(code) - } - - // Example: Generate code for an expression - // Returns (register containing result, assembly code) - fn generate_expression( - &mut self, - expr: &Expression, - use_result: bool, - ) -> Result<(String, Vec), CompilerError> { - let mut code = Vec::new(); - - // optimisation to prevent generating dead code! - if expr.is_pure() && !use_result { - return Ok((String::new(), code)); - } - - match expr { - Expression::StringLiteral(value) => { - let (reg, alloc_code) = self.allocator.alloc_temp()?; - code.extend(alloc_code); - - // write string into memory - let uuid = self.get_unique_label(); - code.push(format!("\tdb str_{uuid}: \"{value}\"")); - - // Load pointer to string - code.push(format!("\tlwi str_{uuid}, {reg}")); - - Ok((reg, code)) - } - - Expression::CharLiteral(value) => { - let (reg, alloc_code) = self.allocator.alloc_temp()?; - code.extend(alloc_code); - - // Load immediate value - code.push(format!("\tlli {}, {} // '{value}'", *value as u8, reg)); - - Ok((reg, code)) - } - - Expression::Number(value) => { - let (reg, alloc_code) = self.allocator.alloc_temp()?; - code.extend(alloc_code); - - // Load immediate value - code.push(format!("\tlli {}, {}", value & 0xFFFF, reg)); - if *value > 0xFFFF || *value < 0 { - code.push(format!("\tlui {}, {}", (value >> 16) & 0xFFFF, reg)); - } - - Ok((reg, code)) - } - - Expression::Variable { name, .. } => { - if self.is_global(&name.name) { - // Allocate a temporary register for the global - let (reg, alloc_code) = self.allocator.alloc_temp()?; - code.extend(alloc_code); - - // Load from global label - code.push(format!("\tldw {}, {}", name.name, reg)); - - Ok((reg, code)) - } else { - // Local variable - use existing allocator logic - let (reg, load_code) = self.allocator.load_var(&name.name)?; - code.extend(load_code); - Ok((reg, code)) - } - } - - Expression::Binary { op, left, right } => { - // Evaluate left operand - let (left_reg, left_code) = self.generate_expression(left, true)?; - code.extend(left_code); - - // Evaluate right operand - let (right_reg, right_code) = self.generate_expression(right, true)?; - code.extend(right_code); - - // Allocate result register - let (result_reg, result_alloc) = self.allocator.alloc_temp()?; - code.extend(result_alloc); - - // Generate operation - match op { - BinaryOperator::Add => { - code.push(format!( - "\tadd {}, {}, {}", - left_reg, right_reg, result_reg - )); - } - BinaryOperator::Sub => { - code.push(format!( - "\tsub {}, {}, {}", - left_reg, right_reg, result_reg - )); - } - BinaryOperator::Mul => { - self.include("maths", "./lib/maths/core.dsa"); - // Call multiply function - code.push(format!("\tpush {}", right_reg)); - code.push(format!("\tpush {}", left_reg)); - code.push("\tcall maths::multiply".to_string()); - code.push(format!("\tpop {}", result_reg)); - code.push("\tpop zero".to_string()); - } - // Comparison operators - return 1 (true) or 0 (false) - BinaryOperator::Eq => { - code.push(format!("\tcmp {}, {}", left_reg, right_reg)); - code.push(format!("\tlli 0, {}", result_reg)); - let end_label = format!("_cmp_end_{}", self.get_unique_label()); - code.push(format!("\tjne {}", end_label)); // If not equal, skip setting to 1 - code.push(format!("\tlli 1, {}", result_reg)); - code.push(format!("{}:", end_label)); - } - BinaryOperator::Ne => { - code.push(format!("\tcmp {}, {}", left_reg, right_reg)); - code.push(format!("\tlli 0, {}", result_reg)); - let end_label = format!("_cmp_end_{}", self.get_unique_label()); - code.push(format!("\tjeq {}", end_label)); // If equal, skip setting to 1 - code.push(format!("\tlli 1, {}", result_reg)); - code.push(format!("{}:", end_label)); - } - BinaryOperator::Lt => { - code.push(format!("\tcmp {}, {}", left_reg, right_reg)); - code.push(format!("\tlli 0, {}", result_reg)); - let end_label = format!("_cmp_end_{}", self.get_unique_label()); - code.push(format!("\tjge {}", end_label)); // If greater or equal, skip setting to 1 - code.push(format!("\tlli 1, {}", result_reg)); - code.push(format!("{}:", end_label)); - } - BinaryOperator::Le => { - code.push(format!("\tcmp {}, {}", left_reg, right_reg)); - code.push(format!("\tlli 0, {}", result_reg)); - let end_label = format!("_cmp_end_{}", self.get_unique_label()); - code.push(format!("\tjgt {}", end_label)); // If greater than, skip setting to 1 - code.push(format!("\tlli 1, {}", result_reg)); - code.push(format!("{}:", end_label)); - } - BinaryOperator::Gt => { - code.push(format!("\tcmp {}, {}", left_reg, right_reg)); - code.push(format!("\tlli 0, {}", result_reg)); - let end_label = format!("_cmp_end_{}", self.get_unique_label()); - code.push(format!("\tjle {}", end_label)); // If less or equal, skip setting to 1 - code.push(format!("\tlli 1, {}", result_reg)); - code.push(format!("{}:", end_label)); - } - BinaryOperator::Ge => { - code.push(format!("\tcmp {}, {}", left_reg, right_reg)); - code.push(format!("\tlli 0, {}", result_reg)); - let end_label = format!("_cmp_end_{}", self.get_unique_label()); - code.push(format!("\tjlt {}", end_label)); // If less than, skip setting to 1 - code.push(format!("\tlli 1, {}", result_reg)); - code.push(format!("{}:", end_label)); - } - _ => unimplemented!(), - } - - // Free operand registers (allocator will protect variables) - self.allocator.free_temp(&left_reg); - self.allocator.free_temp(&right_reg); - - Ok((result_reg, code)) - } - - Expression::Call { name, args } => { - // first evaluate all the args we're going to need - let mut arg_regs = Vec::new(); - for arg in args.iter().rev() { - let (arg_reg, arg_code) = self.generate_expression(arg, true)?; - code.extend(arg_code); - arg_regs.push(arg_reg); - } - - // Save caller-saved registers and track which ones we saved - // old method, inefficient. - // let saved_regs = self.allocator.get_caller_saved_registers(); - // for reg in &saved_regs { - // code.push(format!("\tpush {}", reg)); - // } - - // Save caller-saved registers and track which ones we saved - let saved_regs = self.allocator.get_caller_saved_registers(); - for reg in &saved_regs { - // spill variables to stack - code.extend(self.allocator.spill_register(reg).unwrap()); - } - - // Evaluate and push arguments in reverse order - for (i, arg_reg) in arg_regs.iter().enumerate() { - code.push(format!( - "\tpush {} // push arg {}", - arg_reg, - args.len() - 1 - i - )); - } - - // if GLOBAL_METHODS.contains_key(name.name.as_str()) { - // code.push(format!("\tcall {}", - // GLOBAL_METHODS[name.name.as_str()])); } else - if self.symbols.contains(&name.name) { - // Call local function - code.push(format!("\tcall {}", name)); - } else if let Some(ns) = name.namespace.clone() - && self.imports.contains_key(&ns) - { - code.push(format!("\tcall {}", name)); - } else { - return Err(CompilerError::Undefined(name.clone())); - } - - let result_reg: String; - - if use_result { - let (temp_result_reg, result_alloc) = self.allocator.alloc_temp()?; - result_reg = temp_result_reg; - - code.extend(result_alloc); - code.push(format!("\tpop {}", result_reg)); - - // Clean up arguments - if args.len() > 1 { - for _ in 0..(args.len() - 1) { - code.push("\tpop zero".to_string()); - } - } - } else { - result_reg = "zero".to_string(); - - // Clean up arguments - if args.len() > 0 { - for _ in 0..(args.len()) { - code.push("\tpop zero".to_string()); - } - } - } - - // Restore caller-saved registers in reverse order (LIFO) - // for reg in saved_regs.iter().rev() { - // code.push(format!("\tpop {}", reg)); - // } - - // Free argument registers - for reg in arg_regs { - self.allocator.free_temp(®); - } - - Ok((result_reg, code)) - } - - Expression::Unary { op, operand } => { - let (operand_reg, operand_code) = - self.generate_expression(operand, true)?; - code.extend(operand_code); - - let (result_reg, result_alloc) = self.allocator.alloc_temp()?; - code.extend(result_alloc); - - match op { - UnaryOperator::Minus => { - // Negate: result = 0 - operand - code.push(format!("\tsub zero, {}, {}", operand_reg, result_reg)); - } - UnaryOperator::Plus => { - // Just move - code.push(format!("\tmov {}, {}", operand_reg, result_reg)); - } - UnaryOperator::Dereference => { - code.push(format!("\tldw {}, {}", operand_reg, result_reg)); - } - UnaryOperator::Reference => { - code.extend(self.allocator.spill_register(&operand_reg)?); - code.push(format!( - "\tsubi bpr {} {}", - -(4 + self.allocator.get_stack_offset()), - result_reg - )) - } - } - - self.allocator.free_temp(&operand_reg); - Ok((result_reg, code)) - } - - Expression::Empty => Ok(("zero".to_string(), code)), - } - } - - // Helper for generating unique labels - fn get_unique_label(&mut self) -> String { - // You'd implement a counter here - static COUNTER: AtomicU32 = AtomicU32::new(0); - - let val = COUNTER.fetch_add(1, std::sync::atomic::Ordering::SeqCst); - (val + 1).to_string() - } -} - -/// Build a single string from any number of arguments. -/// Each argument must implement `Display` or be convertible to a string. -#[macro_export] -macro_rules! dsa { - ($($arg:expr),* $(,)?) => {{ - // Start with an empty String – we’ll grow it as we go. - use std::fmt::Write; - let mut s = ::std::string::String::new(); - $( - // `write!` is cheaper than `format!` for each element - // because it re‑uses the same buffer. - - write!(s, "{}\n", $arg).expect("write to String failed"); - )* - s - }}; -} - -// ──────────────────────── dsa! ──────────────────────── -// A tiny helper that just turns its token‑stream into a string. -// The trailing comma is kept – it’s part of the syntax you want. -#[macro_export] -macro_rules! cmd { - ($($tokens:tt)*) => {{ - // We’ll just stringify the tokens and return a String. - format!("{}", concat!(stringify!($tokens), "\n")) - }}; -} - -// ──────────────────────── block! ──────────────────────── -// Usage: -// -// let asm = block![ "name" -// dsa![mov rg0, rg1], -// dsa![add rg1, rg1] -// ]; -// -// `asm` is a `&'static str` containing: -// -// name: -// mov rg0, rg1 -// add rg1, rg1 -// -#[macro_export] -macro_rules! block { - // The first token must be a string literal – that’s the label. - ($label:literal $(dsa![$($ins:tt)*]),* ) => {{ - // Build a single string at compile time. - const CODE: &str = concat!( - $label, ":\n", - // Each `dsa!` call yields a string like `"mov rg0, rg1"`. - // We add a newline after each one to get the desired layout. - $(concat!("\t", stringify!($($ins)*), "\n")),* - ); - CODE - }}; -} - -#[macro_export] -macro_rules! comment { - ($text:expr) => {{ format!("// {}", $text) }}; -} diff --git a/compiler/src/lexer.rs b/compiler/src/lexer.rs deleted file mode 100644 index 5245bee..0000000 --- a/compiler/src/lexer.rs +++ /dev/null @@ -1,627 +0,0 @@ -use std::iter::Peekable; -use std::str::Chars; - -#[derive(Debug, PartialEq, Clone)] -pub enum Token { - // Keywords - Fn, - Let, - If, - Else, - Loop, - While, - Break, - Return, - Continue, - Include, - Static, - Const, - - // Identifiers and literals - Identifier(Name), - String(String), - Integer(u64), - Char(char), - - // Symbols - LeftParen, // ( - RightParen, // ) - LeftBrace, // { - RightBrace, // } - Semicolon, // ; - Colon, // : - Comma, // , - - // Operators - Plus, // + - Minus, // - - Star, // * - Amphersand, // & - Slash, // / - Assign, // = - EqualEqual, // == - Bang, // ! - BangEqual, // != - Less, // < - LessEqual, // <= - Greater, // > - GreaterEqual, // >= - RightArrow, // -> - - // Special - Eof, -} - -#[derive(Debug, PartialEq, Clone)] -pub struct Name { - pub name: String, - pub namespace: Option, -} - -use std::fmt; - -impl fmt::Display for Name { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if let Some(ref ns) = self.namespace { - write!(f, "{}::{}", ns, self.name) - } else { - write!(f, "{}", self.name) - } - } -} - -impl Token { - pub fn tt(&self) -> &str { - match self { - Token::Const => "Const", - Token::Static => "Static", - Token::Include => "Include", - Token::Fn => "Fn", - Token::If => "If", - Token::Let => "Let", - Token::Else => "Else", - Token::Loop => "Loop", - Token::While => "While", - Token::Break => "Break", - Token::Return => "Return", - Token::Continue => "Continue", - Token::Identifier(_) => "Identifier", - Token::String(_) => "String", - Token::Integer(_) => "UnsignedInt", - Token::Char(_) => "Char", - Token::LeftParen => "LeftParen", - Token::RightParen => "RightParen", - Token::LeftBrace => "LeftBrace", - Token::RightBrace => "RightBrace", - Token::Semicolon => "Semicolon", - Token::Colon => "Colon", - Token::Comma => "Comma", - Token::RightArrow => "RightArrow", - Token::Plus => "Plus", - Token::Minus => "Minus", - Token::Star => "Star", - Token::Amphersand => "Amphersand", - Token::Slash => "Slash", - Token::Assign => "Assign", - Token::EqualEqual => "EqualEqual", - Token::Bang => "Bang", - Token::BangEqual => "BangEqual", - Token::Less => "Less", - Token::LessEqual => "LessEqual", - Token::Greater => "Greater", - Token::GreaterEqual => "GreaterEqual", - Token::Eof => "Eof", - } - } -} - -#[derive(Debug)] -pub struct Lexer<'a> { - chars: Peekable>, - current: Option, - line: usize, -} - -impl<'a> Lexer<'a> { - pub fn new(input: &'a str) -> Self { - let mut chars = input.chars().peekable(); - let current = chars.next(); - - Lexer { - chars, - current, - line: 1, - } - } - - fn advance(&mut self) -> Option { - self.current = self.chars.next(); - self.current - } - - fn peek(&mut self) -> Option<&char> { - self.chars.peek() - } - - fn skip_whitespace(&mut self) { - while let Some(c) = self.current { - if !c.is_whitespace() { - break; - } - if c == '\n' { - self.line += 1; - } - self.advance(); - } - } - - fn skip_line_comment(&mut self) { - // Skip the two slashes - self.advance(); // first / - self.advance(); // second / - - // Skip until newline or EOF - while let Some(c) = self.current { - if c == '\n' { - self.line += 1; - self.advance(); - break; - } - self.advance(); - } - } - - fn skip_block_comment(&mut self) -> Result<(), String> { - // Skip the /* - self.advance(); // / - self.advance(); // * - - let start_line = self.line; - - // Look for */ - while let Some(c) = self.current { - if c == '\n' { - self.line += 1; - } - - if c == '*' { - if let Some(&next) = self.peek() { - if next == '/' { - self.advance(); // * - self.advance(); // / - return Ok(()); - } - } - } - - self.advance(); - } - - Err(format!( - "Unterminated block comment starting at line {}", - start_line - )) - } - - fn skip_whitespace_and_comments(&mut self) { - loop { - self.skip_whitespace(); - - // Check for comments - if let Some('/') = self.current { - if let Some(&next) = self.peek() { - match next { - '/' => { - self.skip_line_comment(); - continue; - } - '*' => { - if let Err(e) = self.skip_block_comment() { - eprintln!("Lexer error: {}", e); - } - continue; - } - _ => break, - } - } - } - - break; - } - } - - fn read_identifier(&mut self) -> String { - let mut ident = String::new(); - - // Include the current character if it's valid - if let Some(c) = self.current { - if c.is_alphabetic() || c == '_' { - ident.push(c); - } - } - - // Read remaining characters - while let Some(&c) = self.peek() { - if c.is_alphanumeric() || c == '_' { - self.advance(); - ident.push(c); - } else { - break; - } - } - - ident - } - - fn keyword_or_identifier(&mut self) -> Token { - let first_ident = self.read_identifier(); - - // Check if it's a keyword first (keywords can't have namespaces) - let keyword = match first_ident.as_str() { - "fn" => Some(Token::Fn), - "if" => Some(Token::If), - "else" => Some(Token::Else), - "while" => Some(Token::While), - "loop" => Some(Token::Loop), - "break" => Some(Token::Break), - "return" => Some(Token::Return), - "continue" => Some(Token::Continue), - "include" => Some(Token::Include), - "let" => Some(Token::Let), - "const" => Some(Token::Const), - "static" => Some(Token::Static), - _ => None, - }; - - if let Some(kw) = keyword { - return kw; - } - - // Not a keyword - check for namespace separator (::) - // We need to peek TWO characters ahead without consuming anything - if let Some(&':') = self.peek() { - // We see one colon, but we need to check if there's another one after it - // We can't peek two ahead directly, so we need a different approach - - // Save the current position by using a temporary peekable iterator - // Actually, we can't do that easily. Instead, let's just check: - // If we see ':', temporarily advance and check the next char - - // Create a temporary check - let mut temp_chars = self.chars.clone(); - let first_peek = temp_chars.next(); // This is the ':' we already saw - let second_peek = temp_chars.peek(); - - if let Some(&':') = second_peek { - // It's :: - consume both colons - self.advance(); // consume first : - self.advance(); // consume second : - - // Read the second identifier (the actual name) - let second_ident = self.read_identifier(); - - // Return namespaced identifier - return Token::Identifier(Name { - namespace: Some(first_ident), - name: second_ident, - }); - } - // else: It's a single colon (type annotation) - DON'T consume it - // Just fall through and return the identifier - } - - // No namespace separator - just a regular identifier - Token::Identifier(Name { - namespace: None, - name: first_ident, - }) - } - - fn read_number(&mut self) -> Result { - let current = self.current.unwrap(); - - // Check for hex (0x) or binary (0b) prefix - if current == '0' { - if let Some(&next_char) = self.peek() { - match next_char { - 'x' | 'X' => { - self.advance(); // consume '0' - self.advance(); // consume 'x' - return self.read_hex_number(); - } - 'b' | 'B' => { - self.advance(); // consume '0' - self.advance(); // consume 'b' - return self.read_binary_number(); - } - _ => {} - } - } - } - - // Read decimal number - self.read_decimal_number() - } - - fn read_decimal_number(&mut self) -> Result { - let mut num_str = String::new(); - - if let Some(c) = self.current { - num_str.push(c); - } - - while let Some(&c) = self.peek() { - if c.is_ascii_digit() { - self.advance(); - num_str.push(c); - } else { - break; - } - } - - num_str - .parse::() - .map_err(|_| format!("Invalid decimal number: {}", num_str)) - } - - fn read_hex_number(&mut self) -> Result { - let mut num_str = String::new(); - - // Read current character if it's a hex digit - if let Some(c) = self.current { - if c.is_ascii_hexdigit() { - num_str.push(c); - } - } - - while let Some(&c) = self.peek() { - if c.is_ascii_hexdigit() { - self.advance(); - num_str.push(c); - } else { - break; - } - } - - if num_str.is_empty() { - return Err("Invalid hexadecimal number: no digits after 0x".to_string()); - } - - u64::from_str_radix(&num_str, 16) - .map_err(|_| format!("Invalid hexadecimal number: {}", num_str)) - } - - fn read_binary_number(&mut self) -> Result { - let mut num_str = String::new(); - - // Read current character if it's a binary digit - if let Some(c) = self.current { - if c == '0' || c == '1' { - num_str.push(c); - } - } - - while let Some(&c) = self.peek() { - if c == '0' || c == '1' { - self.advance(); - num_str.push(c); - } else { - break; - } - } - - if num_str.is_empty() { - return Err("Invalid binary number: no digits after 0b".to_string()); - } - - u64::from_str_radix(&num_str, 2) - .map_err(|_| format!("Invalid binary number: {}", num_str)) - } - - fn read_string(&mut self) -> Result { - self.advance(); // Skip the opening quote - let mut s = String::new(); - - while let Some(c) = self.current { - if c == '"' { - return Ok(s); - } - - // Handle escape sequences - if c == '\\' { - self.advance(); - if let Some(escaped) = self.current { - let escaped_char = match escaped { - 'n' => '\n', - 't' => '\t', - 'r' => '\r', - '\\' => '\\', - '"' => '"', - _ => escaped, // For now, just use the character as-is - }; - s.push(escaped_char); - } else { - return Err("Unexpected end of string after escape".to_string()); - } - } else { - s.push(c); - } - - self.advance(); - } - - Err("Unterminated string literal".to_string()) - } - - fn match_next(&mut self, expected: char) -> bool { - match self.peek() { - Some(&c) if c == expected => { - self.advance(); - true - } - _ => false, - } - } - - fn scan_single_char_token(&mut self, c: char) -> Option { - match c { - '(' => Some(Token::LeftParen), - ')' => Some(Token::RightParen), - '{' => Some(Token::LeftBrace), - '}' => Some(Token::RightBrace), - ';' => Some(Token::Semicolon), - ',' => Some(Token::Comma), - '&' => Some(Token::Amphersand), - '+' => Some(Token::Plus), - '*' => Some(Token::Star), - _ => None, - } - } - - fn scan_operator(&mut self, c: char) -> Option { - match c { - '-' => Some(if self.match_next('>') { - Token::RightArrow - } else { - Token::Minus - }), - '!' => Some(if self.match_next('=') { - Token::BangEqual - } else { - Token::Bang - }), - '=' => Some(if self.match_next('=') { - Token::EqualEqual - } else { - Token::Assign - }), - '<' => Some(if self.match_next('=') { - Token::LessEqual - } else { - Token::Less - }), - '>' => Some(if self.match_next('=') { - Token::GreaterEqual - } else { - Token::Greater - }), - ':' => { - // Single colon (for type annotations) - // Note: :: is handled in keyword_or_identifier for namespaces - Some(Token::Colon) - } - '/' => { - // Check if it's a comment or division - if let Some(&next) = self.peek() { - if next == '/' || next == '*' { - // It's a comment, don't consume it here - // Let skip_whitespace_and_comments handle it - None - } else { - Some(Token::Slash) - } - } else { - Some(Token::Slash) - } - } - _ => None, - } - } - - pub fn next_token(&mut self) -> Token { - self.skip_whitespace_and_comments(); - - let Some(c) = self.current else { - return Token::Eof; - }; - - // Try single-character tokens first - if let Some(token) = self.scan_single_char_token(c) { - self.advance(); - return token; - } - - // Try operators (may be multi-character) - if let Some(token) = self.scan_operator(c) { - self.advance(); - return token; - } - - // String literals - if c == '"' { - let token = match self.read_string() { - Ok(s) => Token::String(s), - Err(e) => { - eprintln!("Lexer error on line {}: {}", self.line, e); - // Skip to next quote or end - while let Some(ch) = self.current { - if ch == '"' || ch == '\n' { - break; - } - self.advance(); - } - Token::String(String::new()) - } - }; - self.advance(); - return token; - } - - // Identifiers and keywords (including namespaced identifiers) - if c.is_alphabetic() || c == '_' { - let token = self.keyword_or_identifier(); - self.advance(); - return token; - } - - // Numbers (decimal, hex, binary) - if c.is_ascii_digit() { - let token = match self.read_number() { - Ok(num) => Token::Integer(num), - Err(e) => { - eprintln!("Lexer error on line {}: {}", self.line, e); - // Skip invalid number - while let Some(&ch) = self.peek() { - if !ch.is_alphanumeric() { - break; - } - self.advance(); - } - Token::Integer(0) - } - }; - self.advance(); - return token; - } - - // Unknown character - skip it - eprintln!( - "Lexer warning on line {}: Skipping unknown character '{}'", - self.line, c - ); - self.advance(); - self.next_token() - } -} - -impl<'a> Iterator for Lexer<'a> { - type Item = Token; - - fn next(&mut self) -> Option { - match self.next_token() { - Token::Eof => None, - token => Some(token), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_basic() { - // Placeholder test - assert!(true); - } -} -- 2.47.3 From a35cfbe8646ade06b40f47bb946fa21bee380dd4 Mon Sep 17 00:00:00 2001 From: zxq5 Date: Thu, 5 Feb 2026 01:09:14 +0000 Subject: [PATCH 26/53] updated compiler to support multiple frontends and backends --- Cargo.toml | 2 +- compiler/src/backend/dsa/codegen.rs | 738 ++++++++++++++++++ compiler/src/backend/dsa/mod.rs | 9 + compiler/src/{ => backend/dsa}/registers.rs | 66 +- compiler/src/backend/mod.rs | 13 + compiler/src/frontend/dsc/lexer.rs | 627 +++++++++++++++ compiler/src/frontend/dsc/mod.rs | 38 + compiler/src/{ => frontend/dsc}/parser.rs | 234 +----- .../src/frontend/dsc/semantic_analyser.rs | 13 + compiler/src/frontend/mod.rs | 15 + compiler/src/lib.rs | 78 +- compiler/src/main.rs | 2 +- compiler/src/model.rs | 213 +++++ compiler/src/semantic_analyser.rs | 13 - 14 files changed, 1737 insertions(+), 324 deletions(-) create mode 100644 compiler/src/backend/dsa/codegen.rs create mode 100644 compiler/src/backend/dsa/mod.rs rename compiler/src/{ => backend/dsa}/registers.rs (86%) create mode 100644 compiler/src/backend/mod.rs create mode 100644 compiler/src/frontend/dsc/lexer.rs create mode 100644 compiler/src/frontend/dsc/mod.rs rename compiler/src/{ => frontend/dsc}/parser.rs (78%) create mode 100644 compiler/src/frontend/dsc/semantic_analyser.rs create mode 100644 compiler/src/frontend/mod.rs create mode 100644 compiler/src/model.rs delete mode 100644 compiler/src/semantic_analyser.rs diff --git a/Cargo.toml b/Cargo.toml index b317ec7..9ae7c73 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ cargo-features = ["codegen-backend"] [workspace] -members = ["emulator", "common", "assembler", "dsa_editor", "compiler", "c_compiler"] +members = ["emulator", "common", "assembler", "dsa_editor", "compiler"] resolver = "3" [workspace.package] diff --git a/compiler/src/backend/dsa/codegen.rs b/compiler/src/backend/dsa/codegen.rs new file mode 100644 index 0000000..e6a4dca --- /dev/null +++ b/compiler/src/backend/dsa/codegen.rs @@ -0,0 +1,738 @@ +use std::collections::HashMap; +use std::sync::atomic::AtomicU32; +use std::time::SystemTime; + +use chrono::{DateTime, Local}; + +use super::registers::RegisterAllocator; +use crate::{block, comment, dsa}; + +use crate::model::{ + BinaryOperator, CompilerError, ConstExpr, Declaration, Dependency, Expression, + Program, Statement, UnaryOperator, Variable, +}; + +pub struct CodeGenerator { + ast: Program, + imports: HashMap, + globals: Vec, + functions: Vec, + symbols: Vec, + allocator: RegisterAllocator, +} + +fn import(name: &str, path: &str) -> String { + format!("include {name}: \"{}\"", path) +} + +impl CodeGenerator { + const RET: &'static str = "\tjmp _ret"; + + pub fn new(ast: Program) -> Self { + CodeGenerator { + ast, + imports: HashMap::new(), + globals: Vec::new(), + functions: Vec::new(), + symbols: Vec::new(), + allocator: RegisterAllocator::new(), + } + } + + pub fn include(&mut self, name: &str, path: &str) { + self.imports.insert(name.to_string(), path.to_string()); + } + + fn is_global(&self, name: &str) -> bool { + // Check if this variable is in the globals list + self.globals + .iter() + .any(|g| g.contains(&format!("dw {}:", name))) + } + + pub fn generate(&mut self) -> Result { + // always include the print library for debugging! + self.include("print", "./lib/io/print.dsa"); + + for block in self.ast.clone().declarations { + match block { + Declaration::Variable { + var: Variable { name, .. }, + .. + } => self.symbols.push(name), + Declaration::Function { name, .. } => self.symbols.push(name), + Declaration::Dependency(Dependency { name, .. }) => { + self.symbols.push(name) + } + } + } + + for block in self.ast.clone().declarations { + self.generate_block(block.clone())?; + } + + self.generate_layout() + } + + fn generate_layout(&mut self) -> Result { + let datetime: DateTime = SystemTime::now().into(); + Ok(dsa![ + "", + comment!("GENERATED BY DSC COMPILER"), + comment!(format!( + "Generated at {}", + datetime.format("%Y-%m-%d %H:%M:%S") + )), + "", + // imports + comment!("Imports"), + self.imports + .iter() + .map(|(k, v)| import(k, v)) + .collect::>() + .join("\n"), + "", + // reserved memory + comment!("Globals & Reserved Memory"), + self.globals.join("\n"), + "", + // entry point + comment!("Entry Point"), + "dw stack: 0x10000", + "db message: \"Process Exited with code:\"", + block! [ "_init" + dsa![ldw stack, bpr], + dsa![mov bpr, spr], + dsa![push zero], + dsa![call main], + dsa![call print::print_newline], + dsa![lwi message, rg0], + dsa![push rg0], + dsa![call print::print], + dsa![pop zero], + dsa![call print::print_hex_word], + dsa![pop zero], + dsa![hlt] + ], + "", + comment!("Return"), + block! [ "_ret" + dsa![mov bpr, spr], + dsa![pop bpr], + dsa![return] + ], + comment!("Compiled Code Starts..."), + // block! [ "main" + // dsa![push bpr], + // dsa![mov spr, bpr], + // dsa![lwi 67, rg1], + // dsa![stw rg1, spr, 8], + // dsa![mov bpr, spr], + // dsa![pop bpr], + // dsa![return] + // ], + self.functions.join("\n"), + ]) + } + + fn generate_global(&mut self, name: &str, init: Option) { + self.globals.push(format!( + "dw {}: {}", + name, + init.unwrap_or(ConstExpr::Number(0)) + )) + } + + fn generate_block(&mut self, block: Declaration) -> Result<(), CompilerError> { + match block { + Declaration::Variable { var, init, .. } => { + self.generate_global(&var.name, init) + } + Declaration::Function { + name, params, body, .. + } => { + let func = self.generate_function(&name, ¶ms, &body).join("\n"); + + self.functions.push(format!("{func}\n")); + } + Declaration::Dependency(Dependency { name, path }) => { + self.imports.insert(name, path); + } + }; + + Ok(()) + } + + // Example: Generate code for a function + fn generate_function( + &mut self, + name: &str, + params: &[Variable], + body: &[Statement], + ) -> Vec { + let mut code = Vec::new(); + + // Reset allocator for new function + self.allocator.reset(); + + // Function prologue + code.push(format!("{}:", name)); + code.push("\tpush bpr".to_string()); + code.push("\tmov spr, bpr".to_string()); + code.push(String::new()); + + // Allocate parameters to registers or stack locations + for (i, param) in params.iter().enumerate() { + let offset = 8 + (i as i32 * 4); // Parameters start at bpr+8 + // Track that this parameter is at a stack location + let (reg, load_code) = self.allocator.alloc_var(¶m.name).unwrap(); + code.extend(load_code); + code.push(format!("\tldw bpr, {}, {}", reg, offset)); + } + + // Generate code for function body + for stmt in body { + let stmt_code = self.generate_statement(stmt).unwrap(); + code.extend(stmt_code); + } + + // automatically return at function end + if let Some(x) = code.last() + && x == Self::RET + { + } else { + code.push(Self::RET.to_string()); + } + + code + } + + // Example: Generate code for a statement + fn generate_statement( + &mut self, + stmt: &Statement, + ) -> Result, CompilerError> { + let mut code = Vec::new(); + + match stmt { + Statement::Declaration { var, value } => { + if let Some(expr) = value { + // Evaluate expression + let (result_reg, expr_code) = self.generate_expression(expr, true)?; + code.extend(expr_code); + + // Store result in variable + let store_code = self.allocator.store_var(&var.name, &result_reg); + code.extend(store_code); + + // Free temporary register + self.allocator.free_temp(&result_reg); + } else { + // Just declaring variable without initialization + self.allocator.alloc_var(&var.name)?; + } + } + + Statement::Break => unimplemented!(), + Statement::Continue => unimplemented!(), + + Statement::PtrWrite { ptr, value } => { + let (result_reg, expr_code) = self.generate_expression(value, true)?; + code.extend(expr_code); + + let (ptr_reg, ptr_code) = self.generate_expression(ptr, true)?; + code.extend(ptr_code); + + code.push(format!("\tstw {}, {}", result_reg, ptr_reg)); + + self.allocator.free_temp(&result_reg); + self.allocator.free_temp(&ptr_reg); + } + + Statement::Assign { varname, value } => { + // Evaluate expression + let (result_reg, expr_code) = self.generate_expression(value, true)?; + code.extend(expr_code); + + // Check if this is a global variable + if self.is_global(varname) { + // Store to global label + code.push(format!("\tstw {}, {}", result_reg, varname)); + } else { + // Store result in local variable + let store_code = self.allocator.store_var(varname, &result_reg); + code.extend(store_code); + } + + // Free temporary register + self.allocator.free_temp(&result_reg); + } + + Statement::Return(expr) => { + if let Some(e) = expr { + let (result_reg, expr_code) = self.generate_expression(e, true)?; + code.extend(expr_code); + code.push(format!("\tstw {}, bpr, 8", result_reg)); + code.push(format!("\tjmp _ret")); + self.allocator.free_temp(&result_reg); + } + } + + Statement::If { + condition, + then_stmt, + else_stmt, + } => { + // Generate condition + let (cond_reg, cond_code) = self.generate_expression(condition, true)?; + code.extend(cond_code); + + // Compare with zero + code.push(format!("\tcmp {}, zero", cond_reg)); + self.allocator.free_temp(&cond_reg); + + // Generate unique labels + let then_label = format!("_then_{}", self.get_unique_label()); + let else_label = format!("_else_{}", self.get_unique_label()); + let end_label = format!("_end_{}", self.get_unique_label()); + + // Jump to else if condition is false (equal to zero) + code.push(format!("\tjeq {}", else_label)); + + // Then block + code.push(format!("{}:", then_label)); + for s in then_stmt { + code.extend(self.generate_statement(s)?); + } + + if then_stmt.len() == 0 { + code.push("\tnop".to_string()); + } + + code.push(format!("\tjmp {}", end_label)); + + // Else block + code.push(format!("{}:", else_label)); + for s in else_stmt { + code.extend(self.generate_statement(s)?); + } + + if else_stmt.len() == 0 { + code.push("\tnop".to_string()); + } + + code.push(format!("{}:", end_label)); + } + + Statement::While { condition, body } => { + let loop_start = format!("_while_start_{}", self.get_unique_label()); + let loop_end = format!("_while_end_{}", self.get_unique_label()); + + code.push(format!("{}:", loop_start)); + + // Generate condition + let (cond_reg, cond_code) = self.generate_expression(condition, true)?; + code.extend(cond_code); + + code.push(format!("\tcmp {}, zero", cond_reg)); + self.allocator.free_temp(&cond_reg); + + code.push(format!("\tjeq {}", loop_end)); + + // Loop body + for s in body { + code.extend(self.generate_statement(s)?); + } + + code.push(format!("\tjmp {}", loop_start)); + code.push(format!("{}:", loop_end)); + } + + Statement::Loop(body) => { + let loop_start = format!("_loop_start_{}", self.get_unique_label()); + + code.push(format!("{}:", loop_start)); + + for s in body { + code.extend(self.generate_statement(s)?); + } + + code.push(format!("\tjmp {}", loop_start)); + } + + Statement::Expression { expr } => { + let (result_reg, expr_code) = self.generate_expression(expr, false)?; + code.extend(expr_code); + self.allocator.free_temp(&result_reg); + } + + Statement::Block(statements) => { + for s in statements { + code.extend(self.generate_statement(s)?); + } + } + } + + Ok(code) + } + + // Example: Generate code for an expression + // Returns (register containing result, assembly code) + fn generate_expression( + &mut self, + expr: &Expression, + use_result: bool, + ) -> Result<(String, Vec), CompilerError> { + let mut code = Vec::new(); + + // optimisation to prevent generating dead code! + if expr.is_pure() && !use_result { + return Ok((String::new(), code)); + } + + match expr { + Expression::StringLiteral(value) => { + let (reg, alloc_code) = self.allocator.alloc_temp()?; + code.extend(alloc_code); + + // write string into memory + let uuid = self.get_unique_label(); + code.push(format!("\tdb str_{uuid}: \"{value}\"")); + + // Load pointer to string + code.push(format!("\tlwi str_{uuid}, {reg}")); + + Ok((reg, code)) + } + + Expression::CharLiteral(value) => { + let (reg, alloc_code) = self.allocator.alloc_temp()?; + code.extend(alloc_code); + + // Load immediate value + code.push(format!("\tlli {}, {} // '{value}'", *value as u8, reg)); + + Ok((reg, code)) + } + + Expression::Number(value) => { + let (reg, alloc_code) = self.allocator.alloc_temp()?; + code.extend(alloc_code); + + // Load immediate value + code.push(format!("\tlli {}, {}", value & 0xFFFF, reg)); + if *value > 0xFFFF || *value < 0 { + code.push(format!("\tlui {}, {}", (value >> 16) & 0xFFFF, reg)); + } + + Ok((reg, code)) + } + + Expression::Variable { name, .. } => { + if self.is_global(&name.name) { + // Allocate a temporary register for the global + let (reg, alloc_code) = self.allocator.alloc_temp()?; + code.extend(alloc_code); + + // Load from global label + code.push(format!("\tldw {}, {}", name.name, reg)); + + Ok((reg, code)) + } else { + // Local variable - use existing allocator logic + let (reg, load_code) = self.allocator.load_var(&name.name)?; + code.extend(load_code); + Ok((reg, code)) + } + } + + Expression::Binary { op, left, right } => { + // Evaluate left operand + let (left_reg, left_code) = self.generate_expression(left, true)?; + code.extend(left_code); + + // Evaluate right operand + let (right_reg, right_code) = self.generate_expression(right, true)?; + code.extend(right_code); + + // Allocate result register + let (result_reg, result_alloc) = self.allocator.alloc_temp()?; + code.extend(result_alloc); + + // Generate operation + match op { + BinaryOperator::Add => { + code.push(format!( + "\tadd {}, {}, {}", + left_reg, right_reg, result_reg + )); + } + BinaryOperator::Sub => { + code.push(format!( + "\tsub {}, {}, {}", + left_reg, right_reg, result_reg + )); + } + BinaryOperator::Mul => { + self.include("maths", "./lib/maths/core.dsa"); + // Call multiply function + code.push(format!("\tpush {}", right_reg)); + code.push(format!("\tpush {}", left_reg)); + code.push("\tcall maths::multiply".to_string()); + code.push(format!("\tpop {}", result_reg)); + code.push("\tpop zero".to_string()); + } + // Comparison operators - return 1 (true) or 0 (false) + BinaryOperator::Eq => { + code.push(format!("\tcmp {}, {}", left_reg, right_reg)); + code.push(format!("\tlli 0, {}", result_reg)); + let end_label = format!("_cmp_end_{}", self.get_unique_label()); + code.push(format!("\tjne {}", end_label)); // If not equal, skip setting to 1 + code.push(format!("\tlli 1, {}", result_reg)); + code.push(format!("{}:", end_label)); + } + BinaryOperator::Ne => { + code.push(format!("\tcmp {}, {}", left_reg, right_reg)); + code.push(format!("\tlli 0, {}", result_reg)); + let end_label = format!("_cmp_end_{}", self.get_unique_label()); + code.push(format!("\tjeq {}", end_label)); // If equal, skip setting to 1 + code.push(format!("\tlli 1, {}", result_reg)); + code.push(format!("{}:", end_label)); + } + BinaryOperator::Lt => { + code.push(format!("\tcmp {}, {}", left_reg, right_reg)); + code.push(format!("\tlli 0, {}", result_reg)); + let end_label = format!("_cmp_end_{}", self.get_unique_label()); + code.push(format!("\tjge {}", end_label)); // If greater or equal, skip setting to 1 + code.push(format!("\tlli 1, {}", result_reg)); + code.push(format!("{}:", end_label)); + } + BinaryOperator::Le => { + code.push(format!("\tcmp {}, {}", left_reg, right_reg)); + code.push(format!("\tlli 0, {}", result_reg)); + let end_label = format!("_cmp_end_{}", self.get_unique_label()); + code.push(format!("\tjgt {}", end_label)); // If greater than, skip setting to 1 + code.push(format!("\tlli 1, {}", result_reg)); + code.push(format!("{}:", end_label)); + } + BinaryOperator::Gt => { + code.push(format!("\tcmp {}, {}", left_reg, right_reg)); + code.push(format!("\tlli 0, {}", result_reg)); + let end_label = format!("_cmp_end_{}", self.get_unique_label()); + code.push(format!("\tjle {}", end_label)); // If less or equal, skip setting to 1 + code.push(format!("\tlli 1, {}", result_reg)); + code.push(format!("{}:", end_label)); + } + BinaryOperator::Ge => { + code.push(format!("\tcmp {}, {}", left_reg, right_reg)); + code.push(format!("\tlli 0, {}", result_reg)); + let end_label = format!("_cmp_end_{}", self.get_unique_label()); + code.push(format!("\tjlt {}", end_label)); // If less than, skip setting to 1 + code.push(format!("\tlli 1, {}", result_reg)); + code.push(format!("{}:", end_label)); + } + _ => unimplemented!(), + } + + // Free operand registers (allocator will protect variables) + self.allocator.free_temp(&left_reg); + self.allocator.free_temp(&right_reg); + + Ok((result_reg, code)) + } + + Expression::Call { name, args } => { + // first evaluate all the args we're going to need + let mut arg_regs = Vec::new(); + for arg in args.iter().rev() { + let (arg_reg, arg_code) = self.generate_expression(arg, true)?; + code.extend(arg_code); + arg_regs.push(arg_reg); + } + + // Save caller-saved registers and track which ones we saved + // old method, inefficient. + // let saved_regs = self.allocator.get_caller_saved_registers(); + // for reg in &saved_regs { + // code.push(format!("\tpush {}", reg)); + // } + + // Save caller-saved registers and track which ones we saved + let saved_regs = self.allocator.get_caller_saved_registers(); + for reg in &saved_regs { + // spill variables to stack + code.extend(self.allocator.spill_register(reg).unwrap()); + } + + // Evaluate and push arguments in reverse order + for (i, arg_reg) in arg_regs.iter().enumerate() { + code.push(format!( + "\tpush {} // push arg {}", + arg_reg, + args.len() - 1 - i + )); + } + + // if GLOBAL_METHODS.contains_key(name.name.as_str()) { + // code.push(format!("\tcall {}", + // GLOBAL_METHODS[name.name.as_str()])); } else + if self.symbols.contains(&name.name) { + // Call local function + code.push(format!("\tcall {}", name)); + } else if let Some(ns) = name.namespace.clone() + && self.imports.contains_key(&ns) + { + code.push(format!("\tcall {}", name)); + } else { + return Err(CompilerError::Undefined(name.clone())); + } + + let result_reg: String; + + if use_result { + let (temp_result_reg, result_alloc) = self.allocator.alloc_temp()?; + result_reg = temp_result_reg; + + code.extend(result_alloc); + code.push(format!("\tpop {}", result_reg)); + + // Clean up arguments + if args.len() > 1 { + for _ in 0..(args.len() - 1) { + code.push("\tpop zero".to_string()); + } + } + } else { + result_reg = "zero".to_string(); + + // Clean up arguments + if args.len() > 0 { + for _ in 0..(args.len()) { + code.push("\tpop zero".to_string()); + } + } + } + + // Restore caller-saved registers in reverse order (LIFO) + // for reg in saved_regs.iter().rev() { + // code.push(format!("\tpop {}", reg)); + // } + + // Free argument registers + for reg in arg_regs { + self.allocator.free_temp(®); + } + + Ok((result_reg, code)) + } + + Expression::Unary { op, operand } => { + let (operand_reg, operand_code) = + self.generate_expression(operand, true)?; + code.extend(operand_code); + + let (result_reg, result_alloc) = self.allocator.alloc_temp()?; + code.extend(result_alloc); + + match op { + UnaryOperator::Minus => { + // Negate: result = 0 - operand + code.push(format!("\tsub zero, {}, {}", operand_reg, result_reg)); + } + UnaryOperator::Plus => { + // Just move + code.push(format!("\tmov {}, {}", operand_reg, result_reg)); + } + UnaryOperator::Dereference => { + code.push(format!("\tldw {}, {}", operand_reg, result_reg)); + } + UnaryOperator::Reference => { + code.extend(self.allocator.spill_register(&operand_reg)?); + code.push(format!( + "\tsubi bpr {} {}", + -(4 + self.allocator.get_stack_offset()), + result_reg + )) + } + } + + self.allocator.free_temp(&operand_reg); + Ok((result_reg, code)) + } + + Expression::Empty => Ok(("zero".to_string(), code)), + } + } + + // Helper for generating unique labels + fn get_unique_label(&mut self) -> String { + // You'd implement a counter here + static COUNTER: AtomicU32 = AtomicU32::new(0); + + let val = COUNTER.fetch_add(1, std::sync::atomic::Ordering::SeqCst); + (val + 1).to_string() + } +} + +/// Build a single string from any number of arguments. +/// Each argument must implement `Display` or be convertible to a string. +#[macro_export] +macro_rules! dsa { + ($($arg:expr),* $(,)?) => {{ + // Start with an empty String – we’ll grow it as we go. + use std::fmt::Write; + let mut s = ::std::string::String::new(); + $( + // `write!` is cheaper than `format!` for each element + // because it re‑uses the same buffer. + + write!(s, "{}\n", $arg).expect("write to String failed"); + )* + s + }}; +} + +// ──────────────────────── dsa! ──────────────────────── +// A tiny helper that just turns its token‑stream into a string. +// The trailing comma is kept – it’s part of the syntax you want. +#[macro_export] +macro_rules! cmd { + ($($tokens:tt)*) => {{ + // We’ll just stringify the tokens and return a String. + format!("{}", concat!(stringify!($tokens), "\n")) + }}; +} + +// ──────────────────────── block! ──────────────────────── +// Usage: +// +// let asm = block![ "name" +// dsa![mov rg0, rg1], +// dsa![add rg1, rg1] +// ]; +// +// `asm` is a `&'static str` containing: +// +// name: +// mov rg0, rg1 +// add rg1, rg1 +// +#[macro_export] +macro_rules! block { + // The first token must be a string literal – that’s the label. + ($label:literal $(dsa![$($ins:tt)*]),* ) => {{ + // Build a single string at compile time. + const CODE: &str = concat!( + $label, ":\n", + // Each `dsa!` call yields a string like `"mov rg0, rg1"`. + // We add a newline after each one to get the desired layout. + $(concat!("\t", stringify!($($ins)*), "\n")),* + ); + CODE + }}; +} + +#[macro_export] +macro_rules! comment { + ($text:expr) => {{ format!("// {}", $text) }}; +} diff --git a/compiler/src/backend/dsa/mod.rs b/compiler/src/backend/dsa/mod.rs new file mode 100644 index 0000000..8ee13d7 --- /dev/null +++ b/compiler/src/backend/dsa/mod.rs @@ -0,0 +1,9 @@ +use crate::model::{CompilerError, Program}; + +mod codegen; +mod registers; + +pub fn generate_code(ast: &Program) -> Result { + let mut codegen = codegen::CodeGenerator::new(ast.clone()); + codegen.generate() +} diff --git a/compiler/src/registers.rs b/compiler/src/backend/dsa/registers.rs similarity index 86% rename from compiler/src/registers.rs rename to compiler/src/backend/dsa/registers.rs index e07949c..00f59e7 100644 --- a/compiler/src/registers.rs +++ b/compiler/src/backend/dsa/registers.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use crate::parser::CompilerError; +use crate::model::CompilerError; /// Register allocator for DSA assembly generation /// Manages general-purpose registers (rg0-rgf) and handles stack spilling @@ -147,7 +147,7 @@ impl RegisterAllocator { } /// Get the current location of a variable - pub fn get_var_location(&self, var_name: &str) -> Option<&Location> { + pub fn _get_var_location(&self, var_name: &str) -> Option<&Location> { self.variable_locations.get(var_name) } @@ -264,7 +264,7 @@ impl RegisterAllocator { } /// Spill all registers to stack (useful before function calls) - pub fn spill_all(&mut self) -> Vec { + pub fn _spill_all(&mut self) -> Vec { let mut code = Vec::new(); let regs_to_spill: Vec = self.register_contents.keys().cloned().collect(); @@ -284,7 +284,7 @@ impl RegisterAllocator { } /// Get the total stack space needed for local variables - pub fn get_stack_size(&self) -> i32 { + pub fn _get_stack_size(&self) -> i32 { -self.stack_offset // Convert negative offset to positive size } @@ -298,7 +298,7 @@ impl RegisterAllocator { /// Mark a variable as dead (no longer needed) /// Frees its register if it's in one - pub fn free_var(&mut self, var_name: &str) { + pub fn _free_var(&mut self, var_name: &str) { if let Some(Location::Register(reg)) = self.variable_locations.get(var_name) { let reg = reg.clone(); self.register_contents.remove(®); @@ -319,12 +319,12 @@ impl RegisterAllocator { /// Save caller-saved registers before a function call /// Returns assembly code to save them - pub fn save_caller_saved(&mut self) -> Vec { + pub fn _save_caller_saved(&mut self) -> Vec { let mut code = Vec::new(); // For simplicity, save all currently used registers // In a more sophisticated compiler, you'd only save registers that are live - for (reg, var_name) in self.register_contents.clone() { + for (reg, _) in self.register_contents.clone() { if *self.in_use.get(®).unwrap_or(&false) { code.push(format!("\tpush {}", reg)); } @@ -335,7 +335,7 @@ impl RegisterAllocator { /// Restore caller-saved registers after a function call /// Returns assembly code to restore them - pub fn restore_caller_saved(&mut self, saved_regs: &[String]) -> Vec { + pub fn _restore_caller_saved(&mut self, saved_regs: &[String]) -> Vec { let mut code = Vec::new(); // Restore in reverse order (LIFO) @@ -346,53 +346,3 @@ impl RegisterAllocator { code } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_basic_allocation() { - let mut allocator = RegisterAllocator::new(); - - let (reg1, code1) = allocator.alloc_temp().unwrap(); - assert_eq!(code1.len(), 0); // No spill needed - assert_eq!(reg1, "rg0"); - - let (reg2, code2) = allocator.alloc_temp().unwrap(); - assert_eq!(code2.len(), 0); - assert_eq!(reg2, "rg1"); - - allocator.free_temp(®1); - - let (reg3, code3) = allocator.alloc_temp().unwrap(); - assert_eq!(code3.len(), 0); - assert_eq!(reg3, "rg0"); // Reuses freed register - } - - #[test] - fn test_variable_allocation() { - let mut allocator = RegisterAllocator::new(); - - let (reg, _) = allocator.alloc_var("x").unwrap(); - assert_eq!(reg, "rg0"); - - // Requesting same variable again should return same register - let (reg2, _) = allocator.alloc_var("x").unwrap(); - assert_eq!(reg2, "rg0"); - } - - #[test] - fn test_stack_allocation() { - let mut allocator = RegisterAllocator::new(); - - // Allocate all 16 registers - for i in 0..16 { - allocator.alloc_var(&format!("var{}", i)).unwrap(); - } - - // Next allocation should spill to stack - let (reg, code) = allocator.alloc_var("var16").unwrap(); - assert!(code.len() > 0); // Should have spill code - } -} diff --git a/compiler/src/backend/mod.rs b/compiler/src/backend/mod.rs new file mode 100644 index 0000000..9e912e8 --- /dev/null +++ b/compiler/src/backend/mod.rs @@ -0,0 +1,13 @@ +use crate::model::{CompilerError, Program}; + +mod dsa; + +pub fn compiler_backend(ext: &str, ast: &Program) -> Result { + match ext { + "dsa" => Ok(dsa::generate_code(ast)?), + _ => Err(CompilerError::Generic(format!( + "File type {} not supported", + ext + ))), + } +} diff --git a/compiler/src/frontend/dsc/lexer.rs b/compiler/src/frontend/dsc/lexer.rs new file mode 100644 index 0000000..c41a62b --- /dev/null +++ b/compiler/src/frontend/dsc/lexer.rs @@ -0,0 +1,627 @@ +use std::iter::Peekable; +use std::str::Chars; + +#[derive(Debug, PartialEq, Clone)] +pub enum Token { + // Keywords + Fn, + Let, + If, + Else, + Loop, + While, + Break, + Return, + Continue, + Include, + Static, + Const, + + // Identifiers and literals + Identifier(Name), + String(String), + Integer(u64), + Char(char), + + // Symbols + LeftParen, // ( + RightParen, // ) + LeftBrace, // { + RightBrace, // } + Semicolon, // ; + Colon, // : + Comma, // , + + // Operators + Plus, // + + Minus, // - + Star, // * + Amphersand, // & + Slash, // / + Assign, // = + EqualEqual, // == + Bang, // ! + BangEqual, // != + Less, // < + LessEqual, // <= + Greater, // > + GreaterEqual, // >= + RightArrow, // -> + + // Special + Eof, +} + +use std::fmt; + +use crate::model::Name; + +impl fmt::Display for Name { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some(ref ns) = self.namespace { + write!(f, "{}::{}", ns, self.name) + } else { + write!(f, "{}", self.name) + } + } +} + +impl Token { + pub fn tt(&self) -> &str { + match self { + Token::Const => "Const", + Token::Static => "Static", + Token::Include => "Include", + Token::Fn => "Fn", + Token::If => "If", + Token::Let => "Let", + Token::Else => "Else", + Token::Loop => "Loop", + Token::While => "While", + Token::Break => "Break", + Token::Return => "Return", + Token::Continue => "Continue", + Token::Identifier(_) => "Identifier", + Token::String(_) => "String", + Token::Integer(_) => "UnsignedInt", + Token::Char(_) => "Char", + Token::LeftParen => "LeftParen", + Token::RightParen => "RightParen", + Token::LeftBrace => "LeftBrace", + Token::RightBrace => "RightBrace", + Token::Semicolon => "Semicolon", + Token::Colon => "Colon", + Token::Comma => "Comma", + Token::RightArrow => "RightArrow", + Token::Plus => "Plus", + Token::Minus => "Minus", + Token::Star => "Star", + Token::Amphersand => "Amphersand", + Token::Slash => "Slash", + Token::Assign => "Assign", + Token::EqualEqual => "EqualEqual", + Token::Bang => "Bang", + Token::BangEqual => "BangEqual", + Token::Less => "Less", + Token::LessEqual => "LessEqual", + Token::Greater => "Greater", + Token::GreaterEqual => "GreaterEqual", + Token::Eof => "Eof", + } + } +} + +#[derive(Debug)] +pub struct Lexer<'a> { + chars: Peekable>, + current: Option, + line: usize, +} + +impl<'a> Lexer<'a> { + pub fn new(input: &'a str) -> Self { + let mut chars = input.chars().peekable(); + let current = chars.next(); + + Lexer { + chars, + current, + line: 1, + } + } + + fn advance(&mut self) -> Option { + self.current = self.chars.next(); + self.current + } + + fn peek(&mut self) -> Option<&char> { + self.chars.peek() + } + + fn skip_whitespace(&mut self) { + while let Some(c) = self.current { + if !c.is_whitespace() { + break; + } + if c == '\n' { + self.line += 1; + } + self.advance(); + } + } + + fn skip_line_comment(&mut self) { + // Skip the two slashes + self.advance(); // first / + self.advance(); // second / + + // Skip until newline or EOF + while let Some(c) = self.current { + if c == '\n' { + self.line += 1; + self.advance(); + break; + } + self.advance(); + } + } + + fn skip_block_comment(&mut self) -> Result<(), String> { + // Skip the /* + self.advance(); // / + self.advance(); // * + + let start_line = self.line; + + // Look for */ + while let Some(c) = self.current { + if c == '\n' { + self.line += 1; + } + + if c == '*' { + if let Some(&next) = self.peek() { + if next == '/' { + self.advance(); // * + self.advance(); // / + return Ok(()); + } + } + } + + self.advance(); + } + + Err(format!( + "Unterminated block comment starting at line {}", + start_line + )) + } + + fn skip_whitespace_and_comments(&mut self) { + loop { + self.skip_whitespace(); + + // Check for comments + if let Some('/') = self.current { + if let Some(&next) = self.peek() { + match next { + '/' => { + self.skip_line_comment(); + continue; + } + '*' => { + if let Err(e) = self.skip_block_comment() { + eprintln!("Lexer error: {}", e); + } + continue; + } + _ => break, + } + } + } + + break; + } + } + + fn read_identifier(&mut self) -> String { + let mut ident = String::new(); + + // Include the current character if it's valid + if let Some(c) = self.current { + if c.is_alphabetic() || c == '_' { + ident.push(c); + } + } + + // Read remaining characters + while let Some(&c) = self.peek() { + if c.is_alphanumeric() || c == '_' { + self.advance(); + ident.push(c); + } else { + break; + } + } + + ident + } + + fn keyword_or_identifier(&mut self) -> Token { + let first_ident = self.read_identifier(); + + // Check if it's a keyword first (keywords can't have namespaces) + let keyword = match first_ident.as_str() { + "fn" => Some(Token::Fn), + "if" => Some(Token::If), + "else" => Some(Token::Else), + "while" => Some(Token::While), + "loop" => Some(Token::Loop), + "break" => Some(Token::Break), + "return" => Some(Token::Return), + "continue" => Some(Token::Continue), + "include" => Some(Token::Include), + "let" => Some(Token::Let), + "const" => Some(Token::Const), + "static" => Some(Token::Static), + _ => None, + }; + + if let Some(kw) = keyword { + return kw; + } + + // Not a keyword - check for namespace separator (::) + // We need to peek TWO characters ahead without consuming anything + if let Some(&':') = self.peek() { + // We see one colon, but we need to check if there's another one after it + // We can't peek two ahead directly, so we need a different approach + + // Save the current position by using a temporary peekable iterator + // Actually, we can't do that easily. Instead, let's just check: + // If we see ':', temporarily advance and check the next char + + // Create a temporary check + let mut temp_chars = self.chars.clone(); + let _ = temp_chars.next(); // This is the ':' we already saw + let second_peek = temp_chars.peek(); + + if let Some(&':') = second_peek { + // It's :: - consume both colons + self.advance(); // consume first : + self.advance(); // consume second : + + // Read the second identifier (the actual name) + let second_ident = self.read_identifier(); + + // Return namespaced identifier + return Token::Identifier(Name { + namespace: Some(first_ident), + name: second_ident, + }); + } + // else: It's a single colon (type annotation) - DON'T consume it + // Just fall through and return the identifier + } + + // No namespace separator - just a regular identifier + Token::Identifier(Name { + namespace: None, + name: first_ident, + }) + } + + fn read_number(&mut self) -> Result { + let current = self.current.unwrap(); + + // Check for hex (0x) or binary (0b) prefix + if current == '0' { + if let Some(&next_char) = self.peek() { + match next_char { + 'x' | 'X' => { + self.advance(); // consume '0' + self.advance(); // consume 'x' + return self.read_hex_number(); + } + 'b' | 'B' => { + self.advance(); // consume '0' + self.advance(); // consume 'b' + return self.read_binary_number(); + } + _ => {} + } + } + } + + // Read decimal number + self.read_decimal_number() + } + + fn read_decimal_number(&mut self) -> Result { + let mut num_str = String::new(); + + if let Some(c) = self.current { + num_str.push(c); + } + + while let Some(&c) = self.peek() { + if c.is_ascii_digit() { + self.advance(); + num_str.push(c); + } else { + break; + } + } + + num_str + .parse::() + .map_err(|_| format!("Invalid decimal number: {}", num_str)) + } + + fn read_hex_number(&mut self) -> Result { + let mut num_str = String::new(); + + // Read current character if it's a hex digit + if let Some(c) = self.current { + if c.is_ascii_hexdigit() { + num_str.push(c); + } + } + + while let Some(&c) = self.peek() { + if c.is_ascii_hexdigit() { + self.advance(); + num_str.push(c); + } else { + break; + } + } + + if num_str.is_empty() { + return Err("Invalid hexadecimal number: no digits after 0x".to_string()); + } + + u64::from_str_radix(&num_str, 16) + .map_err(|_| format!("Invalid hexadecimal number: {}", num_str)) + } + + fn read_binary_number(&mut self) -> Result { + let mut num_str = String::new(); + + // Read current character if it's a binary digit + if let Some(c) = self.current { + if c == '0' || c == '1' { + num_str.push(c); + } + } + + while let Some(&c) = self.peek() { + if c == '0' || c == '1' { + self.advance(); + num_str.push(c); + } else { + break; + } + } + + if num_str.is_empty() { + return Err("Invalid binary number: no digits after 0b".to_string()); + } + + u64::from_str_radix(&num_str, 2) + .map_err(|_| format!("Invalid binary number: {}", num_str)) + } + + fn read_string(&mut self) -> Result { + self.advance(); // Skip the opening quote + let mut s = String::new(); + + while let Some(c) = self.current { + if c == '"' { + return Ok(s); + } + + // Handle escape sequences + if c == '\\' { + self.advance(); + if let Some(escaped) = self.current { + let escaped_char = match escaped { + 'n' => '\n', + 't' => '\t', + 'r' => '\r', + '\\' => '\\', + '"' => '"', + _ => escaped, // For now, just use the character as-is + }; + s.push(escaped_char); + } else { + return Err("Unexpected end of string after escape".to_string()); + } + } else { + s.push(c); + } + + self.advance(); + } + + Err("Unterminated string literal".to_string()) + } + + fn match_next(&mut self, expected: char) -> bool { + match self.peek() { + Some(&c) if c == expected => { + self.advance(); + true + } + _ => false, + } + } + + fn scan_single_char_token(&mut self, c: char) -> Option { + match c { + '(' => Some(Token::LeftParen), + ')' => Some(Token::RightParen), + '{' => Some(Token::LeftBrace), + '}' => Some(Token::RightBrace), + ';' => Some(Token::Semicolon), + ',' => Some(Token::Comma), + '&' => Some(Token::Amphersand), + '+' => Some(Token::Plus), + '*' => Some(Token::Star), + _ => None, + } + } + + fn scan_operator(&mut self, c: char) -> Option { + match c { + '-' => Some(if self.match_next('>') { + Token::RightArrow + } else { + Token::Minus + }), + '!' => Some(if self.match_next('=') { + Token::BangEqual + } else { + Token::Bang + }), + '=' => Some(if self.match_next('=') { + Token::EqualEqual + } else { + Token::Assign + }), + '<' => Some(if self.match_next('=') { + Token::LessEqual + } else { + Token::Less + }), + '>' => Some(if self.match_next('=') { + Token::GreaterEqual + } else { + Token::Greater + }), + ':' => { + // Single colon (for type annotations) + // Note: :: is handled in keyword_or_identifier for namespaces + Some(Token::Colon) + } + '/' => { + // Check if it's a comment or division + if let Some(&next) = self.peek() { + if next == '/' || next == '*' { + // It's a comment, don't consume it here + // Let skip_whitespace_and_comments handle it + None + } else { + Some(Token::Slash) + } + } else { + Some(Token::Slash) + } + } + _ => None, + } + } + + pub fn next_token(&mut self) -> Token { + self.skip_whitespace_and_comments(); + + let Some(c) = self.current else { + return Token::Eof; + }; + + // Try single-character tokens first + if let Some(token) = self.scan_single_char_token(c) { + self.advance(); + return token; + } + + // Try operators (may be multi-character) + if let Some(token) = self.scan_operator(c) { + self.advance(); + return token; + } + + // Char literals + if c == '\'' { + let mut value = ' '; + self.advance(); + if let Some(ch) = self.current { + value = ch; + self.advance(); + } + if self.current == Some('\'') { + self.advance(); + return Token::Char(value); + } + eprintln!("Lexer error on line {}: Invalid char literal", self.line); + } + + // String literals + if c == '"' { + let token = match self.read_string() { + Ok(s) => Token::String(s), + Err(e) => { + eprintln!("Lexer error on line {}: {}", self.line, e); + // Skip to next quote or end + while let Some(ch) = self.current { + if ch == '"' || ch == '\n' { + break; + } + self.advance(); + } + Token::String(String::new()) + } + }; + self.advance(); + return token; + } + + // Identifiers and keywords (including namespaced identifiers) + if c.is_alphabetic() || c == '_' { + let token = self.keyword_or_identifier(); + self.advance(); + return token; + } + + // Numbers (decimal, hex, binary) + if c.is_ascii_digit() { + let token = match self.read_number() { + Ok(num) => Token::Integer(num), + Err(e) => { + eprintln!("Lexer error on line {}: {}", self.line, e); + // Skip invalid number + while let Some(&ch) = self.peek() { + if !ch.is_alphanumeric() { + break; + } + self.advance(); + } + Token::Integer(0) + } + }; + self.advance(); + return token; + } + + // Unknown character - skip it + eprintln!( + "Lexer warning on line {}: Skipping unknown character '{}'", + self.line, c + ); + self.advance(); + self.next_token() + } +} + +impl<'a> Iterator for Lexer<'a> { + type Item = Token; + + fn next(&mut self) -> Option { + match self.next_token() { + Token::Eof => None, + token => Some(token), + } + } +} diff --git a/compiler/src/frontend/dsc/mod.rs b/compiler/src/frontend/dsc/mod.rs new file mode 100644 index 0000000..b4a6666 --- /dev/null +++ b/compiler/src/frontend/dsc/mod.rs @@ -0,0 +1,38 @@ +use common::logging::log; + +use crate::model::{CompilerError, Program}; +use parser::{ParseResult, Parser}; +use semantic_analyser::Analyser; + +pub mod lexer; +pub mod parser; +pub mod semantic_analyser; + +pub fn generate_ast(input: &str) -> Result { + log("Tokenising Input..."); + + let lexer = lexer::Lexer::new(&input); + let tokens = lexer.collect::>(); + // println!("{tokens:?}"); + + log(&format!("Parsing {} Tokens...", tokens.len())); + + let mut parser = Parser::new(tokens); + let ast = match parser.parse() { + ParseResult::Accept(ast) => ast, + ParseResult::Reject(e) => return Err(e), + ParseResult::Deny => { + return Err(CompilerError::Generic("Parser used ::Deny".to_string())); + } + }; + // println!("{ast:#?}"); + + log("Analyzing AST..."); + log("Checking Type Information..."); + + let analyser = Analyser::new(); + analyser.analyse(ast.clone()).unwrap(); + + log("Type Checking Complete..."); + Ok(ast) +} diff --git a/compiler/src/parser.rs b/compiler/src/frontend/dsc/parser.rs similarity index 78% rename from compiler/src/parser.rs rename to compiler/src/frontend/dsc/parser.rs index 6b56d55..752fbba 100644 --- a/compiler/src/parser.rs +++ b/compiler/src/frontend/dsc/parser.rs @@ -1,6 +1,9 @@ -use crate::lexer::{Name, Token}; +use super::lexer::Token; +use crate::model::{ + BinaryOperator, Block, CompilerError, ConstExpr, Declaration, Dependency, Expression, + Program, Statement, TypeId, UnaryOperator, Variable, +}; use crate::{expect_tt, expect_value}; -use core::fmt; use std::ops::{ControlFlow, FromResidual, Try}; #[derive(Debug, Clone)] @@ -10,16 +13,6 @@ pub enum ParseResult { Reject(E), } -#[derive(Debug, Clone)] -pub enum CompilerError { - UnexpectedToken(Token), - UnexpectedEndOfInput, - UnexpectedCharacter(char), - Undefined(Name), - InvalidSyntax(String), - Generic(String), -} - pub struct Parser { tokens: Vec, idx: usize, @@ -86,7 +79,11 @@ impl Parser { let init = match value { Token::String(x) => Some(ConstExpr::String(x)), Token::Integer(x) => Some(ConstExpr::Number(x as i32)), - _ => return ParseResult::Reject(CompilerError::UnexpectedToken(value)), + _ => { + return ParseResult::Reject(CompilerError::UnexpectedToken( + value.tt().to_string(), + )); + } }; let _ = expect_tt!(self.next()?, Semicolon)?; @@ -141,7 +138,9 @@ impl Parser { body: self.parse_block()?, }) } else { - ParseResult::Reject(CompilerError::UnexpectedToken(self.peek_next()?)) + ParseResult::Reject(CompilerError::UnexpectedToken( + self.peek_next()?.tt().to_string(), + )) } } @@ -268,7 +267,7 @@ impl Parser { expr } else { return ParseResult::Reject(CompilerError::UnexpectedToken( - self.peek_next()?, + self.peek_next()?.tt().to_string(), )); }; @@ -341,7 +340,9 @@ impl Parser { }); } - ParseResult::Reject(CompilerError::UnexpectedToken(self.peek_next()?)) + ParseResult::Reject(CompilerError::UnexpectedToken( + self.peek_next()?.tt().to_string(), + )) } fn parse_expression(&mut self) -> ParseResult { @@ -463,7 +464,9 @@ impl Parser { let _ = expect_tt!(self.next()?, RightParen)?; ParseResult::Accept(expr) } - _ => ParseResult::Reject(CompilerError::UnexpectedToken(self.peek_next()?)), + _ => ParseResult::Reject(CompilerError::UnexpectedToken( + self.peek_next()?.tt().to_string(), + )), } } @@ -525,197 +528,6 @@ impl Parser { } } -#[derive(Debug, Clone)] -pub struct Program { - pub declarations: Vec, -} - -#[derive(Debug, Clone)] -pub enum Declaration { - Function { - name: String, - return_type: TypeId, - params: Vec, - body: Block, - }, - Variable { - var: Variable, - init: Option, - is_const: bool, - }, - Dependency(Dependency), -} - -#[derive(Debug, Clone)] -pub struct Dependency { - pub name: String, - pub path: String, -} - -#[derive(Debug, Clone)] -pub enum TypeId { - U8, - U16, - U32, - I8, - I16, - I32, - Char, - Void, - Ptr(Box), - Ref(Box), - Array(Box, usize), - Struct { name: Name, fields: Vec }, -} - -pub type Block = Vec; - -#[derive(Debug, Clone)] -pub struct Variable { - pub name: String, - pub type_id: TypeId, -} - -#[derive(Debug, Clone)] -pub enum Statement { - Block(Block), - Declaration { - var: Variable, - value: Option, - }, - Assign { - varname: String, - value: Expression, - }, - PtrWrite { - ptr: Expression, - value: Expression, - }, - Expression { - expr: Expression, - }, - If { - condition: Expression, - then_stmt: Block, - else_stmt: Block, - }, - While { - condition: Expression, - body: Vec, - }, - Loop(Block), - Break, - Continue, - Return(Option), -} - -#[derive(Debug, Clone)] -pub enum ConstExpr { - Number(i32), - String(String), -} - -impl fmt::Display for ConstExpr { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - ConstExpr::Number(n) => write!(f, "{}", n), - ConstExpr::String(s) => write!(f, "\"{}\"", s), - } - } -} - -#[derive(Debug, Clone)] -pub enum Expression { - Empty, - Binary { - op: BinaryOperator, - left: Box, - right: Box, - }, - Unary { - op: UnaryOperator, - operand: Box, - }, - Variable { - name: Name, - expr_type: Option, - }, - Call { - name: Name, - args: Vec, - }, - Number(isize), - StringLiteral(String), - CharLiteral(char), -} - -impl Expression { - pub fn is_pure(&self) -> bool { - match self { - Expression::Number(_) => true, - Expression::StringLiteral(_) => true, - Expression::CharLiteral(_) => true, - Expression::Call { name, args } => false, /* TODO: will require checking */ - // if the associated function - // body is pure - Expression::Binary { left, right, .. } => left.is_pure() && right.is_pure(), - Expression::Unary { op, operand } => operand.is_pure(), - Expression::Empty => true, - Expression::Variable { name, expr_type } => true, - } - } -} - -#[derive(Debug, Clone, PartialEq)] -pub enum BinaryOperator { - Add, - Sub, - Mul, - Div, - Eq, - Ne, - Lt, - Gt, - Le, - Ge, -} - -impl fmt::Display for BinaryOperator { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - BinaryOperator::Add => write!(f, "+"), - BinaryOperator::Sub => write!(f, "-"), - BinaryOperator::Mul => write!(f, "*"), - BinaryOperator::Div => write!(f, "/"), - BinaryOperator::Eq => write!(f, "=="), - BinaryOperator::Ne => write!(f, "!="), - BinaryOperator::Lt => write!(f, "<"), - BinaryOperator::Gt => write!(f, ">"), - BinaryOperator::Le => write!(f, "<="), - BinaryOperator::Ge => write!(f, ">="), - } - } -} - -#[derive(Debug, Clone, PartialEq)] -pub enum UnaryOperator { - Plus, - Minus, - Reference, - Dereference, -} - -impl fmt::Display for UnaryOperator { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - UnaryOperator::Plus => write!(f, "+"), - UnaryOperator::Minus => write!(f, "-"), - UnaryOperator::Dereference => write!(f, "*"), - UnaryOperator::Reference => write!(f, "&"), - } - } -} - impl ParseResult { pub fn accepted(&self) -> bool { matches!(self, ParseResult::Accept(_)) @@ -772,7 +584,7 @@ macro_rules! expect_tt { )+ _ => { // let expected = format!("[{}]", vec![$(stringify!($variant)),+].join(" | ")); - ParseResult::Reject(CompilerError::UnexpectedToken(token)) + ParseResult::Reject(CompilerError::UnexpectedToken(tt)) } } }}; @@ -784,7 +596,9 @@ macro_rules! expect_value { let tok = $expr; match tok.clone() { Token::$variant(value) => ParseResult::Accept(value), - _ => ParseResult::Reject(CompilerError::UnexpectedToken(tok)), + _ => { + ParseResult::Reject(CompilerError::UnexpectedToken(tok.tt().to_string())) + } } }}; } diff --git a/compiler/src/frontend/dsc/semantic_analyser.rs b/compiler/src/frontend/dsc/semantic_analyser.rs new file mode 100644 index 0000000..2b18e2c --- /dev/null +++ b/compiler/src/frontend/dsc/semantic_analyser.rs @@ -0,0 +1,13 @@ +use crate::model::{CompilerError, Program}; + +pub struct Analyser; + +impl Analyser { + pub fn new() -> Self { + Self + } + + pub fn analyse(&self, _ast: Program) -> Result<(), CompilerError> { + Ok(()) + } +} diff --git a/compiler/src/frontend/mod.rs b/compiler/src/frontend/mod.rs new file mode 100644 index 0000000..a17ba62 --- /dev/null +++ b/compiler/src/frontend/mod.rs @@ -0,0 +1,15 @@ +use crate::model::{CompilerError, Program}; + +mod c; +mod dsc; + +pub fn compiler_frontend(ext: &str, data: &str) -> Result { + match ext { + "dsc" => Ok(dsc::generate_ast(&data)?), + "c" => Ok(c::generate_ast(&data)?), + _ => Err(CompilerError::Generic(format!( + "File type {} not supported", + ext + ))), + } +} diff --git a/compiler/src/lib.rs b/compiler/src/lib.rs index c94ab1a..41fb48b 100644 --- a/compiler/src/lib.rs +++ b/compiler/src/lib.rs @@ -4,17 +4,12 @@ use std::path::Path; use common::logging::log; -use crate::{ - codegen::CodeGenerator, - parser::{ParseResult, Parser}, - semantic_analyser::Analyser, -}; +use crate::specialised::build_specialised; -mod codegen; -mod lexer; -mod parser; -mod registers; -mod semantic_analyser; +mod backend; +mod frontend; +mod model; +mod specialised; pub fn compile_file( input_path: &Path, @@ -22,43 +17,44 @@ pub fn compile_file( ) -> Result<(), Box> { let input = std::fs::read_to_string(input_path).expect("Failed to read input file"); - log("Tokenising Input..."); + let input_ext = input_path + .extension() + .and_then(|s| s.to_str()) + .unwrap_or(""); - let lexer = lexer::Lexer::new(&input); - let tokens = lexer.collect::>(); - // println!("{tokens:?}"); + // check if we're using a specialised compiler + if let Some(output) = build_specialised(input_ext, &input) { + let result = match output { + Ok(output) => output, + Err(err) => return Err(format!("Compilation failed: {err:?}").into()), + }; - log(&format!("Parsing {} Tokens...", tokens.len())); + std::fs::write(output_path, &result).expect("Failed to write output"); - let mut parser = Parser::new(tokens); - let ast = match parser.parse() { - ParseResult::Accept(ast) => ast, - ParseResult::Reject(e) => { - eprintln!("Error: {e:?}"); - return Err("Parsing error".into()); - } - ParseResult::Deny => { - panic!("Parser denied parsing") - } + log(&format!( + "Compilation Successful ✅ \n\tSource: {}\n\tOutput: {}\n", + input_path.display(), + output_path.display(), + )); + + return Ok(()); + } + + // Parse the input using the frontend, providing the file extension and data. + let ast = match frontend::compiler_frontend(input_ext, &input) { + Ok(ast) => ast, + Err(err) => return Err(format!("Compilation failed: {err:?}").into()), }; - // println!("{ast:#?}"); - log("Analyzing AST..."); - log("Checking Type Information..."); + let output_ext = output_path + .extension() + .and_then(|s| s.to_str()) + .unwrap_or(""); - let analyser = Analyser::new(); - analyser.analyse(ast.clone()).unwrap(); - - log("Generating Code..."); - - // Code Gen - let mut generator = CodeGenerator::new(ast); - let result = match generator.generate() { - Ok(code) => code, - Err(e) => { - eprintln!("Parsing error: {:?}", e); - return Err("Code generation error".into()); - } + // Generate the output using the backend with the parsed result. + let result = match backend::compiler_backend(output_ext, &ast) { + Ok(result) => result, + Err(err) => return Err(format!("Compilation failed: {err:?}").into()), }; // println!("{result}"); diff --git a/compiler/src/main.rs b/compiler/src/main.rs index c5eb367..3bfaf00 100644 --- a/compiler/src/main.rs +++ b/compiler/src/main.rs @@ -6,7 +6,7 @@ fn main() { // read from input file: syntax "c_compiler [output.dsa]" let args: Vec = std::env::args().collect(); if args.len() < 2 { - eprintln!("Usage: c_compiler [output.dsa]"); + eprintln!("Usage: c_compiler [output.dsa]"); return; } diff --git a/compiler/src/model.rs b/compiler/src/model.rs new file mode 100644 index 0000000..9c7e68e --- /dev/null +++ b/compiler/src/model.rs @@ -0,0 +1,213 @@ +use core::fmt; + +#[allow(unused)] +#[derive(Debug, Clone)] +pub enum CompilerError { + UnexpectedToken(String), + UnexpectedEndOfInput, + UnexpectedCharacter(char), + Undefined(Name), + InvalidSyntax(String), + Generic(String), +} + +#[derive(Debug, PartialEq, Clone)] +pub struct Name { + pub name: String, + pub namespace: Option, +} + +#[derive(Debug, Clone)] +pub struct Program { + pub declarations: Vec, +} + +#[allow(unused)] +#[derive(Debug, Clone)] +pub enum Declaration { + Function { + name: String, + return_type: TypeId, + params: Vec, + body: Block, + }, + Variable { + var: Variable, + init: Option, + is_const: bool, + }, + Dependency(Dependency), +} + +#[derive(Debug, Clone)] +pub struct Dependency { + pub name: String, + pub path: String, +} + +#[allow(unused)] +#[derive(Debug, Clone)] +pub enum TypeId { + U8, + U16, + U32, + I8, + I16, + I32, + Char, + Void, + Ptr(Box), + Ref(Box), + Array(Box, usize), + Struct { name: Name, fields: Vec }, +} + +pub type Block = Vec; + +#[allow(unused)] +#[derive(Debug, Clone)] +pub struct Variable { + pub name: String, + pub type_id: TypeId, +} + +#[allow(unused)] +#[derive(Debug, Clone)] +pub enum Statement { + Block(Block), + Declaration { + var: Variable, + value: Option, + }, + Assign { + varname: String, + value: Expression, + }, + PtrWrite { + ptr: Expression, + value: Expression, + }, + Expression { + expr: Expression, + }, + If { + condition: Expression, + then_stmt: Block, + else_stmt: Block, + }, + While { + condition: Expression, + body: Vec, + }, + Loop(Block), + Break, + Continue, + Return(Option), +} + +#[derive(Debug, Clone)] +pub enum ConstExpr { + Number(i32), + String(String), +} + +impl fmt::Display for ConstExpr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + ConstExpr::Number(n) => write!(f, "{}", n), + ConstExpr::String(s) => write!(f, "\"{}\"", s), + } + } +} + +#[allow(unused)] +#[derive(Debug, Clone)] +pub enum Expression { + Empty, + Binary { + op: BinaryOperator, + left: Box, + right: Box, + }, + Unary { + op: UnaryOperator, + operand: Box, + }, + Variable { + name: Name, + expr_type: Option, + }, + Call { + name: Name, + args: Vec, + }, + Number(isize), + StringLiteral(String), + CharLiteral(char), +} + +impl Expression { + pub fn is_pure(&self) -> bool { + match self { + Expression::Number(_) => true, + Expression::StringLiteral(_) => true, + Expression::CharLiteral(_) => true, + Expression::Call { .. } => false, + Expression::Binary { left, right, .. } => left.is_pure() && right.is_pure(), + Expression::Unary { operand, .. } => operand.is_pure(), + Expression::Empty => true, + Expression::Variable { .. } => true, + } + } +} + +#[allow(unused)] +#[derive(Debug, Clone, PartialEq)] +pub enum BinaryOperator { + Add, + Sub, + Mul, + Div, + Eq, + Ne, + Lt, + Gt, + Le, + Ge, +} + +impl fmt::Display for BinaryOperator { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + BinaryOperator::Add => write!(f, "+"), + BinaryOperator::Sub => write!(f, "-"), + BinaryOperator::Mul => write!(f, "*"), + BinaryOperator::Div => write!(f, "/"), + BinaryOperator::Eq => write!(f, "=="), + BinaryOperator::Ne => write!(f, "!="), + BinaryOperator::Lt => write!(f, "<"), + BinaryOperator::Gt => write!(f, ">"), + BinaryOperator::Le => write!(f, "<="), + BinaryOperator::Ge => write!(f, ">="), + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub enum UnaryOperator { + Plus, + Minus, + Reference, + Dereference, +} + +impl fmt::Display for UnaryOperator { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + UnaryOperator::Plus => write!(f, "+"), + UnaryOperator::Minus => write!(f, "-"), + UnaryOperator::Dereference => write!(f, "*"), + UnaryOperator::Reference => write!(f, "&"), + } + } +} diff --git a/compiler/src/semantic_analyser.rs b/compiler/src/semantic_analyser.rs deleted file mode 100644 index d8f3ce5..0000000 --- a/compiler/src/semantic_analyser.rs +++ /dev/null @@ -1,13 +0,0 @@ -use crate::parser::{CompilerError, Program}; - -pub struct Analyser; - -impl Analyser { - pub fn new() -> Self { - Self - } - - pub fn analyse(&self, ast: Program) -> Result<(), CompilerError> { - Ok(()) - } -} -- 2.47.3 From 89762b54e38447ff4d78eecbea78f46f2272af6e Mon Sep 17 00:00:00 2001 From: zxq5 Date: Thu, 5 Feb 2026 01:09:38 +0000 Subject: [PATCH 27/53] updated docs --- docs/DSA_Assembly_Reference.md | 944 ++++++++++++++++++ docs/DSA_ISA_Specification.md | 401 ++++++++ .../ideas => docs}/DSA_Project_Roadmap.md | 0 .../ideas => docs}/DSA_Project_Roadmap.pdf | Bin docs/IMPLEMENTATION_DISCREPANCIES.md | 638 ++++++++++++ {resources/ideas => docs}/design.rnote | Bin docs/inconsistencies.md | 149 +++ resources/ideas/dsa_assembly_reference.md | 427 -------- resources/ideas/dsa_binary_format.md | 10 - 9 files changed, 2132 insertions(+), 437 deletions(-) create mode 100644 docs/DSA_Assembly_Reference.md create mode 100644 docs/DSA_ISA_Specification.md rename {resources/ideas => docs}/DSA_Project_Roadmap.md (100%) rename {resources/ideas => docs}/DSA_Project_Roadmap.pdf (100%) create mode 100644 docs/IMPLEMENTATION_DISCREPANCIES.md rename {resources/ideas => docs}/design.rnote (100%) create mode 100644 docs/inconsistencies.md delete mode 100644 resources/ideas/dsa_assembly_reference.md delete mode 100644 resources/ideas/dsa_binary_format.md diff --git a/docs/DSA_Assembly_Reference.md b/docs/DSA_Assembly_Reference.md new file mode 100644 index 0000000..7f99089 --- /dev/null +++ b/docs/DSA_Assembly_Reference.md @@ -0,0 +1,944 @@ +# DSA Assembly Language Reference + +## Overview + +This document is the comprehensive reference for writing DSA assembly programs. It covers assembly syntax, pseudo-instructions, directives, the module system, calling conventions, and provides complete examples. + +**Related Documents:** +- For hardware instruction details and encoding: See *DSA ISA Specification* +- For build system and toolchain: See project documentation + +## Assembly Syntax + +### General Rules + +- **Case Insensitive:** Mnemonics can be uppercase or lowercase (`mov` = `MOV`) +- **Comments:** Use `//` for line comments or `/* */` for block comments +- **Labels:** Identifier followed by colon (e.g., `main:`, `loop:`) +- **Whitespace:** Flexible spacing between operands +- **Numbers:** + - Decimal: `100`, `255` + - Hexadecimal: `0x10`, `0xFFFF` + - Binary: `0b1010` (if supported by assembler) + +### Operand Order Convention + +DSA assembly uses **GAS-style syntax** (source → destination): + +```asm +mov rg0, rg1 ; Copy rg0 TO rg1 (destination is last) +add rg0, rg1, rg2 ; rg2 = rg0 + rg1 (destination is last) +``` + +For load/store with immediates: +```asm +lli 0x1234, rg0 ; Load immediate 0x1234 INTO rg0 +ldw rg0, rg1, 8 ; Load from (rg0+8) INTO rg1 +stw rg0, rg1, 8 ; Store rg0 TO address (rg1+8) +``` + +## Registers + +| Register(s) | Type | Description | Usage Notes | +|-------------|------|-------------|-------------| +| **rg0-rgf** | General | 16 general-purpose registers | Use for variables, temporaries | +| **acc** | Special | Accumulator | ⚠️ Volatile - pseudo-instructions may overwrite | +| **spr** | Special | Stack pointer | Points to top of stack | +| **bpr** | Special | Base pointer | Used for stack frames | +| **ret** | Special | Return address | Holds return address for functions | +| **zero** | Read-only | Always zero | Reads return 0, writes discarded | +| **pcx** | Read-only | Program counter | Cannot be written directly | +| **idr** | Privileged | Interrupt descriptor table | Kernel mode only | +| **mmr** | Privileged | Memory map register | Kernel mode only | +| **noreg** | Placeholder | No register | Used in encoding, triggers fault if accessed | + +**Register Conventions:** +- **acc**: Used by pseudo-instructions for temporary values - do not rely on it being preserved +- **rgf**: Used by label-addressing pseudo-instructions as a scratch register +- **rg0-rge**: Available for general use; calling convention defines which are preserved + +## Hardware Instructions + +This section shows assembly syntax. For encoding details, see the ISA Specification. + +### Data Movement + +```asm +mov src_reg, dest_reg ; Copy value from src_reg to dest_reg +movs src_reg, dest_reg ; Copy with sign extension +``` + +**Examples:** +```asm +mov rg0, rg1 ; rg1 = rg0 +movs acc, rg2 ; rg2 = sign_extend(acc) +``` + +### Memory Load Instructions + +```asm +ldb base_reg, dest_reg [, offset] ; Load byte (zero-extend) +ldbs base_reg, dest_reg [, offset] ; Load byte (sign-extend) +ldh base_reg, dest_reg [, offset] ; Load halfword (zero-extend) +ldhs base_reg, dest_reg [, offset] ; Load halfword (sign-extend) +ldw base_reg, dest_reg [, offset] ; Load word +``` + +**Offset:** Optional signed 16-bit offset (defaults to 0) + +**Examples:** +```asm +ldb rg0, rg1 ; Load byte from address in rg0 +ldw rg0, rg1, 8 ; Load word from (rg0 + 8) +ldhs rg2, rg3, -4 ; Load signed halfword from (rg2 - 4) +``` + +**Alignment Requirements:** +- `ldb/ldbs`: No alignment required +- `ldh/ldhs`: Must be 2-byte aligned +- `ldw`: Must be 4-byte aligned + +### Memory Store Instructions + +```asm +stb src_reg, base_reg [, offset] ; Store byte +sth src_reg, base_reg [, offset] ; Store halfword +stw src_reg, base_reg [, offset] ; Store word +``` + +**Examples:** +```asm +stb rg0, rg1 ; Store byte to address in rg1 +stw rg0, rg1, 12 ; Store word to (rg1 + 12) +sth acc, spr, -2 ; Store halfword to (spr - 2) +``` + +**Alignment Requirements:** Same as loads + +### Immediate Load Instructions + +```asm +lli immediate, dest_reg ; Load lower 16 bits (CLEARS upper 16!) +lui immediate, dest_reg ; Load upper 16 bits (preserves lower 16) +``` + +**⚠️ CRITICAL:** `lli` clears the upper 16 bits! Always use `lli` before `lui`. + +**Loading 32-bit Constants:** +```asm +lli 0x1234, rg0 ; rg0 = 0x00001234 +lui 0xABCD, rg0 ; rg0 = 0xABCD1234 +``` + +**Loading Addresses:** See `lwi` pseudo-instruction + +### Jump and Branch Instructions + +```asm +jmp addr [, offset_reg] ; Unconditional jump +jeq addr [, offset_reg] ; Jump if equal +jne addr [, offset_reg] ; Jump if not equal +jgt addr [, offset_reg] ; Jump if greater than +jge addr [, offset_reg] ; Jump if greater or equal +jlt addr [, offset_reg] ; Jump if less than +jle addr [, offset_reg] ; Jump if less or equal +``` + +**Jump Modes:** +```asm +; Absolute jump (using zero register) +jmp label, zero ; Jump to label address +jmp 0x4000, zero ; Jump to absolute address 0x4000 + +; Register-based jump +jmp 0, ret ; Jump to address in ret register +jmp 4, ret ; Jump to (ret + 4) + +; PC-relative (if assembler supports) +jeq loop_start ; Jump to loop_start if equal flag set +``` + +**Conditional Jumps:** Based on flags set by `cmp` instruction + +### Comparison + +```asm +cmp reg1, reg2 ; Compare reg1 with reg2, set flags +``` + +**Flags Set:** +- Equal: `reg1 == reg2` +- GreaterThan: `reg1 > reg2` +- LessThan: `reg1 < reg2` +- GreaterThanOrEqual: `reg1 >= reg2` +- LessThanOrEqual: `reg1 <= reg2` + +**Example:** +```asm +cmp rg0, zero ; Compare rg0 with 0 +jeq is_zero ; Branch if rg0 == 0 +jgt is_positive ; Branch if rg0 > 0 +jlt is_negative ; Branch if rg0 < 0 +``` + +### Arithmetic Instructions + +```asm +add src1, src2, dest ; dest = src1 + src2 +sub src1, src2, dest ; dest = src1 - src2 +iadd src, immediate, dest ; dest = src + immediate +isub src, immediate, dest ; dest = src - immediate +inc reg ; reg = reg + 1 +dec reg ; reg = reg - 1 +``` + +**Examples:** +```asm +add rg0, rg1, rg2 ; rg2 = rg0 + rg1 +sub rg0, rg1, rg2 ; rg2 = rg0 - rg1 +iadd rg0, 10, rg0 ; rg0 = rg0 + 10 +isub rg1, 5, rg2 ; rg2 = rg1 - 5 +inc spr ; spr = spr + 1 +dec spr ; spr = spr - 1 +``` + +**Note:** For `iadd`/`isub`, destination can be the same as source for in-place operations. + +### Bitwise Logical Operations + +```asm +and src1, src2, dest ; dest = src1 & src2 +or src1, src2, dest ; dest = src1 | src2 +xor src1, src2, dest ; dest = src1 ^ src2 +not src, dest ; dest = ~src +nand src1, src2, dest ; dest = ~(src1 & src2) +nor src1, src2, dest ; dest = ~(src1 | src2) +xnor src1, src2, dest ; dest = ~(src1 ^ src2) +``` + +**Examples:** +```asm +and rg0, rg1, rg2 ; rg2 = rg0 & rg1 +or rg0, rg1, rg2 ; rg2 = rg0 | rg1 +not rg0, rg1 ; rg1 = ~rg0 +xor rg0, rg0, rg0 ; rg0 = 0 (XOR register with itself) +``` + +### Shift Operations + +```asm +shl reg, shift_amount ; Shift left by amount (0-31) +shr reg, shift_amount ; Shift right by amount (0-31) +``` + +**Shift Amount:** +- Can be a literal: `shl rg0, 2` (shift by 2) +- Can be a register: `shl rg0, rg1` (shift by value in rg1, uses low 5 bits) + +**Examples:** +```asm +shl rg0, 2 ; rg0 = rg0 << 2 +shr rg1, 3 ; rg1 = rg1 >> 3 +shl rg0, rg1 ; rg0 = rg0 << (rg1 & 0x1F) +``` + +**Note:** Shift right is logical (zero-fill), not arithmetic + +### System and Control Instructions + +```asm +hlt ; Halt processor +nop ; No operation +int interrupt_code ; Trigger interrupt (8-bit code) +irt ; Return from interrupt +``` + +**Examples:** +```asm +hlt ; Stop execution +nop ; Do nothing (timing/alignment) +int 0x21 ; Trigger interrupt 0x21 +irt ; Return from interrupt handler +``` + +## Pseudo-Instructions + +Pseudo-instructions are assembly-level constructs that expand into one or more hardware instructions. + +### Data Definition Directives + +```asm +db label: value1 [, value2, ...] ; Define bytes +dh label: value1 [, value2, ...] ; Define halfwords (16-bit) +dw label: value1 [, value2, ...] ; Define words (32-bit) +``` + +**Examples:** +```asm +db message: "Hello, World!", 0 ; String with null terminator +db bytes: 0x01, 0x02, 0x03 ; Array of bytes +dh numbers: 1000, 2000, 3000 ; Array of halfwords +dw stack_base: 0x10000 ; Single word value +dw table: 0, 0, 0, 0 ; Array of 4 words +``` + +**String Encoding:** Strings are encoded as byte sequences with escape sequences: +- `\n` = newline (0x0A) +- `\t` = tab (0x09) +- `\r` = carriage return (0x0D) +- `\\` = backslash +- `\"` = double quote +- `\0` = null (0x00) + +### Memory Reservation Directives + +```asm +resb label: size ; Reserve 'size' bytes +resh label: size ; Reserve 'size' halfwords +resw label: size ; Reserve 'size' words +``` + +**Examples:** +```asm +resb buffer: 256 ; Reserve 256 bytes +resh array: 100 ; Reserve 100 halfwords (200 bytes) +resw heap: 1024 ; Reserve 1024 words (4096 bytes) +``` + +**Note:** Reserved memory is uninitialized (contents undefined). + +### Stack Operations + +```asm +push reg ; Push register onto stack +pop reg ; Pop stack into register +``` + +**Expansion:** +```asm +; push rg0 expands to: +iadd spr, 4, spr ; spr = spr + 4 (stack grows up) +stw rg0, spr, 0 ; Store rg0 to [spr] + +; pop rg0 expands to: +ldw spr, rg0, 0 ; Load [spr] into rg0 +isub spr, 4, spr ; spr = spr - 4 +``` + +**Note:** DSA stack grows upward (toward higher addresses). + +**Examples:** +```asm +push rg0 ; Save rg0 on stack +push rg1 ; Save rg1 on stack +; ... do work ... +pop rg1 ; Restore rg1 +pop rg0 ; Restore rg0 +``` + +### Load Address Pseudo-Instruction + +```asm +lwi label, dest_reg ; Load address of label into register +``` + +**Expansion:** +```asm +; lwi message, rg0 expands to: +lli message, rg0 ; Load lower 16 bits of address +lui message, rg0 ; Load upper 16 bits of address +``` + +**Example:** +```asm +db message: "Hello!", 0 + +lwi message, rg0 ; rg0 = address of message +ldb rg0, rg1 ; rg1 = first byte of message ('H') +``` + +### Memory Access with Labels + +Load and store instructions can use labels directly: + +```asm +ldb label, dest_reg [, offset] +ldh label, dest_reg [, offset] +ldw label, dest_reg [, offset] +stb src_reg, label [, offset] +sth src_reg, label [, offset] +stw src_reg, label [, offset] +``` + +**Expansion (uses rgf as scratch):** +```asm +; ldb buffer, rg2 expands to: +lli buffer, rgf ; Load lower 16 bits of buffer address +lui buffer, rgf ; Load upper 16 bits of buffer address +ldb rgf, rg2, 0 ; Load byte from address in rgf + +; stw rg1, current expands to: +lli current, rgf ; Load lower 16 bits of current address +lui current, rgf ; Load upper 16 bits of current address +stw rg1, rgf, 0 ; Store word to address in rgf +``` + +**⚠️ Important:** These pseudo-instructions use `rgf` as a scratch register! Do not use `rgf` for other purposes when using label-based memory access. + +**Examples:** +```asm +dw counter: 0 + +ldw counter, rg0 ; Load value of counter +iadd rg0, 1, rg0 ; Increment +stw rg0, counter ; Store back +``` + +### Function Call Pseudo-Instructions + +```asm +call namespace::function ; Call function from included module +return ; Return from function +``` + +**Expansion:** +```asm +; call print::print expands to: +lwi print::print, ret ; Load function address into ret +jmp 0, ret ; Jump to function (saves return in pcx) +; (The assembler/linker resolves namespace::function to address) + +; return expands to: +jmp 0, ret ; Jump to address in ret register +``` + +**Note:** The actual return address handling may be more complex depending on the calling convention. + +### Module System + +```asm +include namespace "path/to/file.dsa" +``` + +**Example:** +```asm +include print "lib/print.dsa" +include math "lib/math.dsa" + +; Can now call: +call print::print +call math::multiply +``` + +**Namespace Resolution:** +- Functions in included modules are accessible via `namespace::label` +- Namespace is the identifier before the filename +- Labels in included files are prefixed with the namespace + +## Calling Convention + +DSA uses a standard calling convention for function calls. + +### Stack Frame Layout + +``` +Higher Addresses +├─────────────┤ +│ Arg N │ ← spr + (8 + 4*(N-1)) +│ ... │ +│ Arg 2 │ ← spr + 16 +│ Arg 1 │ ← spr + 12 +│ Arg 0 │ ← spr + 8 (first argument) +├─────────────┤ +│ Ret Addr │ ← spr + 4 (return address) +├─────────────┤ +│ Old BPR │ ← spr + 0 (saved base pointer) +├─────────────┤ ← bpr, spr (current frame) +│ Locals │ (local variables, if any) +Lower Addresses +``` + +### Calling Sequence + +**Caller Responsibilities:** + +1. **Push arguments in reverse order** (last argument first): +```asm +push arg2 +push arg1 +push arg0 +``` + +2. **Call the function:** +```asm +call namespace::function +``` + +3. **Clean up arguments** after return: +```asm +pop zero ; Discard or retrieve arg0 +pop zero ; Discard arg1 +pop zero ; Discard arg2 +``` + +**Callee Responsibilities:** + +1. **Set up stack frame:** +```asm +function: + push bpr ; Save old base pointer + mov spr, bpr ; Establish new base pointer +``` + +2. **Access arguments:** +```asm + ldw bpr, rg0, 8 ; Load arg0 from spr+8 + ldw bpr, rg1, 12 ; Load arg1 from spr+12 + ldw bpr, rg2, 16 ; Load arg2 from spr+16 +``` + +3. **Execute function body:** +```asm + ; Function logic here + add rg0, rg1, acc ; Example: acc = arg0 + arg1 +``` + +4. **Store return value** (optional, overwrites arg0): +```asm + stw acc, bpr, 8 ; Store result where arg0 was +``` + +5. **Restore stack frame:** +```asm + mov bpr, spr ; Restore stack pointer + pop bpr ; Restore old base pointer +``` + +6. **Return to caller:** +```asm + return +``` + +### Complete Example + +```asm +; Function: add two numbers +; Args: arg0, arg1 +; Returns: sum in arg0 position + +add_function: + push bpr ; Save base pointer + mov spr, bpr ; Set up stack frame + + ldw bpr, rg0, 8 ; Load arg0 + ldw bpr, rg1, 12 ; Load arg1 + add rg0, rg1, acc ; acc = arg0 + arg1 + + stw acc, bpr, 8 ; Store result + + mov bpr, spr ; Restore stack + pop bpr ; Restore base pointer + return + +; Caller: +main: + lwi stack_base, bpr + mov bpr, spr + + lli 5, rg0 + lli 7, rg1 + + push rg1 ; Push arg1 (7) + push rg0 ; Push arg0 (5) + call local::add_function + pop rg2 ; Get result (12) + pop zero ; Discard arg1 + + hlt + +dw stack_base: 0x10000 +``` + +### Register Usage Conventions + +| Register(s) | Usage | Preserved? | +|-------------|-------|------------| +| rg0-rg3 | Function arguments, temporaries | No (caller-saved) | +| rg4-rge | Local variables | Yes (callee-saved if used) | +| rgf | Scratch (used by label addressing) | No | +| acc | Temporary calculations | No | +| spr | Stack pointer | Yes (must be restored) | +| bpr | Base pointer | Yes (must be restored) | +| ret | Return address | Managed by call/return | + +**Notes:** +- Functions should save and restore rg4-rge if they use them +- rg0-rg3 may be overwritten by called functions +- acc and rgf are volatile - assume they're overwritten + +## Complete Examples + +### Example 1: Multiplication Library + +```asm +// multiply.dsa +// Multiplies two numbers using repeated addition +// +// Usage: +// include multiply "multiply.dsa" +// push arg1 +// push arg0 +// call multiply::multiply +// pop result +// pop zero ; discard second argument + +multiply: + push bpr + mov spr, bpr + + ldw bpr, rg0, 8 ; Load multiplier + ldw bpr, rg1, 12 ; Load multiplicand + + lli 0, acc ; Initialize result to 0 + +loop_start: + add acc, rg0, acc ; acc += multiplier + dec rg1 ; multiplicand-- + + cmp rg1, zero + jgt loop_start ; Continue if multiplicand > 0 + + stw acc, bpr, 8 ; Store result for caller + + mov bpr, spr + pop bpr + return +``` + +### Example 2: Print Library + +```asm +// print.dsa +// Prints null-terminated string to display memory +// +// Usage: +// include print "print.dsa" +// +// push string_address +// call print::print +// pop zero +// +// call print::reset ; Reset cursor (no args) + +dw display: 0x20000 ; Display memory base address +dw current: 0x20000 ; Current cursor position + +// Print function +print: + push bpr + mov spr, bpr + + ldw bpr, rg0, 8 ; Get string address argument + ldw current, rg1 ; Get current cursor position + +print_loop: + ldb rg0, acc ; Load character + stb acc, rg1 ; Store to display + + iadd rg0, 1, rg0 ; Advance string pointer + iadd rg1, 1, rg1 ; Advance cursor + + cmp acc, zero ; Check for null terminator + jne print_loop ; Continue if not null + + stw rg1, current ; Save cursor position + + mov bpr, spr + pop bpr + return + +// Reset cursor function +reset: + push bpr + mov spr, bpr + + ldw display, rg1 ; Load display base + stw rg1, current ; Reset cursor to start + + mov bpr, spr + pop bpr + return +``` + +### Example 3: Main Program + +```asm +// main.dsa +// Demonstrates using included libraries + +include print "./print.dsa" + +dw stack: 0x10000 +db string: "'To confuse your enemy, you must first confuse yourself' - Probably Sun Tzu.", 0 + +init: + // Set up stack + ldw stack, bpr + mov bpr, spr + +start: + // Load string address + lwi string, rg1 + + // Call print function + push rg1 + call print::print + pop rg1 ; Clean up (rg1 now contains arg we passed) + + hlt +``` + +### Example 4: Conditional Logic + +```asm +// Demonstrates comparisons and branching + +dw value: 42 + +main: + ldw value, rg0 + + cmp rg0, zero + jeq is_zero + jgt is_positive + jlt is_negative + +is_zero: + // Handle zero case + lwi zero_msg, rg1 + jmp print_and_exit + +is_positive: + // Handle positive case + lwi positive_msg, rg1 + jmp print_and_exit + +is_negative: + // Handle negative case + lwi negative_msg, rg1 + jmp print_and_exit + +print_and_exit: + push rg1 + call print::print + pop zero + hlt + +db zero_msg: "Value is zero", 0 +db positive_msg: "Value is positive", 0 +db negative_msg: "Value is negative", 0 +``` + +### Example 5: Loop with Counter + +```asm +// Count from 0 to 9 + +dw stack: 0x10000 + +main: + ldw stack, bpr + mov bpr, spr + + lli 0, rg0 ; Counter = 0 + lli 10, rg1 ; Limit = 10 + +loop: + // Do something with counter in rg0 + push rg0 + call process_value + pop zero + + inc rg0 ; Counter++ + cmp rg0, rg1 ; Compare with limit + jlt loop ; Loop if counter < limit + + hlt + +process_value: + push bpr + mov spr, bpr + + ldw bpr, rg0, 8 ; Get value + ; Process value here... + + mov bpr, spr + pop bpr + return +``` + +## Best Practices + +### 1. Stack Management +- Always balance push/pop operations +- Set up stack frame in every function +- Clean up arguments after function calls +- Use `pop zero` to discard unwanted values + +### 2. Register Usage +- Don't rely on `acc` being preserved +- Don't use `rgf` for variables (used by label addressing) +- Save callee-saved registers if you modify them +- Use `zero` register for zero constants + +### 3. Memory Access +- Ensure proper alignment for halfword/word access +- Use label-based addressing for clearer code +- Check that labels are defined before use + +### 4. Function Design +- Document calling convention in comments +- Validate input arguments when appropriate +- Use consistent parameter order +- Return values via stack or designated register + +### 5. Code Organization +- Use meaningful label names +- Comment complex operations +- Group related functions in modules +- Use includes for code reuse + +### 6. Performance +- Minimize memory accesses (use registers) +- Avoid unnecessary comparisons +- Use shifts for multiplication/division by powers of 2 +- Consider instruction pipelining if supported + +## Assembler Directives + +### Alignment (if supported) +```asm +.align 4 ; Align to 4-byte boundary +.align 2 ; Align to 2-byte boundary +``` + +### Origin (if supported) +```asm +.org 0x1000 ; Set location counter to 0x1000 +``` + +### Section Control (if supported) +```asm +.text ; Code section +.data ; Data section +.bss ; Uninitialized data section +``` + +**Note:** Assembler directive support depends on the specific DSA assembler implementation. + +## Common Patterns + +### Loading 32-bit Constants +```asm +lli lower_16_bits, reg +lui upper_16_bits, reg +``` + +### Zero a Register +```asm +mov zero, reg ; Method 1 +xor reg, reg, reg ; Method 2 +lli 0, reg ; Method 3 +``` + +### Copy Memory +```asm +ldw src_addr, rg0 ; Load from source +stw rg0, dest_addr ; Store to destination +``` + +### Multiply by Power of 2 +```asm +shl reg, 3 ; Multiply by 8 (2^3) +``` + +### Divide by Power of 2 +```asm +shr reg, 2 ; Divide by 4 (2^2) +``` + +### Boolean NOT +```asm +cmp reg, zero +jeq was_zero ; If reg == 0, result is 1 +lli 0, reg +jmp done +was_zero: +lli 1, reg +done: +``` + +### Min/Max +```asm +; max(rg0, rg1) -> rg2 +mov rg0, rg2 ; Assume rg0 is max +cmp rg0, rg1 +jge done +mov rg1, rg2 ; rg1 was larger +done: +``` + +## Troubleshooting + +### Common Errors + +**Alignment Fault:** +- Check that halfword loads/stores use even addresses +- Check that word loads/stores use addresses divisible by 4 + +**Illegal Instruction:** +- Verify opcode is valid +- Check that shift amount is 0 for non-shift instructions +- Ensure you're not using `noreg` as a source/destination + +**Stack Corruption:** +- Verify push/pop balance +- Check that functions restore `bpr` before returning +- Ensure caller cleans up arguments + +**Wrong Results:** +- Verify `lli` is called before `lui` when loading constants +- Check that you're not relying on `acc` or `rgf` being preserved +- Verify signed vs. unsigned loads (ldb vs. ldbs) + +### Debugging Tips + +1. Add `nop` instructions as breakpoint markers +2. Print register values using display memory +3. Use single-step execution to trace program flow +4. Verify stack pointer values at function boundaries +5. Check label addresses in disassembly + +## Appendix: Instruction Quick Reference + +| Category | Instructions | +|----------|-------------| +| **Data Movement** | mov, movs | +| **Memory Load** | ldb, ldbs, ldh, ldhs, ldw | +| **Memory Store** | stb, sth, stw | +| **Immediate Load** | lli, lui | +| **Jump/Branch** | jmp, jeq, jne, jgt, jge, jlt, jle | +| **Comparison** | cmp | +| **Arithmetic** | add, sub, iadd, isub, inc, dec | +| **Logical** | and, or, xor, not, nand, nor, xnor | +| **Shift** | shl, shr | +| **System** | hlt, nop, int, irt | +| **Pseudo** | db, dh, dw, resb, resh, resw, push, pop, lwi, call, return, include | + +## Version History + +- **v1.0** - Initial comprehensive reference + - Combined hardware instructions and pseudo-instructions + - Added complete calling convention + - Included practical examples + - Documented common patterns and best practices diff --git a/docs/DSA_ISA_Specification.md b/docs/DSA_ISA_Specification.md new file mode 100644 index 0000000..c2973ee --- /dev/null +++ b/docs/DSA_ISA_Specification.md @@ -0,0 +1,401 @@ +# DSA Instruction Set Architecture Specification + +## Overview + +The Damn Simple Architecture (DSA) is a 32-bit RISC-style architecture designed for simplicity and educational purposes. This document provides the complete instruction set architecture specification, including all hardware instructions, registers, and encoding formats. + +## Data Types and Sizes + +| Type | Size | Alignment | +|------|------|-----------| +| Byte | 8 bits | 1-byte aligned | +| Halfword | 16 bits | 2-byte aligned | +| Word | 32 bits | 4-byte aligned | + +All multi-byte values use little-endian byte order. + +## Registers + +DSA provides 32 programmer-accessible registers plus several internal system registers. + +### Programmer-Accessible Registers + +| Hex | Register | Type | Description | +|-----|----------|------|-------------| +| 0x00-0x0F | **rg0-rgf** | General Purpose | 16 general-purpose registers for variables and temporary values | +| 0x10 | **acc** | Special | Accumulator for calculations and temporary storage
⚠️ May be overwritten by pseudo-instructions | +| 0x11 | **spr** | Special | Stack pointer - points to top of stack | +| 0x12 | **bpr** | Special | Base pointer - used for stack frame management | +| 0x13 | **ret** | Special | Return address register - stores function return addresses | +| 0x14 | **idr** | Privileged | Interrupt descriptor table address
Read/write triggers protection fault in user mode | +| 0x15 | **mmr** | Privileged | Hardware memory map table address
Read/write triggers protection fault in user mode | +| 0x16 | **zero** | Read-only | Constant zero value
Reads always return 0, writes are discarded | +| 0x17 | **noreg** | Placeholder | Indicates unused register field
Read/write triggers illegal instruction fault | +| 0x18-0x1F | - | Reserved | Reserved for future use | + +**Note on PCX (Program Counter):** +- PCX is a read-only system register that can be accessed in some contexts +- Writing to PCX triggers a protection fault +- PCX is automatically updated by jump and branch instructions + +### System Registers (Internal) + +These registers are used internally by the CPU and are not directly accessible via assembly instructions: + +| Register | Description | +|----------|-------------| +| **MAR** | Memory Address Register - holds address for memory operations | +| **MDR** | Memory Data Register - holds data for memory transfers | +| **CIR** | Current Instruction Register - holds instruction being executed | +| **STS** | Status Register - stores comparison and arithmetic flags | +| **PCX** | Program Counter - stores address of next instruction | + +### Status Register (STS) Layout + +The status register is a 32-bit register with the following flag bits: + +| Bit | Name | Description | Boot Value | +|-----|------|-------------|------------| +| 0 | **Equal** | Set if last comparison result was equal | 0 | +| 1 | **GreaterThan** | Set if last comparison result was greater than | 0 | +| 2 | **GreaterThanOrEqual** | Set if last comparison was greater than or equal | 0 | +| 3 | **LessThan** | Set if last comparison result was less than | 0 | +| 4 | **LessThanOrEqual** | Set if last comparison was less than or equal | 0 | +| 5 | **Zero** | Set if last arithmetic/logic operation result was zero | 0 | +| 6-31 | - | Reserved | 0 | + +## Instruction Encoding Formats + +DSA uses three instruction encoding formats: + +### R-Type (Register) Instructions + +Used for operations with register operands only, including shifts. + +``` + 31-26 | 25-21 | 20-16 | 15-11 | 10-6 | 5-0 +--------+---------+---------+---------+--------+------- + Opcode | SrcReg1 | SrcReg2 | DestReg | ShiftAmt | Unused +``` + +- **Opcode** (6 bits): Instruction operation code +- **SrcReg1** (5 bits): First source register +- **SrcReg2** (5 bits): Second source register +- **DestReg** (5 bits): Destination register +- **ShiftAmt** (5 bits): Shift amount (for shift instructions only, must be 0 otherwise) +- **Unused** (6 bits): Must be 0 + +**Important Rules:** +- ShiftAmt must be 0 for non-shift instructions (else illegal instruction fault) +- Unused register fields must be set to `noreg` (0x17) if not used +- Using registers in unexpected positions may cause illegal instruction fault + +### I-Type (Immediate) Instructions + +Used for operations with a 16-bit immediate value. + +``` + 31-26 | 25-21 | 20-16 | 15-0 +--------+---------+---------+------------- + Opcode | SrcReg | DestReg | 16-bit Immediate +``` + +- **Opcode** (6 bits): Instruction operation code +- **SrcReg** (5 bits): Source register (base for memory ops) +- **DestReg** (5 bits): Destination register (or offset register for jumps) +- **Immediate** (16 bits): Signed 16-bit immediate value or offset + +**Usage:** +- Arithmetic: Immediate is a signed value +- Memory access: Immediate is a signed byte offset from base address +- Branches: Immediate is a signed offset from current PCX +- Literal loads: Immediate is unsigned 16-bit value + +### J-Type (Jump) Instructions + +Used for absolute jumps with large address ranges. + +``` + 31-26 | 25-0 +--------+---------------------- + Opcode | 26-bit Address +``` + +- **Opcode** (6 bits): Jump instruction code +- **Address** (26 bits): Partial address for jump + +**Address Calculation:** +1. Left-shift the 26-bit address by 2 (word alignment) +2. OR with upper 4 bits of current PCX +3. Result is final 32-bit jump address + +**Jump Range:** 256MB region around current PC (±128MB) + +**Note:** J-type instructions are defined but currently unused. Use I-type JMP with register addressing for long jumps. + +## Hardware Instructions + +### Data Movement + +| Hex | Mnemonic | Type | Operands | Description | +|-----|----------|------|----------|-------------| +| 0x00 | **NOP** | R | - | No operation - does nothing | +| 0x01 | **MOV** | R | SrcReg, DestReg | Copy value from SrcReg to DestReg | +| 0x02 | **MOVS** | R | SrcReg, DestReg | Copy with sign extension to fill 32 bits | + +**MOV/MOVS Details:** +- MOV performs direct copy (all 32 bits) +- MOVS sign-extends the value (useful after byte/halfword loads) +- Both instructions set the Zero flag if result is zero + +### Memory Access - Load Instructions + +All loads require proper alignment or trigger an alignment fault. + +| Hex | Mnemonic | Type | Operands | Description | +|-----|----------|------|----------|-------------| +| 0x03 | **LDB** | I | BaseReg, DestReg, Offset | Load byte (8-bit), zero-extend to 32 bits | +| 0x04 | **LDBS** | I | BaseReg, DestReg, Offset | Load byte (8-bit), sign-extend to 32 bits | +| 0x05 | **LDH** | I | BaseReg, DestReg, Offset | Load halfword (16-bit), zero-extend to 32 bits | +| 0x06 | **LDHS** | I | BaseReg, DestReg, Offset | Load halfword (16-bit), sign-extend to 32 bits | +| 0x07 | **LDW** | I | BaseReg, DestReg, Offset | Load word (32-bit) | + +**Load Operation:** +- Effective address = BaseReg + SignExtend(Offset) +- Offset is a signed 16-bit value +- Alignment requirements: + - LDB/LDBS: No alignment required (byte-aligned) + - LDH/LDHS: Must be 2-byte aligned + - LDW: Must be 4-byte aligned + +**Encoding Note:** +In machine code, the order is: BaseReg (SrcReg field), DestReg field, Offset (Immediate field) + +### Memory Access - Store Instructions + +All stores require proper alignment or trigger an alignment fault. + +| Hex | Mnemonic | Type | Operands | Description | +|-----|----------|------|----------|-------------| +| 0x08 | **STB** | I | SrcReg, BaseReg, Offset | Store byte (8-bit) to memory | +| 0x09 | **STH** | I | SrcReg, BaseReg, Offset | Store halfword (16-bit) to memory | +| 0x0A | **STW** | I | SrcReg, BaseReg, Offset | Store word (32-bit) to memory | + +**Store Operation:** +- Effective address = BaseReg + SignExtend(Offset) +- Offset is a signed 16-bit value +- Only the relevant bits are stored (8, 16, or 32) +- Alignment requirements: + - STB: No alignment required (byte-aligned) + - STH: Must be 2-byte aligned + - STW: Must be 4-byte aligned + +**Encoding Note:** +In machine code: SrcReg (SrcReg field), BaseReg (DestReg field), Offset (Immediate field) + +### Immediate Load Instructions + +| Hex | Mnemonic | Type | Operands | Description | +|-----|----------|------|----------|-------------| +| 0x0B | **LLI** | I | DestReg, Value | Load 16-bit value into lower 16 bits
⚠️ **CLEARS upper 16 bits!** | +| 0x0C | **LUI** | I | DestReg, Value | Load 16-bit value into upper 16 bits
Lower 16 bits unchanged | + +**Usage for 32-bit Values:** +``` +LLI 0x1234, rg0 ; rg0 = 0x00001234 +LUI 0xABCD, rg0 ; rg0 = 0xABCD1234 +``` + +**⚠️ CRITICAL:** Always execute LLI before LUI, as LLI clears the upper 16 bits! + +**Encoding Note:** +In machine code: Value (Immediate field), DestReg field (SrcReg unused, set to noreg) + +### Jump and Branch Instructions + +| Hex | Mnemonic | Type | Operands | Description | +|-----|----------|------|----------|-------------| +| 0x0D | **JMP** | I | DestReg, Offset | Unconditional jump to (DestReg + Offset) | +| 0x0E | **JEQ** | I | DestReg, Offset | Jump if Equal flag set | +| 0x0F | **JNE** | I | DestReg, Offset | Jump if Equal flag NOT set | +| 0x10 | **JGT** | I | DestReg, Offset | Jump if GreaterThan flag set | +| 0x11 | **JGE** | I | DestReg, Offset | Jump if GreaterThan OR Equal flag set | +| 0x12 | **JLT** | I | DestReg, Offset | Jump if LessThan flag set | +| 0x13 | **JLE** | I | DestReg, Offset | Jump if LessThan OR Equal flag set | + +**Jump Calculation:** +- Target address = DestReg + SignExtend(Offset) +- If DestReg = zero, this becomes absolute addressing with Offset +- If DestReg = pcx, this becomes PC-relative addressing +- Conditional jumps check flags in STS register + +**Encoding Note:** +In machine code: DestReg field, Offset (Immediate field) (SrcReg unused, set to noreg) + +### Comparison + +| Hex | Mnemonic | Type | Operands | Description | +|-----|----------|------|----------|-------------| +| 0x14 | **CMP** | R | Reg1, Reg2 | Compare Reg1 with Reg2, set flags in STS | + +**Flag Setting:** +- Equal: Set if Reg1 == Reg2 +- GreaterThan: Set if Reg1 > Reg2 (signed) +- GreaterThanOrEqual: Set if Reg1 >= Reg2 (signed) +- LessThan: Set if Reg1 < Reg2 (signed) +- LessThanOrEqual: Set if Reg1 <= Reg2 (signed) +- Zero: Set if (Reg1 - Reg2) == 0 (same as Equal) + +**Encoding Note:** +DestReg and ShiftAmt fields unused (set to noreg and 0) + +### Arithmetic Instructions + +| Hex | Mnemonic | Type | Operands | Description | +|-----|----------|------|----------|-------------| +| 0x15 | **INC** | R | Reg | Increment register by 1 | +| 0x16 | **DEC** | R | Reg | Decrement register by 1 | +| 0x19 | **ADD** | R | Src1, Src2, Dest | Dest = Src1 + Src2 | +| 0x1A | **SUB** | R | Src1, Src2, Dest | Dest = Src1 - Src2 | +| 0x25 | **IADD** | I | Src, Literal, Dest | Dest = Src + SignExtend(Literal) | +| 0x26 | **ISUB** | I | Src, Literal, Dest | Dest = Src - SignExtend(Literal) | + +**Flag Effects:** +- Zero flag set if result is zero +- Other flags undefined after arithmetic (use CMP for comparisons) + +**Encoding Notes:** +- INC/DEC: Reg in SrcReg1 field, also copied to DestReg field +- IADD/ISUB: Immediate is signed 16-bit value + +### Bitwise Logical Operations + +| Hex | Mnemonic | Type | Operands | Description | +|-----|----------|------|----------|-------------| +| 0x1B | **AND** | R | Src1, Src2, Dest | Dest = Src1 & Src2 (bitwise AND) | +| 0x1C | **OR** | R | Src1, Src2, Dest | Dest = Src1 \| Src2 (bitwise OR) | +| 0x1D | **NOT** | R | Src, Dest | Dest = ~Src (bitwise NOT) | +| 0x1E | **XOR** | R | Src1, Src2, Dest | Dest = Src1 ^ Src2 (bitwise XOR) | +| 0x1F | **NAND** | R | Src1, Src2, Dest | Dest = ~(Src1 & Src2) (bitwise NAND) | +| 0x20 | **NOR** | R | Src1, Src2, Dest | Dest = ~(Src1 \| Src2) (bitwise NOR) | +| 0x21 | **XNOR** | R | Src1, Src2, Dest | Dest = ~(Src1 ^ Src2) (bitwise XNOR) | + +**Flag Effects:** +- Zero flag set if result is zero +- Other flags undefined + +**Encoding Note:** +NOT uses only Src and Dest; SrcReg2 unused (set to noreg) + +### Shift Operations + +| Hex | Mnemonic | Type | Operands | Description | +|-----|----------|------|----------|-------------| +| 0x17 | **SHL** | R | Reg, ShiftAmount | Shift Reg left by ShiftAmount bits
Zero-fill from right | +| 0x18 | **SHR** | R | Reg, ShiftAmount | Shift Reg right by ShiftAmount bits
Zero-fill from left (logical shift) | + +**Shift Amount:** +- Can be a 5-bit literal (0-31) in ShiftAmt field +- Can be a register value (low 5 bits used) + - If using register: Place in SrcReg2, set ShiftAmt to 0 + - If using literal: Place in ShiftAmt field, set SrcReg2 to noreg + +**Flag Effects:** +- Zero flag set if result is zero + +**Encoding Notes:** +- Reg in both SrcReg1 and DestReg fields +- For literal shifts: ShiftAmt field contains shift count +- For register shifts: SrcReg2 contains register, ShiftAmt must be 0 + +### System and Control Instructions + +| Hex | Mnemonic | Type | Operands | Description | +|-----|----------|------|----------|-------------| +| 0x22 | **INT** | I | InterruptCode | Trigger interrupt with 8-bit code
Saves return address to ret register
Sets bpr to kernel stack | +| 0x23 | **IRT** | R | - | Return from interrupt
Restores execution context | +| 0x24 | **HLT** | R | - | Halt processor execution
Stops fetch-decode-execute cycle | + +**INT Behavior:** +1. Save current PCX to ret register +2. Switch bpr to kernel stack address +3. Look up interrupt handler address in interrupt descriptor table (idr) +4. Jump to handler at interrupt vector + +**IRT Behavior:** +1. Restore previous execution context +2. Return to address in ret register +3. Restore user stack pointer + +**Encoding Notes:** +- INT: InterruptCode in low 8 bits of Immediate field +- IRT/HLT: All register fields set to noreg, ShiftAmt to 0 + +## Instruction Summary Table + +| Opcode | Mnemonic | Type | Category | +|--------|----------|------|----------| +| 0x00 | NOP | R | Control | +| 0x01 | MOV | R | Data Movement | +| 0x02 | MOVS | R | Data Movement | +| 0x03 | LDB | I | Memory Load | +| 0x04 | LDBS | I | Memory Load | +| 0x05 | LDH | I | Memory Load | +| 0x06 | LDHS | I | Memory Load | +| 0x07 | LDW | I | Memory Load | +| 0x08 | STB | I | Memory Store | +| 0x09 | STH | I | Memory Store | +| 0x0A | STW | I | Memory Store | +| 0x0B | LLI | I | Immediate Load | +| 0x0C | LUI | I | Immediate Load | +| 0x0D | JMP | I | Jump | +| 0x0E | JEQ | I | Branch | +| 0x0F | JNE | I | Branch | +| 0x10 | JGT | I | Branch | +| 0x11 | JGE | I | Branch | +| 0x12 | JLT | I | Branch | +| 0x13 | JLE | I | Branch | +| 0x14 | CMP | R | Comparison | +| 0x15 | INC | R | Arithmetic | +| 0x16 | DEC | R | Arithmetic | +| 0x17 | SHL | R | Shift | +| 0x18 | SHR | R | Shift | +| 0x19 | ADD | R | Arithmetic | +| 0x1A | SUB | R | Arithmetic | +| 0x1B | AND | R | Logical | +| 0x1C | OR | R | Logical | +| 0x1D | NOT | R | Logical | +| 0x1E | XOR | R | Logical | +| 0x1F | NAND | R | Logical | +| 0x20 | NOR | R | Logical | +| 0x21 | XNOR | R | Logical | +| 0x22 | INT | I | System | +| 0x23 | IRT | R | System | +| 0x24 | HLT | R | System | +| 0x25 | IADD | I | Arithmetic | +| 0x26 | ISUB | I | Arithmetic | + +## Exception Conditions + +The following conditions trigger exceptions: + +| Exception | Trigger Condition | +|-----------|------------------| +| **Illegal Instruction** | - Invalid opcode
- noreg used as source/destination
- ShiftAmt non-zero for non-shift instruction
- Register field violations | +| **Protection Fault** | - Write to pcx register
- Read/write idr or mmr in user mode
- Read from noreg
- Write to zero register (discarded, no fault) | +| **Alignment Fault** | - LDH/LDHS/STH with odd address
- LDW/STW with address not divisible by 4 | +| **Memory Access Violation** | - Access to unmapped or protected memory
- Stack overflow/underflow | + +## Calling Convention + +See the DSA Assembly Language Reference for the complete calling convention and ABI specification. + +## Notes on Design + +1. **Word Size:** All addresses and general computation is 32-bit +2. **Endianness:** Little-endian byte order +3. **Stack Growth:** Stack grows upward (incrementing addresses) +4. **Alignment:** Natural alignment required for halfword and word accesses +5. **Sign Extension:** All immediate values are sign-extended unless noted +6. **Zero Register:** Provides constant zero, writes are legal but discarded +7. **Reserved Encodings:** Opcodes 0x27-0x3F reserved for future use diff --git a/resources/ideas/DSA_Project_Roadmap.md b/docs/DSA_Project_Roadmap.md similarity index 100% rename from resources/ideas/DSA_Project_Roadmap.md rename to docs/DSA_Project_Roadmap.md diff --git a/resources/ideas/DSA_Project_Roadmap.pdf b/docs/DSA_Project_Roadmap.pdf similarity index 100% rename from resources/ideas/DSA_Project_Roadmap.pdf rename to docs/DSA_Project_Roadmap.pdf diff --git a/docs/IMPLEMENTATION_DISCREPANCIES.md b/docs/IMPLEMENTATION_DISCREPANCIES.md new file mode 100644 index 0000000..329abae --- /dev/null +++ b/docs/IMPLEMENTATION_DISCREPANCIES.md @@ -0,0 +1,638 @@ +# DSA Implementation vs Documentation Discrepancies + +## Critical Discrepancies + +### 1. **Stack Growth Direction** ❌ CRITICAL + +**Documentation states:** Stack grows upward (toward higher addresses) + +**Implementation shows (expand.rs:44-51):** +```rust +fn expand_push(current: &Node, nodes: &mut Vec) -> Result<(), AssembleError> { + // ... + nodes.extend(vec![ + node!(label, Opcode::SubI, spr, 4, spr), // spr = spr - 4 + node!(None, Opcode::Stw, reg, spr, 0), + ]); +``` + +**Implementation shows (expand.rs:130-137):** +```rust +fn expand_pop(current: &Node, nodes: &mut Vec) -> Result<(), AssembleError> { + // ... + nodes.extend(vec![ + node!(label, Opcode::Ldw, spr, reg, 0), + node!(None, Opcode::AddI, spr, 4, spr), // spr = spr + 4 + ]); +``` + +**Reality:** Stack grows **DOWNWARD** (toward lower addresses) +- PUSH: Decrements SPR by 4, then stores +- POP: Loads, then increments SPR by 4 + +**Impact:** All documentation examples and calling convention diagrams are backwards! + +--- + +### 2. **CALL Pseudo-instruction Expansion** ❌ CRITICAL + +**Documentation states (DSA_Assembly_Reference.md):** +```asm +; call print::print expands to: +lwi print::print, ret ; Load function address into ret +jmp 0, ret ; Jump to function (saves return in pcx) +``` + +**Implementation shows (expand.rs:109-123):** +```rust +fn expand_call(current: &Node, nodes: &mut Vec) -> Result<(), AssembleError> { + nodes.extend(vec![ + node!(label, Opcode::SubI, spr, 4, spr), // Decrement stack pointer + node!(None, Opcode::Stw, pcx, spr, 0), // Store PCX (return addr) on stack + node!(None, Opcode::Jmp, addr, zero), // Jump to function + ]); +``` + +**Reality:** CALL expansion is: +1. Decrement SPR by 4 +2. Store PCX (return address) to stack +3. Jump to function address + +**Impact:** Return address is stored on the STACK, not in RET register! + +--- + +### 3. **RETURN Pseudo-instruction Expansion** ❌ CRITICAL + +**Documentation states:** +```asm +; return expands to: +jmp 0, ret ; Jump to address in ret register +``` + +**Implementation shows (expand.rs:125-135):** +```rust +fn expand_return(current: &Node, nodes: &mut Vec) { + nodes.extend(vec![ + node!(label, Opcode::Ldw, spr, ret, 0), // Load return addr from stack + node!(None, Opcode::AddI, spr, 4, spr), // Increment stack pointer + node!(None, Opcode::Jmp, 4, ret), // Jump to (ret + 4) + ]); +} +``` + +**Reality:** RETURN expansion is: +1. Load return address from stack into RET register +2. Increment SPR by 4 +3. Jump to (RET + 4) + +**Why +4?** The stored PCX points to the instruction AFTER the call's jump, so we need to add 4 to skip past the stored PCX instruction itself... or this might be a bug in the implementation. + +**Impact:** Return mechanism is completely different from documentation! + +--- + +### 4. **Calling Convention - Stack Frame Layout** ❌ CRITICAL + +**Documentation states:** +``` +Higher Addresses +├─────────────┤ +│ Arg N │ ← spr + (8 + 4*(N-1)) +│ ... │ +│ Arg 2 │ ← spr + 16 +│ Arg 1 │ ← spr + 12 +│ Arg 0 │ ← spr + 8 +├─────────────┤ +│ Ret Addr │ ← spr + 4 +├─────────────┤ +│ Old BPR │ ← spr + 0 +├─────────────┤ ← bpr, spr +│ Locals │ +Lower Addresses +``` + +**Reality based on implementation:** +Since stack grows DOWN: +``` +Lower Addresses +├─────────────┤ ← Current SPR/BPR +│ Old BPR │ ← spr + 0 (immediately above SPR) +├─────────────┤ +│ Ret Addr │ ← spr + 4 (pushed by CALL) +├─────────────┤ +│ Arg 0 │ ← spr + 8 +│ Arg 1 │ ← spr + 12 +│ Arg 2 │ ← spr + 16 +│ ... │ +│ Arg N │ ← spr + (8 + 4*(N-1)) +├─────────────┤ +Higher Addresses +``` + +**The diagram needs to be flipped!** The offsets are correct, but the direction is wrong. + +--- + +### 5. **Label-Based Load/Store Scratch Register** ⚠️ IMPORTANT + +**Documentation states:** Uses `rgf` as scratch register + +**Implementation confirms (expand.rs:138-153):** +```rust +fn expand_ldx(current: &Node, nodes: &mut Vec) -> Result<(), AssembleError> { + // For ldb label, reg: + nodes.extend(vec![ + node!(current.label(), Opcode::Lli, name, reg), + node!(None, Opcode::Lui, name, reg), + node!(None, opcode, reg, reg, offset), + ]); +``` + +**Wait! This is WRONG in the implementation!** + +The load expansion uses the DESTINATION register as scratch: +```asm +ldb buffer, rg2 expands to: + lli buffer, rg2 ; Uses rg2 as destination + lui buffer, rg2 ; Uses rg2 as destination + ldb rg2, rg2, 0 ; Uses rg2 as base +``` + +**Documentation says it should use rgf:** +```asm +ldb buffer, rg2 expands to: + lli buffer, rgf ; Uses rgf as scratch + lui buffer, rgf ; Uses rgf as scratch + ldb rgf, rg2, 0 ; Load from rgf into rg2 +``` + +**For stores (expand.rs:155-176):** +```rust +fn expand_stx(current: &Node, nodes: &mut Vec) -> Result<(), AssembleError> { + // For stb reg, label: + let temp = Token::Register(Register::Acc); // Uses ACC, not RGF! + + nodes.extend(vec![ + node!(current.label(), Opcode::Lli, dest, temp), + node!(None, Opcode::Lui, dest, temp), + node!(None, opcode, base, temp, offset), + ]); +``` + +**Reality:** +- Load pseudo-instructions use the DESTINATION register as scratch +- Store pseudo-instructions use the ACC register as scratch, NOT rgf + +**Impact:** Documentation is incorrect about which registers are used! + +--- + +### 6. **LWI Pseudo-instruction** ✅ CORRECT + +**Documentation and implementation agree:** +```rust +fn expand_lwi(current: &Node, nodes: &mut Vec) -> Result<(), AssembleError> { + nodes.extend(vec![ + node!(current.label(), Opcode::Lli, val, reg), + node!(None, Opcode::Lui, val, reg), + ]); +``` + +This matches the documented expansion. + +--- + +### 7. **PUSHA/POPA Pseudo-instructions** 📝 UNDOCUMENTED + +**These exist in implementation but are NOT in documentation!** + +**expand.rs:53-76:** +```rust +fn expand_pusha(current: &Node, nodes: &mut Vec) -> Result<(), AssembleError> { + let count = expect_token!(arg0, Immediate)?; + let spr = Token::Register(Register::Spr); + let registers: Vec = Register::general(); + + nodes.push(node!(label, Opcode::SubI, spr, Token::Immediate(count * 4), spr)); + + nodes.extend((0..count).rev().map(|i| { + node!(None, Opcode::Stw, + Token::Register(registers[i as usize]), + spr, + Token::Immediate(i * 4) + ) + })); +``` + +**expand.rs:78-101:** +```rust +fn expand_popa(current: &Node, nodes: &mut Vec) -> Result<(), AssembleError> { + let count = expect_token!(arg0, Immediate)?; + + nodes.extend((0..count).rev().map(|i| { + node!( + { if i == 0 { label.clone() } else { None } }, + Opcode::Ldw, + spr, + Token::Register(registers[i as usize]), + Token::Immediate(i * 4) + ) + })); + + nodes.push(node!(None, Opcode::AddI, spr, Token::Immediate(count * 4), spr)); +``` + +**What they do:** +- `pusha N` - Push first N general-purpose registers (rg0-rgN) to stack +- `popa N` - Pop first N general-purpose registers from stack + +**Missing from documentation entirely!** + +--- + +### 8. **Register Index Encoding** ⚠️ IMPORTANT + +**Documentation states:** System registers like MAR, MDR, STS, CIR, PCX are "internal" and not accessible + +**Implementation shows (instructions.rs:148-153):** +```rust +0x18 => Self::Mar, +0x19 => Self::Mdr, +0x1A => Self::Sts, +0x1B => Self::Cir, +0x1C => Self::Pcx, +``` + +**Reality:** These registers ARE encoded in the instruction format at indices 0x18-0x1C! + +**However, instructions.rs:186 shows:** +```rust +"null" => Ok(Self::NoReg), // Can parse "null" as NoReg +``` + +**Documentation never mentions "null" as an alternative name for noreg!** + +--- + +### 9. **LUI Immediate Value Handling** ⚠️ IMPORTANT + +**Documentation states:** +``` +lui immediate, dest_reg ; Load immediate into upper 16 bits +``` + +**Implementation shows (codegen.rs:248-254):** +```rust +fn build_load_immediate_instruction(...) -> Result { + // ... + match opcode { + Opcode::Lli => { + let instruction_args = args!(I, immediate: value as u16, r1: dest); + Ok(Instruction::LoadLowerImmediate(instruction_args)) + } + Opcode::Lui => { + let upper_value = value >> 16; // Shifts right by 16! + let instruction_args = args!(I, immediate: upper_value as u16, r1: dest); + Ok(Instruction::LoadUpperImmediate(instruction_args)) + } +``` + +**Reality:** When assembling `lui immediate, reg`, the assembler: +1. Takes the immediate value +2. Shifts it RIGHT by 16 bits +3. Stores the result in the instruction + +**This means:** +```asm +lli 0x1234, rg0 ; Stores 0x1234 in lower 16 bits +lui 0xABCD0000, rg0 ; Right-shifts to 0xABCD, stores in upper 16 bits +``` + +**Or more likely, the assembler expects:** +```asm +lli 0x1234, rg0 ; Stores 0x1234 in lower 16 bits +lui 0xABCD, rg0 ; Stores 0xABCD in upper 16 bits (no shift needed) +``` + +**Documentation needs clarification on what immediate value format LUI expects!** + +--- + +### 10. **Data Definition Encoding** ⚠️ IMPORTANT + +**Implementation (expand.rs:217-267):** +```rust +fn process_dx_data(args: Vec, size: usize) -> Result, AssembleError> { + for token in args { + match token { + Token::StringLit(mut s) => { + s.push('\0'); // Automatically adds null terminator! + for ch in s.chars() { + let mut char_buf = [0u8; 4]; + let char_bytes = ch.encode_utf8(&mut char_buf); + buffer.extend_from_slice(char_bytes.as_bytes()); + } + } + Token::Immediate(value) => { + buffer.extend_from_slice(&value.to_be_bytes()); // BIG ENDIAN! + } +``` + +**Key findings:** +1. String literals automatically get null terminator appended +2. Numeric values are stored in **BIG ENDIAN** format (to_be_bytes) +3. Documentation says "little-endian byte order" globally + +**Contradiction:** Data definition uses BIG ENDIAN, but doc says LITTLE ENDIAN! + +--- + +### 11. **Segment Instruction** 📝 UNDOCUMENTED + +**Implementation has a SEGMENT instruction (0x27/0x3F):** +```rust +Segment(u32) = 0x3F, +``` + +**This is completely undocumented!** + +From model.rs: +```rust +Self::Segment => write!(f, "[SEGMENT]"), +``` + +From codegen.rs: +```rust +Opcode::Segment => build_segment_instruction(&args), +``` + +**Purpose unclear, needs documentation!** + +--- + +### 12. **Data Instruction** 📝 UNDOCUMENTED + +**Implementation has a DATA instruction (0x3E):** +```rust +Data(u32) = 0x3E, +``` + +**This appears to be a meta-instruction for embedding raw data, but it's undocumented in the assembly reference!** + +--- + +### 13. **INC/DEC Instruction Encoding** ⚠️ MINOR + +**Implementation (codegen.rs:293-299):** +```rust +fn build_inc_dec_instruction(opcode: Opcode, args: &[Token]) -> Result { + let reg = expect_token!(reg_token, Register)?; + match opcode { + Opcode::Inc => Ok(Instruction::Increment(args!(R, sr1: reg))), + Opcode::Dec => Ok(Instruction::Decrement(args!(R, sr1: reg))), +``` + +**Reality:** INC/DEC only set SR1 field, not DR field. + +**But args.rs shows:** +```rust +impl RTypeArgs { + pub fn new(...) -> Self { + let sr1 = sr1.unwrap_or_default(); // Defaults to NoReg + let dr = dr.unwrap_or_default(); // Defaults to NoReg +``` + +**So the DR field gets set to NoReg, which is correct per documentation.** + +**However, the Display impl (instructions.rs:449) shows:** +```rust +Self::Increment(a) | Self::Decrement(a) => write!(f, " {}", a.sr1), +``` + +**This is correct - only shows SR1 in disassembly.** + +--- + +### 14. **Shift Instruction Operand Order** ⚠️ MINOR + +**Implementation (codegen.rs:301-312):** +```rust +fn build_shift_instruction(opcode: Opcode, args: &[Token]) -> Result { + let reg = expect_token!(reg_token, Register)?; + let amount = expect_token!(amount_token, Immediate)? as u8; + + match opcode { + Opcode::Shl => Ok(Instruction::ShiftLeft(args!(R, sr1: reg, shamt: amount))), +``` + +**This only handles LITERAL shift amounts, not REGISTER shift amounts!** + +**Documentation states both are supported:** +```asm +shl rg0, 2 ; Literal shift +shl rg0, rg1 ; Register shift +``` + +**The current codegen only handles the literal case!** + +**This is a BUG in the implementation - register shifts aren't properly assembled!** + +--- + +### 15. **Jump Instruction Operand Order** ⚠️ CONFUSION + +**Documentation shows assembly syntax:** +```asm +jmp addr [, offset_reg] +``` + +**But implementation (codegen.rs:256-270):** +```rust +fn build_jump_instruction(opcode: Opcode, args: &[Token]) -> Result { + let address = expect_token!(address_token, Immediate)?; + let offset = expect_token!(offset_token, Register)?; + let instruction_args = args!(I, immediate: address as u16, r1: offset); +``` + +**This expects:** +1. First arg: immediate (address) +2. Second arg: register (offset) + +**So assembly syntax should be:** +```asm +jmp immediate, offset_register +``` + +**Example:** +```asm +jmp 0x1000, zero ; Jump to 0x1000 +jmp 4, ret ; Jump to (ret + 4) +``` + +**Documentation syntax is correct, but parameter names are confusing!** + +The "address" is actually an OFFSET, and the register is the BASE! + +**Better naming:** +```asm +jmp offset, base_register +; Target = base_register + offset +``` + +--- + +### 16. **NOT Instruction Operand Count** ✅ MINOR ISSUE + +**Documentation shows:** +```asm +not src, dest ; Two operands +``` + +**Implementation (instructions.rs:428-429):** +```rust +Self::Compare(args) | Self::Not(args) => { + write!(f, " {}, {}", args.sr1, args.sr2) +} +``` + +**This displays BOTH sr1 and sr2 for NOT!** + +**But codegen.rs:354-362:** +```rust +fn build_not_instruction(args: &[Token]) -> Result { + let reg = expect_token!(reg_token, Register)?; + let dest = expect_token!(dest_token, Register)?; + Ok(Instruction::Not(args!(R, sr1: reg, dr: dest))) +``` + +**Sets sr1 and dr, NOT sr1 and sr2!** + +**The Display impl is WRONG - should show sr1 and dr:** +```rust +Self::Not(args) => write!(f, " {}, {}", args.sr1, args.dr) +``` + +**This is a display bug in the implementation!** + +--- + +### 17. **Register File Indexing** ✅ CORRECT + +**Documentation and implementation both agree:** +- 0x00-0x0F: rg0-rgf (general purpose) +- 0x10: acc +- 0x11: spr +- 0x12: bpr +- 0x13: ret +- 0x14: idr +- 0x15: mmr +- 0x16: zero +- 0x17: noreg + +**This matches perfectly.** + +--- + +### 18. **Immediate Arithmetic Destination** ⚠️ MINOR + +**Implementation (codegen.rs:314-330):** +```rust +fn build_arithmetic_immediate_instruction(...) -> Result { + let reg = expect_token!(reg_token, Register)?; + let immediate = expect_token!(immediate_token, Immediate)? as u16; + let dest = expect_token!(dest_token, Register)?; + let instruction_args = args!(I, immediate: immediate, r1: reg, r2: dest); +``` + +**This REQUIRES three arguments:** +1. Source register +2. Immediate value +3. Destination register + +**But documentation says destination is optional:** +``` +iadd src_reg, imm [, dest_reg] ; dest optional +``` + +**Reality:** The assembler REQUIRES the destination register! + +**If you want in-place operation:** +```asm +iadd rg0, 10, rg0 ; Required to specify rg0 twice +``` + +**Not:** +```asm +iadd rg0, 10 ; This won't work! +``` + +**Documentation is misleading - destination is NOT optional!** + +--- + +### 19. **Memory Instruction Offsets** ✅ CORRECT + +**Implementation correctly handles signed 16-bit offsets:** +```rust +let offset = expect_token!(offset_token, Immediate)? as u16; +``` + +**These are stored as u16 but interpreted as signed i16 at runtime.** + +**Documentation is correct about this.** + +--- + +### 20. **Instruction Opcode Values** ✅ VERIFIED + +Comparing model.rs opcodes with instructions.rs: + +| Instruction | model.rs | instructions.rs | Match | +|-------------|----------|-----------------|-------| +| Nop | 0x00 | 0x0 | ✅ | +| Mov | 0x01 | 0x1 | ✅ | +| MovSigned | 0x02 | 0x2 | ✅ | +| LoadByte | 0x03 | 0x3 | ✅ | +| ... | ... | ... | ✅ | +| AddImmediate | 0x25 | 0x25 | ✅ | +| SubImmediate | 0x26 | 0x26 | ✅ | +| Segment | 0x27 | 0x3F | ❌ MISMATCH! | + +**CRITICAL:** Segment instruction has opcode **0x27** in model.rs but **0x3F** in instructions.rs! + +--- + +## Summary of Critical Issues + +### Must Fix in Documentation: + +1. ✅ **Stack grows DOWNWARD** - flip all diagrams +2. ✅ **CALL expansion** - uses stack, not ret register directly +3. ✅ **RETURN expansion** - loads from stack, jumps to ret+4 +4. ✅ **Stack frame layout** - flip diagram vertically +5. ✅ **Load pseudo scratch register** - uses DEST reg, not rgf +6. ✅ **Store pseudo scratch register** - uses ACC, not rgf +7. ✅ **Add PUSHA/POPA documentation** +8. ✅ **Add SEGMENT instruction documentation** +9. ✅ **Add DATA instruction documentation** +10. ✅ **Clarify LUI immediate value handling** +11. ✅ **Fix endianness** - data definition uses BIG endian +12. ✅ **IADD/ISUB destination NOT optional** +13. ✅ **Add "null" as alias for noreg** +14. ✅ **Fix Segment opcode** - 0x27 or 0x3F? + +### Potential Implementation Bugs: + +1. ⚠️ **Shift instruction** - doesn't handle register shifts +2. ⚠️ **NOT display** - shows sr2 instead of dr +3. ⚠️ **RETURN +4 offset** - why is this needed? +4. ⚠️ **Segment opcode mismatch** - 0x27 vs 0x3F + +### Minor Documentation Improvements: + +1. Add explicit examples of stack growth direction +2. Show complete memory layout diagrams +3. Document which registers are volatile/preserved +4. Add troubleshooting section for common mistakes +5. Clarify jump instruction parameter semantics diff --git a/resources/ideas/design.rnote b/docs/design.rnote similarity index 100% rename from resources/ideas/design.rnote rename to docs/design.rnote diff --git a/docs/inconsistencies.md b/docs/inconsistencies.md new file mode 100644 index 0000000..5d387b0 --- /dev/null +++ b/docs/inconsistencies.md @@ -0,0 +1,149 @@ +# DSA Documentation Inconsistencies Analysis + +## 1. Register Descriptions + +### Issue: System Registers vs Assembly-Accessible Registers +- `registers.md` lists MAR, STS, CIR, MDR as "System" registers +- These are NOT mentioned in `dsa_assembly_reference.md` or `instruction_set.md` +- **Resolution**: System registers are internal CPU registers not directly accessible in assembly. They should be documented separately from programmer-accessible registers. + +### Issue: Register Naming Inconsistencies +- `registers.md` uses `RG0-RGF` (uppercase) +- `dsa_assembly_reference.md` uses `rg0-rgf` (lowercase) +- **Resolution**: Assembly syntax should be lowercase (standard convention) + +### Issue: NOREG Register +- `registers.md`: "Loads/using as dest register must cause an illegal instruction trap" +- `dsa_assembly_reference.md`: "on-read/write: illegal instruction fault" +- **Resolution**: Consistent terminology needed - use "illegal instruction fault" + +## 2. Instruction Operand Order Inconsistencies + +### Issue: Load Instructions +- `instruction_set.md`: `LDB BaseReg, Offset, DestReg` +- `dsa_assembly_reference.md`: `LDB base_reg, dest_reg [, offset]` +- **Resolution**: Assembly reference shows standard syntax (base, dest, offset optional), instruction set shows encoding order + +### Issue: Store Instructions +- `instruction_set.md`: `STB SrcReg, BaseReg, Offset` +- `dsa_assembly_reference.md`: `STB src_reg, base_reg [, offset]` +- **Resolution**: Consistent - offset is optional + +### Issue: Immediate Load Instructions +- `instruction_set.md`: `LLI DstReg, Value` (destination first) +- `dsa_assembly_reference.md`: `LLI imm, dest_reg` (immediate first) +- **Resolution**: Assembly reference shows gas-style syntax (source, dest), instruction set shows encoding order + +### Issue: Jump Instructions +- `instruction_set.md`: `JMP DestReg, Offset | Address` +- `dsa_assembly_reference.md`: `JMP addr [, offset_reg]` or `JMP imm, offset_reg` +- **Resolution**: Different perspectives - instruction set shows encoding, assembly shows usage + +## 3. Instruction Behavior Differences + +### Issue: IADD/ISUB Operands +- `instruction_set.md`: `IADD Src1, Literal, Dest` (3 operands) +- `dsa_assembly_reference.md`: `IADD src_reg, imm [, dest_reg]` (dest optional) +- **Resolution**: Assembly allows dest to default to src_reg + +### Issue: SHL/SHR Operands +- `instruction_set.md`: `SHL Reg, Literal | ValReg` +- `dsa_assembly_reference.md`: `SHL reg, shift_amount` +- **Resolution**: Both literal and register shifts supported + +## 4. Pseudo-Instruction Inconsistencies + +### Issue: PUSH/POP Expansion +- `pseudoinstructions.md`: + - PUSH = `INC SPR` then `STW register, SPR` + - POP = `LDW SPR, register` then `DEC SPR` +- Standard stack conventions suggest PUSH should decrement (grow down) +- **Resolution**: Clarify stack growth direction + +### Issue: LDB/LDH/LDW Pseudo vs Hardware +- `pseudoinstructions.md` lists LDB, LDH, LDW as pseudo-instructions with label addressing +- `instruction_set.md` lists them as hardware instructions +- **Resolution**: Both exist - hardware instructions use registers, pseudo-instructions add label support + +### Issue: LWI Naming +- `dsa_assembly_reference.md`: LWI = Load Word Immediate (load address) +- Could be confused with "Load Word Immediate" (load literal value) +- **Resolution**: LWI specifically means "Load Word address Into register" + +## 5. Calling Convention Details + +### Issue: Argument Offsets +- Calling convention says "first 3 args at offsets 8, 12, 16" +- This assumes 32-bit words (4 bytes each) +- Offset 8 is position of first argument (after return address at offset 4, and old BPR at offset 0) +- **Resolution**: Clarify that SPR+0 = old BPR, SPR+4 = return address, SPR+8 = first arg + +### Issue: Return Value Location +- Says "Store return value (if any) to `spr+8`" +- This overwrites the first argument +- **Resolution**: This is intentional - return value replaces first argument position after cleanup + +## 6. Missing Information + +### From instruction_set.md not in assembly reference: +- Instruction encoding details (R-type, I-type, J-type) +- Hex opcodes for each instruction +- Alignment requirements for memory operations +- Sign extension behavior details + +### From assembly reference not in instruction_set: +- Complete pseudo-instruction expansions showing what they compile to +- Library examples (multiply, print) +- Detailed calling convention walkthrough +- Module system (INCLUDE directive) + +### From registers.md not elsewhere: +- STS (Status Register) bit layout +- Boot values for status flags +- System registers (MAR, STS, CIR, MDR) + +## 7. Terminology Inconsistencies + +- "halfword" vs "half-word" vs "16-bit value" +- "word" assumed to be 32-bit (should be explicit) +- "register" vs "reg" in syntax +- "immediate" vs "literal" vs "constant" + +## 8. Critical Missing Details + +### CALL and RETURN Pseudo-instructions +- Assembly reference shows them but doesn't show their expansion +- Need to document what they expand to + +### Label Addressing Mode +- Shows expansions for loads/stores with labels +- Uses RGF as scratch register - should this be documented as reserved for this purpose? + +### Stack Direction +- Not explicitly stated whether stack grows up or down +- PUSH uses INC SPR (suggests growing up) - unusual! + +## Recommendations + +1. **Separate Documentation into Logical Layers**: + - ISA Specification (hardware-level, for CPU implementers) + - Assembly Language Reference (for programmers) + - ABI/Calling Convention (for compiler/linker writers) + +2. **Standardize Terminology**: + - Use consistent casing (lowercase for assembly mnemonics) + - Define terms clearly (word = 32-bit, halfword = 16-bit, byte = 8-bit) + - Distinguish "literal" (immediate value in code) from "address" (memory location) + +3. **Document Stack Convention Clearly**: + - Explicitly state stack grows upward (unusual but valid) + - Show memory layout diagrams + +4. **Show Complete Pseudo-instruction Expansions**: + - CALL, RETURN need full expansion documentation + - Document which register(s) are used as temporaries + +5. **Clarify Register Usage Conventions**: + - ACC: used by pseudo-instructions, volatile + - RGF: used by label addressing, volatile + - RG0-RGE: general purpose, callee may use per calling convention diff --git a/resources/ideas/dsa_assembly_reference.md b/resources/ideas/dsa_assembly_reference.md deleted file mode 100644 index cfdee16..0000000 --- a/resources/ideas/dsa_assembly_reference.md +++ /dev/null @@ -1,427 +0,0 @@ -# DSA Assembly Language Instruction Reference - -## Overview - -This document provides a comprehensive reference for the DSA (Damn Simple Architecture) assembly language, including all hardware instructions and pseudo-instructions with their syntax variations and usage examples. - -## Calling Convention - -| Step | Responsibility | Action | Description | -|------|----------------|--------|-------------| -| 1 | **Caller** | Push arguments | Push exactly n arguments to the stack (in order, last argument pushed first) | -| 2 | **Caller** | Call function | Execute `call namespace::function` - this automatically pushes the return address (pcx) and jumps to the function | -| 3 | **Function** | Set up stack frame | Execute `push bpr; mov spr, bpr` to establish new stack frame | -| 4 | **Function** | Access arguments | Read arguments starting at `spr+8` (first 3 args at offsets 8, 12, 16) | -| 5 | **Function** | Execute function | Perform the function's operations using the arguments | -| 6 | **Function** | Store return value | Write return value (if any) to `spr+8` | -| 7 | **Function** | Restore stack frame | Execute `mov bpr, spr; pop bpr` to restore previous stack frame | -| 8 | **Function** | Return | Execute `return` pseudo-instruction to return to caller | -| 9 | **Caller** | Clean up stack | Pop exactly n arguments from the stack to clean up | -| 10 | **Caller** | Handle unused values | Use `pop zero` to discard any unused stack values if needed | - -**Notes:** -- The namespace in step 2 is the name assigned in the `include` statement -- The `call` pseudo-instruction automatically handles return address management so long as the callee does not mess with the stack -- Arguments are accessed by the callee using offsets from the base pointer (bpr) - -## Registers - -| Register | Type | Description | -|----------|------|---------------------------------------------------------------------------------------------------| -| `rg0-rgf` | General Purpose | General-purpose registers. | -| `acc` | Special | Accumulator for calculations and temporary storage - don't use this for variables as pseudo instructions may overwrite this implicitly! | -| `spr` | Special | Stack pointer | -| `bpr` | Special | Base pointer for stack frames | -| `ret` | Special | Return address register | -| `idr` | Privileged | Interrupt descriptor table address
**on-read/write: protection fault (unless in kernel mode)** | -| `mmr` | Privileged | Hardware memory map table address
**on-read/write: protection fault (unless in kernel mode)** | -| `zero` | Read-only | Always contains zero
**on-read: always returns zero**
**on-write: value is voided** | -| `pcx` | Read-only | Program counter
**on-write: protection fault** | -| `noreg` | Placeholder | Indicates absence of register argument
**on-read/write: illegal instruction fault** | - -## Hardware Instructions - -### Data Movement Instructions - -| Mnemonic | Operands | Description | -|----------|----------|-------------| -| **MOV** | `src_reg, dest_reg` | Copy value from source to destination register | -| **MOVS** | `src_reg, dest_reg` | Copy with sign extension | - -**Examples:** -```asm -mov rg0, rg1 ; Copy rg0 to rg1 -movs rg0, rg1 ; Copy rg0 to rg1 with sign extension -``` -### Memory Access Instructions - -#### Load Instructions - -| Mnemonic | Operands | Description | -|----------|----------|-------------| -| **LDB** | `base_reg, dest_reg [, offset]`
`label, dest_reg [, offset]` | Load byte from memory | -| **LDBS** | `base_reg, dest_reg [, offset]`
`label, dest_reg [, offset]` | Load byte with sign extension | -| **LDH** | `base_reg, dest_reg [, offset]`
`label, dest_reg [, offset]` | Load half-word (16-bit) | -| **LDHS** | `base_reg, dest_reg [, offset]`
`label, dest_reg [, offset]` | Load half-word with sign extension | -| **LDW** | `base_reg, dest_reg [, offset]`
`label, dest_reg [, offset]` | Load word (32-bit) | - -**Examples:** -```asm -; Direct register addressing -ldb rg0, rg1 ; Load byte from address in rg0 -ldw rg0, rg1, 8 ; Load word from (rg0 + 8) - -; Label addressing -ldb buffer, rg2 ; Load byte from label 'buffer' -ldw stack, bpr ; Load stack address into base pointer -``` -**Label Expansions:** -```asm -; ldb buffer, rg2 expands to: -lli buffer, rg2 ; Load lower 16 bits of buffer address -lui buffer, rg2 ; Load upper 16 bits of buffer address -ldb rg2, rg2 ; Load byte from address in rg2 - -; ldw stack, bpr expands to: -lli stack, bpr ; Load lower 16 bits of stack address -lui stack, bpr ; Load upper 16 bits of stack address -ldw bpr, bpr ; Load word from address in bpr -``` -#### Store Instructions - -| Mnemonic | Operands | Description | -|----------|----------|-------------| -| **STB** | `src_reg, base_reg [, offset]`
`src_reg, label [, offset]` | Store byte to memory | -| **STH** | `src_reg, base_reg [, offset]`
`src_reg, label [, offset]` | Store half-word to memory | -| **STW** | `src_reg, base_reg [, offset]`
`src_reg, label [, offset]` | Store word to memory | - -**Examples:** -```asm -; Direct register addressing -stb rg0, rg1 ; Store byte from rg0 to address in rg1 -stw rg0, rg1, 12 ; Store word to (rg1 + 12) - -; Label addressing -stb acc, buffer ; Store byte from accumulator to 'buffer' -stw rg1, current ; Store word to 'current' variable -``` -**Label Expansions:** -```asm -; stb acc, buffer expands to: -lli buffer, rgf ; Load lower 16 bits of buffer address -lui buffer, rgf ; Load upper 16 bits of buffer address -stb acc, rgf ; Store byte from acc to address in rgf - -; stw rg1, current expands to: -lli current, rgf ; Load lower 16 bits of current address -lui current, rgf ; Load upper 16 bits of current address -stw rg1, rgf ; Store word from rg1 to address in rgf -``` -### Immediate Load Instructions - -| Mnemonic | Operands | Description | -|----------|----------|------------------------------------------------------------------------| -| **LLI** | `imm, dest_reg` | Load 16-bit immediate into lower 16 bits
**Clears upper 16 bits!** | -| **LUI** | `imm, dest_reg` | Load 16-bit immediate into upper 16 bits | - -**Usage** - -ensure that you always run **Lli** before **Lui** as **Lli** clears the upper 16 bits. - -**Examples:** -```asm -lli 0x1234, rg0 ; Load 0x1234 into lower 16 bits of rg0 -lui 0xABCD, rg0 ; Load 0xABCD into upper 16 bits of rg0 -``` -### Jump Instructions - -| Mnemonic | Operands | Description | -|----------|----------|-------------| -| **JMP** | `addr [, offset_reg]`
`imm, offset_reg` | Unconditional jump | -| **JEQ** | `addr [, offset_reg]` | Jump if equal flag set | -| **JNE** | `addr [, offset_reg]` | Jump if not equal flag set | -| **JGT** | `addr [, offset_reg]` | Jump if greater than flag set | -| **JGE** | `addr [, offset_reg]` | Jump if greater or equal flags set | -| **JLT** | `addr [, offset_reg]` | Jump if less than flag set | -| **JLE** | `addr [, offset_reg]` | Jump if less or equal flags set | - -**Examples:** -```asm -jmp start ; Jump to label 'start' -jmp 4, ret ; Jump to address (4 + ret register) -jeq end ; Jump to 'end' if equal flag set -jgt loop ; Jump to 'loop' if greater than flag set -``` -### Arithmetic Instructions - -| Mnemonic | Operands | Description | -|----------|----------|-------------| -| **ADD** | `src1_reg, src2_reg, dest_reg` | Addition | -| **SUB** | `src1_reg, src2_reg, dest_reg` | Subtraction | -| **IADD** | `src_reg, imm [, dest_reg]` | Immediate addition | -| **ISUB** | `src_reg, imm [, dest_reg]` | Immediate subtraction | -| **INC** | `reg` | Increment register by 1 | -| **DEC** | `reg` | Decrement register by 1 | - -**Examples:** -```asm -add rg0, rg1, rg2 ; rg2 = rg0 + rg1 -sub rg0, rg1, rg2 ; rg2 = rg0 - rg1 -iadd rg0, 10 ; rg0 = rg0 + 10 -// or using alternate syntax -addi rg0, 1 ; rg0 = rg0 + 1 -inc rg0 ; rg0 = rg0 + 1 -``` -### Bitwise Operations - -| Mnemonic | Operands | Description | -|----------|----------|-------------| -| **AND** | `src1_reg, src2_reg, dest_reg` | Bitwise AND | -| **OR** | `src1_reg, src2_reg, dest_reg` | Bitwise OR | -| **XOR** | `src1_reg, src2_reg, dest_reg` | Bitwise XOR | -| **NOT** | `src_reg, dest_reg` | Bitwise NOT | -| **NAND** | `src1_reg, src2_reg, dest_reg` | Bitwise NAND | -| **NOR** | `src1_reg, src2_reg, dest_reg` | Bitwise NOR | -| **XNOR** | `src1_reg, src2_reg, dest_reg` | Bitwise XNOR | - -**Examples:** -```asm -and rg0, rg1, rg2 ; rg2 = rg0 & rg1 -not rg0, rg1 ; rg1 = ~rg0 -``` -### Shift Operations - -| Mnemonic | Operands | Description | -|----------|----------|-------------| -| **SHL** | `reg, shift_amount` | Shift left | -| **SHR** | `reg, shift_amount` | Shift right | - -**Examples:** -```asm -shl rg0, 2 ; Shift rg0 left by 2 bits -shr rg0, 3 ; Shift rg0 right by 3 bits -``` -### Comparison and Control - -| Mnemonic | Operands | Description | -|----------|----------|-------------| -| **CMP** | `reg1, reg2` | Compare registers and set flags | - -**Examples:** -```asm -cmp rg0, zero ; Compare rg0 with zero register -cmp rg1, rg2 ; Compare rg1 with rg2 -``` -### System Instructions - -| Mnemonic | Operands | Description | -|----------|----------|-------------| -| **HLT** | - | Halt processor execution | -| **NOP** | - | No operation | -| **INT** | `interrupt_code` | Trigger interrupt | -| **IRT** | - | Return from interrupt | - -**Examples:** -```asm -hlt ; Stop processor execution -int 0x21 ; Trigger interrupt 0x21 -``` -## Pseudo-Instructions - -### Data Definition - -| Mnemonic | Syntax | Description | -|----------|--------|-------------| -| **DB** | `name: value1 [, value2, ...]` | Define bytes | -| **DH** | `name: value1 [, value2, ...]` | Define half-words | -| **DW** | `name: value1 [, value2, ...]` | Define words | - -**Examples:** -```asm -db message: "Hello World", 0 -dh numbers: 1000, 2000, 3000 -dw stack: 0x10000 -``` -### Memory Reservation - -| Mnemonic | Syntax | Description | -|----------|--------|-------------| -| **RESB** | `name: size` | Reserve bytes | -| **RESH** | `name: size` | Reserve half-words | -| **RESW** | `name: size` | Reserve words | - -**Examples:** -```asm -resb buffer: 256 ; Reserve 256 bytes -resh array: 100 ; Reserve space for 100 half-words -resw heap: 1024 ; Reserve space for 1024 words -``` -### Stack Operations - -| Mnemonic | Operands | Description | -|----------|----------|-------------| -| **PUSH** | `reg` | Push register value onto stack | -| **POP** | `reg` | Pop stack value into register | - -**Examples:** -```asm -push rg0 ; Push rg0 value onto stack -pop ret ; Pop return address -``` -### Memory Access Shortcuts - -| Mnemonic | Operands | Description | -|----------|----------|-------------| -| **LWI** | `name, reg` | Load address into register | - -**Examples:** -```asm -lwi string, rg1 ; Load address of 'string' into rg1 -``` - -### Function Control - -| Mnemonic | Operands | Description | -|----------|----------|-------------| -| **CALL** | `namespace::function` | Call a function with automatic return address management | -| **RETURN** | - | Return from a function to the caller | - -**Examples:** -```asm -call print::print ; Call the print function from the print namespace -return ; Return from the current function -``` - -### Module System - -| Mnemonic | Syntax | Description | -|----------|--------|-------------| -| **INCLUDE** | `module_name "path"` | Include module | - -**Examples:** -```asm -include print "print.dsa" -include fib "fib.dsa" -``` -## Library Examples - -### Multiplication Library (multiply.dsa) - -```asm -// multiply.dsa -// usage: -// -// include multiply "" -// -// usage for multiply: -// push (arg1) -// push (arg0) -// call multiply::multiply -// pop (arg0) -// pop (arg1) - -multiply: - push bpr - mov spr, bpr - - ldw bpr, rg0, 8 // load op 1 - ldw bpr, rg1, 12 // load op 2 - - lli 0, acc // initialize accumulator - -start: - add acc, rg0, acc - dec rg1 - - cmp rg1, zero - jgt start - -end: - stw acc, bpr, 8 // store result for caller - mov bpr, spr - pop bpr - return -``` - -### Print Library (print.dsa) - -```asm -// print.dsa -// usage: -// -// include print "" -// -// usage for print: -// push (register containing address of string) -// call print::print -// pop zero -// -// usage for reset: -// call print::reset - -dw display: 0x20000 -dw current: 0x20000 - -// prints the given text to the screen. -print: - push bpr - mov spr, bpr - - ldw bpr, rg0, 8 // get string address argument - ldw current, rg1 // get current display position - -print_loop: - ldb rg0, acc - stb acc, rg1 - - iadd rg0, 1 - iadd rg1, 1 - - cmp acc, zero - jne print_loop - jmp end - -// return -end: - stw rg1, current - - mov bpr, spr - pop bpr - return - -// resets the cursor position on the screen -reset: - push bpr - mov spr, bpr - ldw display, rg1 - stw rg1, current - mov bpr, spr - pop bpr - return -``` - -### Example Program (main.dsa) - -```asm -include print "./print.dsa" - -dw stack: 0x10000 -db string: "'To confuse your enemy, you must first confuse yourself' - Probably Sun Tzu." - -init: - // set up a stack. - ldw stack, bpr - mov bpr, spr - -start: - lwi string, rg1 - - // push string address argument - push rg1 - // call print function - call print::print - // clean up stack - pop rg1 - - hlt -``` \ No newline at end of file diff --git a/resources/ideas/dsa_binary_format.md b/resources/ideas/dsa_binary_format.md deleted file mode 100644 index 3a372ad..0000000 --- a/resources/ideas/dsa_binary_format.md +++ /dev/null @@ -1,10 +0,0 @@ -# DSA File formatting specification. - -First, a clarification on what formats this document references. - -- .dsb: DSA Binary object, similar to a .o object file -- .dse: DSA Executable file, similar to a .exe/ELF binary - -## Format Specification - -### DSB binary format -- 2.47.3 From 2f91c4127c3e7ba2fd76fde438d444ea1e27d3b3 Mon Sep 17 00:00:00 2001 From: zxq5 Date: Thu, 5 Feb 2026 01:10:31 +0000 Subject: [PATCH 28/53] reorganised code examples --- resources/dsa/example.dsa | 16 ++++++++-------- resources/dsa/example.dsc | 2 -- resources/dsa/main.dsa | 1 - {assembler => resources/examples}/brainf.bf | 0 resources/examples/example.c | 11 +++++++++++ 5 files changed, 19 insertions(+), 11 deletions(-) rename {assembler => resources/examples}/brainf.bf (100%) create mode 100644 resources/examples/example.c diff --git a/resources/dsa/example.dsa b/resources/dsa/example.dsa index 07913c1..0d70938 100644 --- a/resources/dsa/example.dsa +++ b/resources/dsa/example.dsa @@ -1,10 +1,10 @@ // GENERATED BY DSC COMPILER -// Generated at 2026-02-04 01:55:11 +// Generated at 2026-02-05 00:42:40 // Imports -include arena: "./lib/memory/arena_alloc.dsa" include print: "./lib/io/print.dsa" +include arena: "./lib/memory/arena_alloc.dsa" // Globals & Reserved Memory @@ -65,8 +65,8 @@ main: pop zero subi bpr 16 rg0 ldw rg0, rg0 // bpr-24: alloc - push rg0 // bpr-24: alloc - push rg4 // bpr-28: ptr2 + push rg4 // bpr-24: ptr2 + push rg0 // bpr-28: alloc push rg0 // push arg 0 call print::print_hex_word pop zero @@ -78,8 +78,8 @@ main: call print::print_hex_word pop zero call print::print_newline - subi bpr 28 rg0 - ldw rg0, rg0 // bpr-36: ptr2 + subi bpr 24 rg0 + ldw rg0, rg0 // bpr-32: ptr2 push rg0 // bpr-36: ptr2 push rg0 // push arg 0 call print::print_hex_word @@ -110,8 +110,8 @@ main: call print::print_num pop zero call print::print_newline - db str_12: "end" - lwi str_12, rg5 + db str_1: "end" + lwi str_1, rg5 push rg5 // push arg 0 call print::println pop zero diff --git a/resources/dsa/example.dsc b/resources/dsa/example.dsc index 0b49b0c..47ebd9d 100644 --- a/resources/dsa/example.dsc +++ b/resources/dsa/example.dsc @@ -28,5 +28,3 @@ fn main() -> u32 { return 0; } - - diff --git a/resources/dsa/main.dsa b/resources/dsa/main.dsa index 2029c7a..cf0559d 100644 --- a/resources/dsa/main.dsa +++ b/resources/dsa/main.dsa @@ -48,4 +48,3 @@ main: call print::print_num pop zero jmp _ret - diff --git a/assembler/brainf.bf b/resources/examples/brainf.bf similarity index 100% rename from assembler/brainf.bf rename to resources/examples/brainf.bf diff --git a/resources/examples/example.c b/resources/examples/example.c new file mode 100644 index 0000000..2cc3308 --- /dev/null +++ b/resources/examples/example.c @@ -0,0 +1,11 @@ +int factorial(int n) { + if (n <= 1) { + return 1; + } + return n * factorial(n - 1); +} + +int main() { + int res = factorial(3); + return res; +} -- 2.47.3 From c2bf9f6667aba7082e20fbd8faebb10cb453b01e Mon Sep 17 00:00:00 2001 From: zxq5 Date: Thu, 5 Feb 2026 01:10:47 +0000 Subject: [PATCH 29/53] added a (very incomplete) C frontend for DSAC --- compiler/src/frontend/c/lexer.rs | 336 +++++++++++++++++++++ compiler/src/frontend/c/mod.rs | 25 ++ compiler/src/frontend/c/parser.rs | 471 ++++++++++++++++++++++++++++++ 3 files changed, 832 insertions(+) create mode 100644 compiler/src/frontend/c/lexer.rs create mode 100644 compiler/src/frontend/c/mod.rs create mode 100644 compiler/src/frontend/c/parser.rs diff --git a/compiler/src/frontend/c/lexer.rs b/compiler/src/frontend/c/lexer.rs new file mode 100644 index 0000000..1cc5df4 --- /dev/null +++ b/compiler/src/frontend/c/lexer.rs @@ -0,0 +1,336 @@ +// ============================================================================ +// Token Types +// ============================================================================ + +#[derive(Debug, Clone, PartialEq)] +pub enum TokenType { + // Keywords + Int, + If, + Else, + While, + Return, + Include, + + // Identifiers and literals + Identifier(String), + Number(i32), + String(String), + Char(char), + + // Operators + Plus, + Minus, + Star, + Slash, + Assign, + Eq, + Ne, + Lt, + Gt, + Le, + Ge, + + // Delimiters + LParen, + RParen, + LBrace, + RBrace, + Semicolon, + Comma, + Colon, + Namespace, + + Eof, +} + +#[allow(unused)] +pub enum Type { + Int32, + Int16, + Int8, + Uint32, + Uint16, + Uint8, + Char, +} + +#[derive(Debug, Clone)] +pub struct Token { + pub token_type: TokenType, + pub line: usize, + pub col: usize, +} + +impl Token { + pub fn new(token_type: TokenType, line: usize, col: usize) -> Self { + Self { + token_type, + line, + col, + } + } +} + +// ============================================================================ +// Lexer +// ============================================================================ + +pub struct Lexer { + source: Vec, + pos: usize, + line: usize, + col: usize, +} + +impl Lexer { + pub fn new(source: &str) -> Self { + Self { + source: source.chars().collect(), + pos: 0, + line: 1, + col: 1, + } + } + + fn error(&self, msg: &str) -> String { + format!( + "Lexer error at line {}, col {}: {}", + self.line, self.col, msg + ) + } + + fn peek(&self, offset: usize) -> Option { + self.source.get(self.pos + offset).copied() + } + + fn advance(&mut self) -> Option { + if self.pos >= self.source.len() { + return None; + } + let ch = self.source[self.pos]; + self.pos += 1; + if ch == '\n' { + self.line += 1; + self.col = 1; + } else { + self.col += 1; + } + Some(ch) + } + + fn skip_whitespace(&mut self) { + while let Some(ch) = self.peek(0) { + if ch.is_whitespace() { + self.advance(); + } else { + break; + } + } + } + + fn skip_comment(&mut self) { + if self.peek(0) == Some('/') && self.peek(1) == Some('/') { + while let Some(ch) = self.peek(0) { + if ch == '\n' { + break; + } + self.advance(); + } + } + } + + fn read_number(&mut self) -> i32 { + let mut num_str = String::new(); + while let Some(ch) = self.peek(0) { + if ch.is_ascii_digit() { + num_str.push(ch); + self.advance(); + } else { + break; + } + } + num_str.parse().unwrap_or(0) + } + + fn read_identifier(&mut self) -> String { + let mut ident = String::new(); + while let Some(ch) = self.peek(0) { + if ch.is_alphanumeric() || ch == '_' { + ident.push(ch); + self.advance(); + } else { + break; + } + } + ident + } + + fn read_string(&mut self) -> Result { + let mut string = String::new(); + self.advance(); // Consume the opening quote + + while let Some(ch) = self.peek(0) { + if ch == '"' { + self.advance(); // Consume the closing quote + return Ok(string); + } else if ch == '\\' { + self.advance(); // Consume the backslash + if let Some(escaped_char) = self.peek(0) { + string.push(escaped_char); + self.advance(); + } + } else { + string.push(ch); + self.advance(); + } + } + + Err(String::from("Unexpected EOF")) + } + + fn read_char(&mut self) -> Result { + self.advance(); // Consume the opening quote + + if let Some(ch) = self.peek(0) { + self.advance(); + if self.peek(0) == Some('\'') { + self.advance(); + return Ok(ch); + } else { + Err(String::from("expected closing quote")) + } + } else { + Err(String::from("expected character")) + } + } + + pub fn tokenize(&mut self) -> Result, String> { + let mut tokens = Vec::new(); + + loop { + self.skip_whitespace(); + self.skip_comment(); + + if self.pos >= self.source.len() { + break; + } + + let line = self.line; + let col = self.col; + let ch = self.peek(0).unwrap(); + + let token_type = if ch.is_ascii_digit() { + let num = self.read_number(); + TokenType::Number(num) + } else if ch == '"' { + let string = self.read_string()?; + TokenType::String(string) + } else if ch == '\'' { + let char = self.read_char()?; + TokenType::Char(char) + } else if ch.is_alphabetic() || ch == '_' { + let ident = self.read_identifier(); + match ident.as_str() { + "int" => TokenType::Int, + "if" => TokenType::If, + "else" => TokenType::Else, + "while" => TokenType::While, + "return" => TokenType::Return, + "include" => TokenType::Include, + _ => TokenType::Identifier(ident), + } + } else { + match ch { + ':' if self.peek(1) == Some(':') => { + self.advance(); + self.advance(); + TokenType::Namespace + } + ':' => { + self.advance(); + TokenType::Colon + } + '=' if self.peek(1) == Some('=') => { + self.advance(); + self.advance(); + TokenType::Eq + } + '!' if self.peek(1) == Some('=') => { + self.advance(); + self.advance(); + TokenType::Ne + } + '<' if self.peek(1) == Some('=') => { + self.advance(); + self.advance(); + TokenType::Le + } + '>' if self.peek(1) == Some('=') => { + self.advance(); + self.advance(); + TokenType::Ge + } + '+' => { + self.advance(); + TokenType::Plus + } + '-' => { + self.advance(); + TokenType::Minus + } + '*' => { + self.advance(); + TokenType::Star + } + '/' => { + self.advance(); + TokenType::Slash + } + '=' => { + self.advance(); + TokenType::Assign + } + '<' => { + self.advance(); + TokenType::Lt + } + '>' => { + self.advance(); + TokenType::Gt + } + '(' => { + self.advance(); + TokenType::LParen + } + ')' => { + self.advance(); + TokenType::RParen + } + '{' => { + self.advance(); + TokenType::LBrace + } + '}' => { + self.advance(); + TokenType::RBrace + } + ';' => { + self.advance(); + TokenType::Semicolon + } + ',' => { + self.advance(); + TokenType::Comma + } + _ => return Err(self.error(&format!("Unexpected character: {}", ch))), + } + }; + + tokens.push(Token::new(token_type, line, col)); + } + + tokens.push(Token::new(TokenType::Eof, self.line, self.col)); + Ok(tokens) + } +} diff --git a/compiler/src/frontend/c/mod.rs b/compiler/src/frontend/c/mod.rs new file mode 100644 index 0000000..d2e0055 --- /dev/null +++ b/compiler/src/frontend/c/mod.rs @@ -0,0 +1,25 @@ +use common::logging::log; + +use crate::model::{CompilerError, Program}; +use parser::Parser; + +pub mod lexer; +pub mod parser; + +pub fn generate_ast(input: &str) -> Result { + log("Tokenising Input..."); + + let mut lexer = lexer::Lexer::new(&input); + let tokens = lexer.tokenize().map_err(|e| CompilerError::Generic(e))?; + // println!("{tokens:?}"); + + log(&format!("Parsing {} Tokens...", tokens.len())); + + let mut parser = Parser::new(tokens); + let ast = match parser.parse() { + Ok(ast) => ast, + Err(e) => return Err(CompilerError::Generic(e)), + }; + + Ok(ast) +} diff --git a/compiler/src/frontend/c/parser.rs b/compiler/src/frontend/c/parser.rs new file mode 100644 index 0000000..9d4c2bf --- /dev/null +++ b/compiler/src/frontend/c/parser.rs @@ -0,0 +1,471 @@ +// ============================================================================ +// AST Node Types +// ============================================================================ + +use crate::model::{ + BinaryOperator, Block, ConstExpr, Declaration, Dependency, Expression, Name, Program, + Statement, TypeId, UnaryOperator, Variable, +}; + +use super::lexer::{Token, TokenType}; + +// ============================================================================ +// Parser +// ============================================================================ + +pub struct Parser { + tokens: Vec, + pos: usize, +} + +impl Parser { + pub fn new(tokens: Vec) -> Self { + Self { tokens, pos: 0 } + } + + fn error(&self, msg: &str) -> String { + let token = self.current(); + format!( + "Parser error at line {}, col {}: {}", + token.line, token.col, msg + ) + } + + fn current(&self) -> &Token { + self.tokens + .get(self.pos) + .unwrap_or_else(|| self.tokens.last().unwrap()) + } + + fn peek(&self, offset: usize) -> &Token { + self.tokens + .get(self.pos + offset) + .unwrap_or_else(|| self.tokens.last().unwrap()) + } + + fn advance(&mut self) -> &Token { + if self.pos < self.tokens.len() - 1 { + self.pos += 1; + } + self.current() + } + + fn expect(&mut self, expected: TokenType) -> Result { + let token = self.current().clone(); + if std::mem::discriminant(&token.token_type) != std::mem::discriminant(&expected) + { + return Err(self.error(&format!( + "Expected {:?}, got {:?}", + expected, token.token_type + ))); + } + self.advance(); + Ok(token) + } + + pub fn parse(&mut self) -> Result { + let mut declarations = Vec::new(); + + while !matches!(self.current().token_type, TokenType::Eof) { + declarations.push(self.parse_declaration()?); + } + + Ok(Program { declarations }) + } + + fn parse_declaration(&mut self) -> Result { + // check for an import + if let TokenType::Include = self.current().token_type { + self.advance(); + + let name = + if let TokenType::Identifier(id) = self.current().clone().token_type { + Some(id) + } else { + None + } + .ok_or(String::from("Expected identifier"))?; + + self.advance(); + self.expect(TokenType::Colon)?; + + let path = if let TokenType::String(id) = self.current().clone().token_type { + Some(id) + } else { + None + } + .ok_or(String::from("Expected string literal"))?; + + self.advance(); + return Ok(Declaration::Dependency(Dependency { name, path })); + } + + self.expect(TokenType::Int)?; + + let name = match &self.current().token_type { + TokenType::Identifier(s) => s.clone(), + _ => return Err(self.error("Expected identifier")), + }; + self.advance(); + + match &self.current().token_type { + TokenType::LParen => { + // Function declaration + self.advance(); + let mut params = Vec::::new(); + + if !matches!(self.current().token_type, TokenType::RParen) { + self.expect(TokenType::Int)?; + + match &self.current().token_type { + TokenType::Identifier(s) => { + params.push(Variable { + name: s.clone(), + type_id: TypeId::U32, + }); + self.advance(); + } + _ => return Err(self.error("Expected parameter name")), + } + + while matches!(self.current().token_type, TokenType::Comma) { + self.advance(); + self.expect(TokenType::Int)?; + + match &self.current().token_type { + TokenType::Identifier(s) => { + params.push(Variable { + name: s.clone(), + type_id: TypeId::U32, + }); + self.advance(); + } + _ => return Err(self.error("Expected parameter name")), + } + } + } + + self.expect(TokenType::RParen)?; + let body = self.parse_block()?; + + Ok(Declaration::Function { + name, + params, + body, + return_type: TypeId::U32, + }) + } + _ => { + // Variable declaration + let init = if matches!(self.current().token_type, TokenType::Assign) { + self.advance(); + + if let TokenType::Number(n) = self.current().token_type { + self.advance(); + Some(ConstExpr::Number(n)) + } else { + return Err(self + .error("Expected constant in global variable declaration")); + } + } else { + None + }; + + self.expect(TokenType::Semicolon)?; + Ok(Declaration::Variable { + var: Variable { + name, + type_id: TypeId::U32, + }, + init, + is_const: false, + }) + } + } + } + + fn parse_block(&mut self) -> Result { + self.expect(TokenType::LBrace)?; + let mut statements = Vec::new(); + + while !matches!(self.current().token_type, TokenType::RBrace) { + statements.push(self.parse_statement()?); + } + + self.expect(TokenType::RBrace)?; + Ok(statements) + } + + fn parse_statement(&mut self) -> Result { + match &self.current().token_type { + TokenType::LBrace => Ok(Statement::Block(self.parse_block()?)), + TokenType::If => self.parse_if_stmt(), + TokenType::While => self.parse_while_stmt(), + TokenType::Return => self.parse_return_stmt(), + TokenType::Identifier(name) => { + let name = name.clone(); + + // peek ahead for open paren (func call expr) + if matches!(self.peek(1).token_type, TokenType::LParen) { + let expr = self.parse_expression()?; // a function call expr + self.expect(TokenType::Semicolon)?; + return Ok(Statement::Expression { expr }); + } + + self.advance(); // advance past identifier + + // assignment expression + if matches!(self.current().token_type, TokenType::Assign) { + self.advance(); + let expr = self.parse_expression()?; + + self.expect(TokenType::Semicolon)?; + Ok(Statement::Assign { + varname: name, + value: expr, + }) + } + // var expression + else { + self.expect(TokenType::Semicolon)?; + Ok(Statement::Expression { + expr: Expression::Variable { + name: Name { + name, + namespace: None, + }, + expr_type: None, + }, + }) + } + } + TokenType::Int => { + // Local variable declaration + self.advance(); + let name = match &self.current().token_type { + TokenType::Identifier(s) => s.clone(), + _ => return Err(self.error("Expected variable name")), + }; + self.advance(); + + let init = if matches!(self.current().token_type, TokenType::Assign) { + self.advance(); + Some(self.parse_expression()?) + } else { + None + }; + + self.expect(TokenType::Semicolon)?; + + // Convert to assignment expression statement + let expr = if let Some(init_expr) = init { + Statement::Assign { + varname: name, + value: init_expr, + } + } else { + Statement::Assign { + varname: name, + value: Expression::Empty, + } + }; + + Ok(expr) + } + _ => { + let expr = if matches!(self.current().token_type, TokenType::Semicolon) { + Expression::Empty + } else { + self.parse_expression()? + }; + + self.expect(TokenType::Semicolon)?; + Ok(Statement::Expression { expr }) + } + } + } + + fn parse_if_stmt(&mut self) -> Result { + self.expect(TokenType::If)?; + self.expect(TokenType::LParen)?; + let condition = self.parse_expression()?; + self.expect(TokenType::RParen)?; + let then_stmt = self.parse_block()?; + + let else_stmt = if matches!(self.current().token_type, TokenType::Else) { + self.advance(); + self.parse_block()? + } else { + Vec::new() + }; + + Ok(Statement::If { + condition, + then_stmt, + else_stmt, + }) + } + + fn parse_while_stmt(&mut self) -> Result { + self.expect(TokenType::While)?; + self.expect(TokenType::LParen)?; + let condition = self.parse_expression()?; + self.expect(TokenType::RParen)?; + let body = self.parse_block()?; + + Ok(Statement::While { condition, body }) + } + + fn parse_return_stmt(&mut self) -> Result { + self.expect(TokenType::Return)?; + + let expr = if matches!(self.current().token_type, TokenType::Semicolon) { + None + } else { + Some(self.parse_expression()?) + }; + + self.expect(TokenType::Semicolon)?; + Ok(Statement::Return(expr)) + } + + fn parse_expression(&mut self) -> Result { + self.parse_comparison() + } + + fn parse_comparison(&mut self) -> Result { + let mut expr = self.parse_additive()?; + + while let Some(op) = match &self.current().token_type { + TokenType::Eq => Some(BinaryOperator::Eq), + TokenType::Ne => Some(BinaryOperator::Ne), + TokenType::Lt => Some(BinaryOperator::Lt), + TokenType::Gt => Some(BinaryOperator::Gt), + TokenType::Le => Some(BinaryOperator::Le), + TokenType::Ge => Some(BinaryOperator::Ge), + _ => None, + } { + self.advance(); + let right = Box::new(self.parse_additive()?); + expr = Expression::Binary { + op, + left: Box::new(expr), + right, + }; + } + + Ok(expr) + } + + fn parse_additive(&mut self) -> Result { + let mut expr = self.parse_multiplicative()?; + + while let Some(op) = match &self.current().token_type { + TokenType::Plus => Some(BinaryOperator::Add), + TokenType::Minus => Some(BinaryOperator::Sub), + _ => None, + } { + self.advance(); + let right = Box::new(self.parse_multiplicative()?); + expr = Expression::Binary { + op, + left: Box::new(expr), + right, + }; + } + + Ok(expr) + } + + fn parse_multiplicative(&mut self) -> Result { + let mut expr = self.parse_unary()?; + + while let Some(op) = match &self.current().token_type { + TokenType::Star => Some(BinaryOperator::Mul), + TokenType::Slash => Some(BinaryOperator::Div), + _ => None, + } { + self.advance(); + let right = Box::new(self.parse_unary()?); + expr = Expression::Binary { + op, + left: Box::new(expr), + right, + }; + } + + Ok(expr) + } + + fn parse_unary(&mut self) -> Result { + let op = match &self.current().token_type { + TokenType::Plus => Some(UnaryOperator::Plus), + TokenType::Minus => Some(UnaryOperator::Minus), + _ => None, + }; + + if let Some(op) = op { + self.advance(); + let operand = Box::new(self.parse_unary()?); + return Ok(Expression::Unary { op, operand }); + } + + self.parse_primary() + } + + fn parse_primary(&mut self) -> Result { + match &self.current().token_type.clone() { + TokenType::Number(n) => { + let value = *n; + self.advance(); + Ok(Expression::Number(value as isize)) + } + TokenType::Identifier(name) => { + let name = name.clone(); + self.advance(); + + if matches!(self.current().token_type, TokenType::LParen) { + // Function call + self.advance(); + let mut args = Vec::new(); + + if !matches!(self.current().token_type, TokenType::RParen) { + args.push(self.parse_expression()?); + + while matches!(self.current().token_type, TokenType::Comma) { + self.advance(); + args.push(self.parse_expression()?); + } + } + + self.expect(TokenType::RParen)?; + Ok(Expression::Call { + name: Name { + name, + namespace: None, + }, + args, + }) + } else { + Ok(Expression::Variable { + name: Name { + name, + namespace: None, + }, + expr_type: None, + }) + } + } + TokenType::LParen => { + self.advance(); + let expr = self.parse_expression()?; + self.expect(TokenType::RParen)?; + Ok(expr) + } + _ => Err(self.error(&format!( + "Unexpected token: {:?}", + self.current().token_type + ))), + } + } +} -- 2.47.3 From b8abbfd02fc4b7c372e68f06c92da80688b5a4a8 Mon Sep 17 00:00:00 2001 From: zxq5 Date: Thu, 5 Feb 2026 01:11:38 +0000 Subject: [PATCH 30/53] added a brainf&&k module to the compiler (specialised module so no frontend/backend distinction or use of standard model) --- compiler/src/specialised/brainf.rs | 135 +++++++++++++++++++++++++++++ compiler/src/specialised/mod.rs | 13 +++ 2 files changed, 148 insertions(+) create mode 100644 compiler/src/specialised/brainf.rs create mode 100644 compiler/src/specialised/mod.rs diff --git a/compiler/src/specialised/brainf.rs b/compiler/src/specialised/brainf.rs new file mode 100644 index 0000000..fe7a2a9 --- /dev/null +++ b/compiler/src/specialised/brainf.rs @@ -0,0 +1,135 @@ +#[must_use] +pub fn build(src: &str) -> String { + parse(src).join("\n") +} + +#[must_use] +#[expect(clippy::too_many_lines)] +pub fn parse(src: &str) -> Vec { + let stack = "0x10000"; + let acc = "acc"; + let rga = "rga"; + + let bpr = "bpr"; + let spr = "spr"; + let mut instrs = Vec::::new(); + + // Define symbols + let print_start = "print"; + + let tokens = lex(src); + + let mut idstack = Vec::::new(); + + // set up a stack + instrs.push(format!("\tlwi {}, {}", stack, bpr)); + instrs.push(format!("\tmov {}, {}", bpr, spr)); + // set up the data pointer + instrs.push(format!("{}: \t lwi 0x30000, {}", "main", rga)); + + for (id, tok) in tokens.iter().enumerate() { + match tok { + BfToken::Inc => { + instrs.push(format!("\tinc {}", acc)); + } + BfToken::Dec => { + instrs.push(format!("\tdec {}", acc)); + } + BfToken::IncPtr => { + instrs.push(format!("\tstw {}, {}, 0", acc, rga)); + instrs.push(format!("\taddi {}, 4, {}", rga, rga)); + instrs.push(format!("\tlwd {}, {}, 0", rga, acc)); + } + BfToken::DecPtr => { + instrs.push(format!("\tstw {}, {}, 0", acc, rga)); + instrs.push(format!("\tsubi {}, 4, {}", rga, rga)); + instrs.push(format!("\tlwd {}, {}, 0", rga, acc)); + } + BfToken::Out => { + instrs.push(format!("\tpush {}", acc)); + instrs.push(format!("\tcall {}", print_start)); + instrs.push(format!("\tpop zero")); + } + BfToken::In => { + instrs.push(format!("\tlwd 0x40000, {}, 0", acc)); + } + BfToken::Forward => { + let loop_start = format!("loop_start_{}", id); + let loop_end = format!("loop_end_{}", id); + idstack.push(id as u32); + instrs.push(format!("\tcmp {}, zero", acc)); + instrs.push(format!("\tjeq {}, zero", loop_end)); + instrs.push(format!("{}: \tnop", loop_start)); + } + BfToken::Back => { + if let Some(start_id) = idstack.pop() { + let loop_start = format!("loop_start_{}", start_id); + let loop_end = format!("loop_end_{}", start_id); + instrs.push(format!("\tcmp {}, zero", acc)); + instrs.push(format!("\tjne {}, zero", loop_start)); + instrs.push(format!("{}: \tnop", loop_end)); + } else { + eprintln!("Warning: Unmatched ']' at position {}", id); + } + } + } + } + + instrs.push("\thlt".to_string()); + + insert_lib(&mut instrs); + + instrs +} + +fn insert_lib(instrs: &mut Vec) { + let bpr = "bpr"; + let spr = "spr"; + let rg0 = "rg0"; + let rg1 = "rg1"; + + let print_start = "print"; + let current = "current"; + instrs.push(format!("\tdw {}, 0x20000", current)); + instrs.push(format!("{}: \tpush {}", print_start, bpr)); + instrs.push(format!("\tmov {}, {}", spr, bpr)); + instrs.push(format!("\tlwd {}, {}, 8", bpr, rg0)); + instrs.push(format!("\tlwd {}, {}, 0", current, rg1)); + instrs.push(format!("\tstb {}, {}, 0", rg0, rg1)); + instrs.push(format!("\taddi {}, 1, {}", rg1, rg1)); + instrs.push(format!("\tstw {}, {}, 0", rg1, current)); + instrs.push(format!("\tmov {}, {}", bpr, spr)); + instrs.push(format!("\tpop {}", bpr)); + instrs.push("\treturn".to_string()); +} + +enum BfToken { + Inc, + Dec, + IncPtr, + DecPtr, + Out, + In, + Forward, + Back, +} + +fn lex(src: &str) -> Vec { + src.chars() + .filter_map(|c| match c { + '+' => Some(BfToken::Inc), + '-' => Some(BfToken::Dec), + '>' => Some(BfToken::IncPtr), + '<' => Some(BfToken::DecPtr), + '.' => Some(BfToken::Out), + ',' => Some(BfToken::In), + '[' => Some(BfToken::Forward), + ']' => Some(BfToken::Back), + _ => None, + }) + .collect() +} + +fn _create_symbol(id: u32) -> String { + format!("label_{}", id) +} diff --git a/compiler/src/specialised/mod.rs b/compiler/src/specialised/mod.rs new file mode 100644 index 0000000..2ff89ed --- /dev/null +++ b/compiler/src/specialised/mod.rs @@ -0,0 +1,13 @@ +use crate::model::CompilerError; + +pub mod brainf; + +pub fn build_specialised(ext: &str, data: &str) -> Option> { + match ext { + "bf" => { + let res = brainf::build(data); + Some(Ok(res)) + } + _ => None, + } +} -- 2.47.3 From e69514e46e1cea1a5ab6392ae55a2c609a582677 Mon Sep 17 00:00:00 2001 From: zxq5 Date: Thu, 5 Feb 2026 01:26:37 +0000 Subject: [PATCH 31/53] modified editor to include syntax for .dsc files --- dsa_editor/src/syntax/dsc.rs | 25 ++++++++++++++ dsa_editor/src/syntax/mod.rs | 1 + emulator/src/emulator/ui/editor.rs | 11 +++---- src/main.rs | 53 ------------------------------ 4 files changed, 30 insertions(+), 60 deletions(-) create mode 100644 dsa_editor/src/syntax/dsc.rs delete mode 100644 src/main.rs diff --git a/dsa_editor/src/syntax/dsc.rs b/dsa_editor/src/syntax/dsc.rs new file mode 100644 index 0000000..185eb1a --- /dev/null +++ b/dsa_editor/src/syntax/dsc.rs @@ -0,0 +1,25 @@ +use super::Syntax; +use std::collections::BTreeSet; + +impl Syntax { + pub fn dsc() -> Self { + Syntax { + language: "Damn Simple Code", + case_sensitive: false, + comment: "//", + comment_multiline: ["/*", "*/"], + hyperlinks: BTreeSet::from(["http"]), + keywords: BTreeSet::from([ + "include", "fn", "let", "const", "static", "if", "else", "while", "for", + "break", "continue", "loop", "return", + ]), + types: BTreeSet::from([ + "u32", "u16", "u8", "i32", "i16", "i8", "str", "char", "bool", "void", + ]), + special: BTreeSet::from([ + ",", ";", ".", ":", "=", "+", "-", "*", "/", "%", "&", "|", "^", "~", + "!", "?", "<", ">", "<<", ">>", "==", "!=", "<=", ">=", "&&", "||", + ]), + } + } +} diff --git a/dsa_editor/src/syntax/mod.rs b/dsa_editor/src/syntax/mod.rs index 12986f2..7a243e8 100644 --- a/dsa_editor/src/syntax/mod.rs +++ b/dsa_editor/src/syntax/mod.rs @@ -1,5 +1,6 @@ #![allow(dead_code)] pub mod dsa; +pub mod dsc; use std::collections::BTreeSet; use std::hash::{Hash, Hasher}; diff --git a/emulator/src/emulator/ui/editor.rs b/emulator/src/emulator/ui/editor.rs index e1d4b4b..47f4fce 100644 --- a/emulator/src/emulator/ui/editor.rs +++ b/emulator/src/emulator/ui/editor.rs @@ -393,8 +393,9 @@ 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" => Some(Syntax::new("dsa")), - _ => None, + "dsa" => Syntax::dsa(), + "dsc" => Syntax::dsc(), + _ => Syntax::default(), }; let ed = CodeEditor::default() @@ -402,16 +403,12 @@ impl Editor { .with_fontsize(12.0) .with_rows(0) .with_theme(ColorTheme::default()) - .with_syntax(Syntax::dsa()) + .with_syntax(syntax) .with_numlines(true) .desired_width(available_width - 500.0); let mut editor = ed.clone(); - if let Some(syntax) = syntax { - editor = ed.with_syntax(syntax); - } - editor.show(ui, &mut self.text); } diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 5f3e652..0000000 --- a/src/main.rs +++ /dev/null @@ -1,53 +0,0 @@ -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)) - }), - ) -} -- 2.47.3 From 1fcfb3120bd8c34f8ab1b58a52f3b8806b828a1a Mon Sep 17 00:00:00 2001 From: zxq5 Date: Thu, 5 Feb 2026 03:12:44 +0000 Subject: [PATCH 32/53] started working on build system --- Cargo.toml | 2 +- assembler/src/lib.rs | 17 ++++ dsx-build/Cargo.toml | 10 ++ dsx-build/src/main.rs | 223 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 251 insertions(+), 1 deletion(-) create mode 100644 dsx-build/Cargo.toml create mode 100644 dsx-build/src/main.rs diff --git a/Cargo.toml b/Cargo.toml index 9ae7c73..c934ada 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ cargo-features = ["codegen-backend"] [workspace] -members = ["emulator", "common", "assembler", "dsa_editor", "compiler"] +members = ["emulator", "common", "assembler", "dsa_editor", "compiler", "dsx-build"] resolver = "3" [workspace.package] diff --git a/assembler/src/lib.rs b/assembler/src/lib.rs index d40c38a..cf293cd 100644 --- a/assembler/src/lib.rs +++ b/assembler/src/lib.rs @@ -24,5 +24,22 @@ pub mod prelude { pub use crate::tooling::project; } +use std::path::Path; + use num_cpus as _; use threadpool as _; + +use crate::prelude::CompilerEngine; + +pub fn assemble_file(input: &str, output: &str) -> Result<(), std::io::Error> { + let mut engine = CompilerEngine::new(); + engine.start_compilation(Path::new(input)); + let result = engine.wait_for_result().unwrap(); + for instruction in result { + if let Err(e) = std::fs::write(output, instruction.encode().to_be_bytes()) { + eprintln!("Failed to write to output file: {e}"); + std::process::exit(1); + } + } + Ok(()) +} diff --git a/dsx-build/Cargo.toml b/dsx-build/Cargo.toml new file mode 100644 index 0000000..3bce473 --- /dev/null +++ b/dsx-build/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "dsx-build" +version.workspace = true +edition.workspace = true +authors.workspace = true + +[dependencies] +compiler = { path = "../compiler" } +assembler = { path = "../assembler" } +chrono = "0.4.43" diff --git a/dsx-build/src/main.rs b/dsx-build/src/main.rs new file mode 100644 index 0000000..85e9512 --- /dev/null +++ b/dsx-build/src/main.rs @@ -0,0 +1,223 @@ +use std::process::{Command, Stdio}; +use std::{ + env, fs, + path::{Path, PathBuf}, +}; + +/// Run a command and exit on failure. +fn run(cmd: &mut Command) { + let status = cmd.status().expect("failed to execute command"); + if !status.success() { + std::process::exit(1); + } +} + +fn main() { + // Very small CLI – only three sub‑commands. + let args: Vec = env::args().collect(); + if args.len() < 2 { + eprintln!("Usage: dsx-build [options]"); + std::process::exit(1); + } + match args[1].as_str() { + "new" => cmd_new(&args[2..]), + "build" => cmd_build(), + "package" => todo!("Package manager stub – not implemented yet."), + _ => { + eprintln!("Unknown command: {}", args[1]); + std::process::exit(1); + } + } +} + +// ---------- new project ---------------------------------------------------- +fn cmd_new(args: &[String]) { + let mut lang = "dsa"; + for i in 0..args.len() { + if args[i] == "--lang" && i + 1 < args.len() { + lang = &args[i + 1]; + } + } + + // Determine project root: a subdirectory named after the supplied --name argument. + let mut name_opt = None; + for i in 0..args.len() { + if args[i] == "--name" && i + 1 < args.len() { + name_opt = Some(&args[i + 1]); + break; + } + } + + let project_name = match name_opt { + Some(name) => name.to_string(), + None => { + eprintln!("Error: --name argument required"); + std::process::exit(1); + } + }; + + let cwd = env::current_dir().unwrap(); + let src_path = cwd.join(&project_name).join("src"); + fs::create_dir_all(&src_path).expect("Failed to create project directory"); + + match lang { + "dsa" => { + // Minimal DSA binary template. + let path = src_path.join(format!("main.dsa")); + let template = format!( + r#" +// GENERATED BY DSX-BUILD +// Generated at: {timestamp} +// Project name: {project_name} + +// Imports +include print: "./lib/io/print.dsa" + +// Globals & Reserved Memory +dw stack: 0x10000 +db message: "Process Exited with code:" + +// Entry Point +_init: + ldw stack, bpr + mov bpr, spr + push zero + call main + call print::print_newline + lwi message, rg0 + push rg0 + call print::print + pop zero + call print::print_hex_word + pop zero + hlt + + main: + push bpr + mov spr, bpr + + lli 0, rg0 + stw rg0, bpr, 8 + + mov bpr, spr + pop bpr + return"#, + project_name = project_name, + timestamp = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S").to_string() + ); + fs::write(path, template).expect("Unable to write DSA file"); + } + "dsc" => { + let path = src_path.join(format!("main.dsc")); + let template = r#" +include print: "./lib/io/print.dsa"; + +fn main() -> u32 { + return 0; +}"#; + fs::write(path, template).expect("Unable to write DSC file"); + } + _ => { + eprintln!("Unsupported language: {}", lang); + std::process::exit(1); + } + } + + println!( + "Created new {} project in {}.", + lang, + src_path.parent().unwrap().display() + ); +} + +// ---------- build ---------------------------------------------------------- +fn cmd_build() { + let cwd = env::current_dir().unwrap(); + + // Detect .dsc or .dsa files in current directory. + let mut has_dsc = false; + let mut has_dsa = false; + for entry in fs::read_dir(&cwd.join("src")).expect("unable to read dir") { + if let Ok(entry) = entry { + let path = entry.path(); + if path.extension().and_then(|s| s.to_str()) == Some("dsc") { + has_dsc = true; + } else if path.extension().and_then(|s| s.to_str()) == Some("dsa") { + has_dsa = true; + } + } + } + + if !has_dsc && !has_dsa { + eprintln!("No .dsc or .dsa source found in src directory."); + std::process::exit(1); + } + + // Assemble main.dsa to a dsb binary. + println!("Assembling Project to a DSB binary..."); + let build_dir = cwd.join("build"); + fs::create_dir_all(&build_dir).expect("Failed to create build directory"); + + // Copy everything from `cwd/src` to the build directory. + fn copy_recursively(src: &Path, dst: &Path) { + if src.is_file() { + fs::create_dir_all(dst.parent().unwrap()) + .expect("Failed to create parent directory"); + fs::copy(src, dst).expect("Failed to copy file"); + } else if src.is_dir() { + for entry in fs::read_dir(src).expect("Unable to read source dir") { + let entry = entry.expect("Failed to read entry"); + let child_src = entry.path(); + let child_dst = dst.join(entry.file_name()); + copy_recursively(&child_src, &child_dst); + } + } + } + + let src_dir = cwd.join("src"); + if src_dir.exists() { + copy_recursively(&src_dir, &build_dir); + } + + // Change current working directory to the build directory. + env::set_current_dir(&build_dir).expect("Failed to change to build directory"); + + if has_dsc { + println!("Compiling DSC to DSA..."); + fn compile_recursive(path: &Path) { + if path.is_dir() { + for entry in fs::read_dir(path).expect("unable to read dir") { + let entry = entry.expect("failed to read entry"); + compile_recursive(&entry.path()); + } + } else if path.extension().and_then(|s| s.to_str()) == Some("dsc") { + let input_path = path; + let output_path = path.with_extension("dsa"); + compiler::compile_file(&input_path, &output_path).unwrap_or_else(|e| { + eprintln!("Failed to compile {:?}: {}", input_path, e); + std::process::exit(1); + }); + } + } + compile_recursive(&build_dir); + } + + // Replace .dsc with .dsa only in include statements, recursively for each file. + let mut sed_cmd = Command::new("bash"); + sed_cmd.args(&[ + "-c", + &format!( + "find \"{}\" -type f -name '*.dsa' -exec sed -i '/^include/ s/\\.dsc/.dsa/g' {{}} +", + build_dir.display() + ), + ]); + run(&mut sed_cmd); + + fs::create_dir_all(&cwd.join("artifacts")).expect("Failed to create build directory"); + assembler::assemble_file("./main.dsa", "../artifacts/out.dsb").unwrap_or_else(|e| { + eprintln!("Failed to assemble {:?}: {}", "./main.dsa", e); + std::process::exit(1); + }); + + println!("Build finished. Binary at {}/main.dsb", build_dir.display()); +} -- 2.47.3 From bbcef7178f9b53f90d7f9108b49d48fa833c2c63 Mon Sep 17 00:00:00 2001 From: zxq5 Date: Fri, 6 Feb 2026 15:15:10 +0000 Subject: [PATCH 33/53] updated assembler to write to binary files correctly :facepalm: --- assembler/src/lib.rs | 17 +++++++---- assembler/src/main.rs | 17 ++--------- dsx-build/src/main.rs | 65 ++++++++++++++----------------------------- 3 files changed, 34 insertions(+), 65 deletions(-) diff --git a/assembler/src/lib.rs b/assembler/src/lib.rs index cf293cd..8731471 100644 --- a/assembler/src/lib.rs +++ b/assembler/src/lib.rs @@ -24,7 +24,7 @@ pub mod prelude { pub use crate::tooling::project; } -use std::path::Path; +use std::{fs, path::Path}; use num_cpus as _; use threadpool as _; @@ -35,11 +35,16 @@ pub fn assemble_file(input: &str, output: &str) -> Result<(), std::io::Error> { let mut engine = CompilerEngine::new(); engine.start_compilation(Path::new(input)); let result = engine.wait_for_result().unwrap(); - for instruction in result { - if let Err(e) = std::fs::write(output, instruction.encode().to_be_bytes()) { - eprintln!("Failed to write to output file: {e}"); - std::process::exit(1); - } + + let buffer: Vec = result + .iter() + .flat_map(|instruction| instruction.encode().to_be_bytes()) + .collect(); + + if let Err(e) = fs::write(output, buffer) { + eprintln!("Failed to write to output file: {e}"); + std::process::exit(1); } + Ok(()) } diff --git a/assembler/src/main.rs b/assembler/src/main.rs index 53d9d84..7b36083 100644 --- a/assembler/src/main.rs +++ b/assembler/src/main.rs @@ -3,6 +3,7 @@ use num_cpus as _; use threadpool as _; use assembler::{ + assemble_file, prelude::*, tooling::{brainf, project}, }; @@ -46,19 +47,5 @@ fn main() { let input_path = &args[2]; let output_path = &args[4]; - let src = PathBuf::from(input_path); - - // Initialize the compiler engine - let mut compiler = CompilerEngine::new(); - compiler.start_compilation(&src); - - // Or block until done - let result = compiler.wait_for_result().unwrap(); - - for instruction in result { - if let Err(e) = fs::write(output_path, instruction.encode().to_be_bytes()) { - eprintln!("Failed to write to output file: {e}"); - std::process::exit(1); - } - } + assemble_file(input_path, output_path).unwrap(); } diff --git a/dsx-build/src/main.rs b/dsx-build/src/main.rs index 85e9512..0f3fcdf 100644 --- a/dsx-build/src/main.rs +++ b/dsx-build/src/main.rs @@ -4,6 +4,10 @@ use std::{ path::{Path, PathBuf}, }; +use crate::templates::{Dsa, Dsc, Template}; + +mod templates; + /// Run a command and exit on failure. fn run(cmd: &mut Command) { let status = cmd.status().expect("failed to execute command"); @@ -39,6 +43,8 @@ fn cmd_new(args: &[String]) { } } + let lib = args.contains(&"--lib".to_string()); + // Determine project root: a subdirectory named after the supplied --name argument. let mut name_opt = None; for i in 0..args.len() { @@ -64,57 +70,16 @@ fn cmd_new(args: &[String]) { "dsa" => { // Minimal DSA binary template. let path = src_path.join(format!("main.dsa")); - let template = format!( - r#" -// GENERATED BY DSX-BUILD -// Generated at: {timestamp} -// Project name: {project_name} -// Imports -include print: "./lib/io/print.dsa" + let template = Dsa::create(&project_name, lib); -// Globals & Reserved Memory -dw stack: 0x10000 -db message: "Process Exited with code:" - -// Entry Point -_init: - ldw stack, bpr - mov bpr, spr - push zero - call main - call print::print_newline - lwi message, rg0 - push rg0 - call print::print - pop zero - call print::print_hex_word - pop zero - hlt - - main: - push bpr - mov spr, bpr - - lli 0, rg0 - stw rg0, bpr, 8 - - mov bpr, spr - pop bpr - return"#, - project_name = project_name, - timestamp = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S").to_string() - ); fs::write(path, template).expect("Unable to write DSA file"); } "dsc" => { let path = src_path.join(format!("main.dsc")); - let template = r#" -include print: "./lib/io/print.dsa"; -fn main() -> u32 { - return 0; -}"#; + let template = Dsc::create(&project_name, lib); + fs::write(path, template).expect("Unable to write DSC file"); } _ => { @@ -123,6 +88,18 @@ fn main() -> u32 { } } + fs::create_dir_all(src_path.join("lib")).expect("Failed to create lib directory"); + fs::write( + src_path.join("lib/print.dsa"), + templates::create_print_lib(), + ) + .expect("Failed to create print.dsa"); + fs::write( + src_path.join("lib/maths.dsa"), + templates::create_maths_lib(), + ) + .expect("Failed to create maths.dsa"); + println!( "Created new {} project in {}.", lang, -- 2.47.3 From 250b780e146433ac7878c5b6978895e6bc622cef Mon Sep 17 00:00:00 2001 From: zxq5 Date: Sat, 7 Feb 2026 18:20:59 +0000 Subject: [PATCH 34/53] fix broken build system commit --- dsx-build/src/templates.rs | 589 +++++++++++++++++++++++++++++++++++++ 1 file changed, 589 insertions(+) create mode 100644 dsx-build/src/templates.rs diff --git a/dsx-build/src/templates.rs b/dsx-build/src/templates.rs new file mode 100644 index 0000000..b5d9aba --- /dev/null +++ b/dsx-build/src/templates.rs @@ -0,0 +1,589 @@ +pub trait Template { + fn lib(project: &str) -> String; + fn bin(project: &str) -> String; + + fn create(project: &str, lib: bool) -> String { + if lib { + Self::lib(project) + } else { + Self::bin(project) + } + } +} + +pub struct Dsa; +pub struct Dsc; + +impl Template for Dsa { + fn lib(project: &str) -> String { + format!( + r#"// +lib.dsa +// usage: +// +// include {project} "" +// +// usage for {project}_main: +// push (arg1) +// push (arg0) +// call {project}::{project}_main +// pop (arg0) +// pop (arg1) + +// Example data declarations +// dw example_data: 0x0000 + +// Main function template +{project}_main: + // the correct way to start a function as defined by the calling convention + push bpr + mov spr, bpr + + // explanation of how to access args + ldw bpr, rg0, 8 // arg 0 + ldw bpr, rg0, 12 // arg 1 + + // your code goes here + // Example: load example_data into rg1 + // ldw example_data, rg1 + + // the correct way to end a function as defined by the calling convention + mov bpr, spr + pop bpr + return +"#, + ) + } + + fn bin(project: &str) -> String { + format!( + r#" +// GENERATED BY DSX-BUILD +// Generated at: {timestamp} +// Project name: {project} + +// Imports +include print: "./lib/print.dsa" + +// Globals & Reserved Memory +dw stack: 0x10000 +db message: "Process Exited with code:" + +// Entry Point +_init: + ldw stack, bpr + mov bpr, spr + push zero + call main + call print::print_newline + lwi message, rg0 + push rg0 + call print::print + pop zero + call print::print_hex_word + pop zero + hlt + +main: + push bpr + mov spr, bpr + + // Your code goes here + + // Return zero + stw zero, bpr, 8 + + mov bpr, spr + pop bpr + return"#, + timestamp = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S").to_string() + ) + } +} + +impl Template for Dsc { + fn lib(project: &str) -> String { + format!( + r#" +// GENERATED BY DSX-BUILD +// Generated at: {timestamp} +// Project name: {project} + +// Imports +include print: "./lib/print.dsa"; + +// Main Function +fn {project}_main() -> u32 {{ + return 0; +}}"#, + timestamp = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S").to_string() + ) + } + + fn bin(project: &str) -> String { + format!( + r#" +// GENERATED BY DSX-BUILD +// Generated at: {timestamp} +// Project name: {project} + +// Imports +include print: "./lib/print.dsa"; + +// Main Function +fn main() -> u32 {{ + return 0; +}}"#, + timestamp = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S").to_string() + ) + } +} + +pub fn create_print_lib() -> String { + format!( + r#" +// lib: +// print.dsa + +// usage: +// +// include print """ +// +// usage for print: +// push (register containing address of string) +// push pcx +// jmp print::print +// +// usage for reset: +// push pcx +// jmp print::reset +// +// usage for clear: +// push pcx +// jmp print::clear +// +// usage for print_byte: +// push (register containing byte) +// push pcx +// jmp print::print_byte +// +// usage for print_word: +// push (register containing word) +// push pcx +// jmp print::print_word +// +// usage for print_num: +// push (register containing number to print in decimal) +// push pcx +// jmp print::print_num +// + +include maths "./maths.dsa" + +dw display: 0x20000 +dw current: 0x20000 + +// ------------------------------------------ +// prints the string at addr(arg[0]) to the screen. (no trailing whitespace unless explicitly provided) +print: + push bpr + mov spr, bpr + + ldw bpr, rg0, 8 + ldw current, rg1 + +_print_loop: + ldb rg0, acc + cmp acc, zero + jeq _end + stb acc, rg1 + + addi rg0, 1 + addi rg1, 1 + + jmp _print_loop + +// ------------------------------------------ +println: + push bpr + mov spr, bpr + + ldw bpr, rg0, 8 + ldw current, rg1 + +_println_loop: + ldb rg0, acc + cmp acc, zero + jeq _println_end + stb acc, rg1 + + addi rg0, 1 + addi rg1, 1 + + jmp _println_loop + +_println_end: + call print_newline + jmp _end + +// ------------------------------------------ +// prints the value of arg[0] to the screen. +print_word: + // initialise + push bpr + mov spr, bpr + + // load byte into acc + ldw bpr, rg0, 8 + ldw current, rg1 + + addi rg1, 3 + + stb rg0, rg1 + subi rg1, 1 + shr rg0, 8 + stb rg0, rg1 + subi rg1, 1 + shr rg0, 8 + stb rg0, rg1 + subi rg1, 1 + shr rg0, 8 + stb rg0, rg1 + + addi rg1, 4 + jmp _end + +// ------------------------------------------ +// prints the last byte of arg[0] to the screen. +print_byte: + push bpr + mov spr, bpr + + ldw bpr, rg0, 8 + ldw current, rg1 + + stb rg0, rg1 + addi rg1, 1 + jmp _end + +// ------------------------------------------ +// prints the value of arg[0] to the screen in hex. +print_hex_word: + push bpr + mov spr, bpr + + ldw current, rg1 + + ldb bpr, rg0, 8 + push rg0 + call _print_hex_byte + addi spr, 4 + + ldb bpr, rg0, 9 + push rg0 + call _print_hex_byte + addi spr, 4 + + ldb bpr, rg0, 10 + push rg0 + call _print_hex_byte + addi spr, 4 + + ldb bpr, rg0, 11 + push rg0 + call _print_hex_byte + addi spr, 4 + + jmp _end + +// ------------------------------------------ +// prints the last byte of arg[0] to the screen in hex. +print_hex_byte: + push bpr + mov spr, bpr + + ldw bpr, rg0, 8 + ldw current, rg1 + + call _print_hex_byte + jmp _end + +// function body +_print_hex_byte: + // mask to get lower nibble + lli 0xF, rg2 + // save rg0 state + push rg0 + + shr rg0, 4 + and rg0, rg2, rg0 + call _print_hex_nibble + pop rg0 + + and rg0, rg2, rg0 + call _print_hex_nibble + return + +// print a hex digit +_print_hex_nibble: + lli 10, rg3 + cmp rg0, rg3 + jlt _print_hex_nibble_number + addi rg0, 0x37, rg0 + stb rg0, rg1 + addi rg1, 1 + return + +// helper function. +_print_hex_nibble_number: + addi rg0, 0x30, rg0 + stb rg0, rg1 + addi rg1, 1 + return + +// ------------------------------------------ +// print whitespace +print_whitespace: + push bpr + mov spr, bpr + + ldw current, rg1 + lli 0x20, rg0 + stb rg0, rg1 + addi rg1, 1 + jmp _end + +// ------------------------------------------ +// print newline +print_newline: + push bpr + mov spr, bpr + + // load variables into registers + ldw display, rg0 + ldw current, rg1 + + // get the offset from the display base + sub rg1, rg0, rg0 + + lwi 80, rg2 + pusha 3 + push rg0 + push rg2 + call maths::divmod + pop zero // result + pop rg3 // remainder + popa 3 + + sub rg1, rg3, rg2 + addi rg2, 80, rg1 + + // _end saves the display state + jmp _end + +// ------------------------------------------ +// prints arg[0] as a decimal number to the screen. +print_num: + push bpr + mov spr, bpr + + ldw bpr, rg0, 8 // load number to print + lli 0, rg5 // rg5 = digit counter + + // check if number is zero + cmp rg0, zero + jne _print_num_extract_digits + + // special case: print '0' for zero + lli 0x30, rg6 + push rg6 // push digit to stack buffer + lli 1, rg5 // we have 1 digit + jmp _print_num_output + +_print_num_extract_digits: + // divide by 10 repeatedly to get digits + cmp rg0, zero + jeq _print_num_output + + // call divmod(rg0, 10) + push rg0 // dividend + lli 10, rg1 + push rg1 // divisor (10) + call maths::divmod + pop rg0 // quotient (continue dividing this) + pop rg1 // remainder (the digit) + + // convert digit to ASCII and push to stack buffer + addi rg1, 0x30, rg6 // convert to ASCII + push rg6 // push digit to stack + inc rg5 // increment digit counter + + jmp _print_num_extract_digits + +_print_num_output: + // now print digits (pop them off in reverse order) + ldw current, rg1 // get display pointer + +_print_num_output_loop: + // check if we've printed all digits + cmp rg5, zero + jeq _print_num_done + + // pop digit and print it + pop rg6 + stb rg6, rg1 + addi rg1, 1 + dec rg5 + + jmp _print_num_output_loop + +_print_num_done: + jmp _end + +// ------------------------------------------ +// resets the cursor position on the screen to 0x20000. (0,0) +reset: + push bpr + mov spr, bpr + ldw display, rg1 + jmp _end + +// ------------------------------------------ +// clears the screen +clear: + push bpr + mov spr, bpr + // display size = 2000 bytes / 500 words + lli 500 rg0 + ldw display, rg1 + +_clear_loop: + dec rg0 + stw zero, rg1 + addi rg1, 4 + cmp rg0, zero + jgt _clear_loop + jmp _end + +// ------------------------------------------ +// return +_end: + stw rg1, current + + mov bpr, spr + pop bpr + return +"# + ) +} + +pub fn create_maths_lib() -> String { + format!( + r#" +// multiply.dsa +// usage: +// +// include multiply "" +// +// usage for multiply: +// push (arg1) +// push (arg0) +// call multiply::multiply +// pop (arg0) +// pop (arg1) + +multiply: + push bpr + mov spr, bpr + + ldw bpr, rg0, 8 // load op 2 + ldw bpr, rg1, 12 // load op 1 + lwi 0, rg2 // initialise rg2 to zero + +_multiply_loop: + add rg2, rg0, rg2 + dec rg1 + + cmp rg1, zero + jgt _multiply_loop + +_multiply_end: + stw rg2, bpr, 8 + + mov bpr, spr + pop bpr + return + +divmod: + push bpr + mov spr, bpr + + ldw bpr, rg1, 8 // load op 2 + ldw bpr, rg0, 12 // load op 1 + + lli 0, rg3 + +_divmod_loop: + cmp rg0, rg1 + jlt _divmod_end + + sub rg0, rg1, rg0 + inc rg3 + + jmp _divmod_loop + +_divmod_end: + // store div in first arg + // store mod in second arg + stw rg3, bpr, 8 + stw rg0, bpr, 12 + + mov bpr, spr + pop bpr + return + +// multiply.dsa - improved version +// Multiplies two 32-bit numbers using shift-and-add +// +// Usage: +// push operand2 (multiplier) +// push operand1 (multiplicand) +// call multiply::multiply +// pop result +// pop zero (discard second argument) + +new_multiply: + push bpr + mov spr, bpr + + ldw bpr, rg0, 8 // rg0 = multiplicand + ldw bpr, rg1, 12 // rg1 = multiplier + + lli 0, rg2 // rg2 = result (accumulator) + lli 32, rg3 // rg3 = bit counter + +mult_loop: + // Check if lowest bit of multiplier is 1 + lli 1, acc + and rg1, acc, acc // acc = rg1 & 1 + cmp acc, zero + jeq skip_add // if (rg1 & 1) == 0, skip addition + + // Add multiplicand to result + add rg2, rg0, rg2 + +skip_add: + shl rg0, 1 // shift multiplicand left + shr rg1, 1 // shift multiplier right + + dec rg3 + cmp rg3, zero + jgt mult_loop + + stw rg2, bpr, 8 // store result + mov bpr, spr + pop bpr + return +"# + ) +} -- 2.47.3 From e9329eca95bed523d6542016b46da58b45330ad4 Mon Sep 17 00:00:00 2001 From: zxq5 Date: Sat, 7 Feb 2026 18:21:37 +0000 Subject: [PATCH 35/53] update roadmap and ISA spec --- docs/DSA_Assembly_Reference.md | 693 ++++++++++++++++++++++----------- docs/DSA_ISA_Specification.md | 118 +++--- docs/DSA_Project_Roadmap.md | 20 +- 3 files changed, 557 insertions(+), 274 deletions(-) diff --git a/docs/DSA_Assembly_Reference.md b/docs/DSA_Assembly_Reference.md index 7f99089..7c6a81d 100644 --- a/docs/DSA_Assembly_Reference.md +++ b/docs/DSA_Assembly_Reference.md @@ -42,20 +42,25 @@ stw rg0, rg1, 8 ; Store rg0 TO address (rg1+8) | Register(s) | Type | Description | Usage Notes | |-------------|------|-------------|-------------| | **rg0-rgf** | General | 16 general-purpose registers | Use for variables, temporaries | -| **acc** | Special | Accumulator | ⚠️ Volatile - pseudo-instructions may overwrite | -| **spr** | Special | Stack pointer | Points to top of stack | +| **acc** | Special | Accumulator | ⚠️ **VOLATILE** - Used as scratch by pseudo-instructions | +| **spr** | Special | Stack pointer | Points to current top of stack | | **bpr** | Special | Base pointer | Used for stack frames | -| **ret** | Special | Return address | Holds return address for functions | +| **ret** | Special | Return address | Holds function return addresses | | **zero** | Read-only | Always zero | Reads return 0, writes discarded | | **pcx** | Read-only | Program counter | Cannot be written directly | | **idr** | Privileged | Interrupt descriptor table | Kernel mode only | | **mmr** | Privileged | Memory map register | Kernel mode only | | **noreg** | Placeholder | No register | Used in encoding, triggers fault if accessed | +**Alternative Names:** +- **noreg** can also be referenced as **null** in assembly + **Register Conventions:** -- **acc**: Used by pseudo-instructions for temporary values - do not rely on it being preserved -- **rgf**: Used by label-addressing pseudo-instructions as a scratch register -- **rg0-rge**: Available for general use; calling convention defines which are preserved +- **acc**: Volatile scratch register used by pseudo-instructions - **never preserved across pseudo-ops** +- **rg0-rge**: Available for general use; calling convention defines preservation rules +- **rgf**: General purpose register, available for use + +**⚠️ CRITICAL:** The `acc` register is used internally by label-based memory operations and other pseudo-instructions. Do not assume its value is preserved across any pseudo-instruction! ## Hardware Instructions @@ -130,36 +135,50 @@ lli 0x1234, rg0 ; rg0 = 0x00001234 lui 0xABCD, rg0 ; rg0 = 0xABCD1234 ``` +**Note:** The assembler may process the immediate value for `lui` - specify the upper 16 bits directly (e.g., `lui 0xABCD, rg0` to set upper bits to 0xABCD). + **Loading Addresses:** See `lwi` pseudo-instruction ### Jump and Branch Instructions ```asm -jmp addr [, offset_reg] ; Unconditional jump -jeq addr [, offset_reg] ; Jump if equal -jne addr [, offset_reg] ; Jump if not equal -jgt addr [, offset_reg] ; Jump if greater than -jge addr [, offset_reg] ; Jump if greater or equal -jlt addr [, offset_reg] ; Jump if less than -jle addr [, offset_reg] ; Jump if less or equal +jmp offset, base_reg ; Unconditional jump to (base_reg + offset) +jeq offset, base_reg ; Jump if equal +jne offset, base_reg ; Jump if not equal +jgt offset, base_reg ; Jump if greater than +jge offset, base_reg ; Jump if greater or equal +jlt offset, base_reg ; Jump if less than +jle offset, base_reg ; Jump if less or equal +``` + +**Target Address Calculation:** +``` +target = base_reg + sign_extend(offset) ``` **Jump Modes:** ```asm ; Absolute jump (using zero register) jmp label, zero ; Jump to label address -jmp 0x4000, zero ; Jump to absolute address 0x4000 -; Register-based jump +; Register-based jump jmp 0, ret ; Jump to address in ret register jmp 4, ret ; Jump to (ret + 4) -; PC-relative (if assembler supports) -jeq loop_start ; Jump to loop_start if equal flag set +; PC-relative (if assembler supports label resolution) +jeq loop_start, zero ; Jump to loop_start if equal flag set ``` **Conditional Jumps:** Based on flags set by `cmp` instruction +**Examples:** +```asm +jmp start, zero ; Absolute jump to 'start' +jmp 4, ret ; Jump to (ret + 4) +jeq end, zero ; Jump to 'end' if equal flag set +jgt loop, zero ; Jump to 'loop' if greater than flag set +``` + ### Comparison ```asm @@ -168,17 +187,17 @@ cmp reg1, reg2 ; Compare reg1 with reg2, set flags **Flags Set:** - Equal: `reg1 == reg2` -- GreaterThan: `reg1 > reg2` -- LessThan: `reg1 < reg2` +- GreaterThan: `reg1 > reg2` (signed comparison) +- LessThan: `reg1 < reg2` (signed comparison) - GreaterThanOrEqual: `reg1 >= reg2` - LessThanOrEqual: `reg1 <= reg2` **Example:** ```asm cmp rg0, zero ; Compare rg0 with 0 -jeq is_zero ; Branch if rg0 == 0 -jgt is_positive ; Branch if rg0 > 0 -jlt is_negative ; Branch if rg0 < 0 +jeq is_zero, zero ; Branch if rg0 == 0 +jgt is_positive, zero ; Branch if rg0 > 0 +jlt is_negative, zero ; Branch if rg0 < 0 ``` ### Arithmetic Instructions @@ -186,8 +205,8 @@ jlt is_negative ; Branch if rg0 < 0 ```asm add src1, src2, dest ; dest = src1 + src2 sub src1, src2, dest ; dest = src1 - src2 -iadd src, immediate, dest ; dest = src + immediate -isub src, immediate, dest ; dest = src - immediate +iadd src, immediate, dest ; dest = src + immediate (DEST REQUIRED!) +isub src, immediate, dest ; dest = src - immediate (DEST REQUIRED!) inc reg ; reg = reg + 1 dec reg ; reg = reg - 1 ``` @@ -196,13 +215,14 @@ dec reg ; reg = reg - 1 ```asm add rg0, rg1, rg2 ; rg2 = rg0 + rg1 sub rg0, rg1, rg2 ; rg2 = rg0 - rg1 -iadd rg0, 10, rg0 ; rg0 = rg0 + 10 +iadd rg0, 10, rg0 ; rg0 = rg0 + 10 (in-place) +iadd rg0, 10, rg1 ; rg1 = rg0 + 10 (separate dest) isub rg1, 5, rg2 ; rg2 = rg1 - 5 inc spr ; spr = spr + 1 dec spr ; spr = spr - 1 ``` -**Note:** For `iadd`/`isub`, destination can be the same as source for in-place operations. +**⚠️ Note:** For `iadd`/`isub`, the destination register is **required** (not optional). For in-place operations, specify the source register as both source and destination. ### Bitwise Logical Operations @@ -232,14 +252,16 @@ shr reg, shift_amount ; Shift right by amount (0-31) ``` **Shift Amount:** -- Can be a literal: `shl rg0, 2` (shift by 2) -- Can be a register: `shl rg0, rg1` (shift by value in rg1, uses low 5 bits) +- **Literal:** `shl rg0, 2` - shift by constant 2 +- **Register:** `shl rg0, rg1` - shift by value in rg1 (low 5 bits) + +**⚠️ Note:** Current assembler may only support literal shift amounts. Check your assembler documentation for register shift support. **Examples:** ```asm shl rg0, 2 ; rg0 = rg0 << 2 shr rg1, 3 ; rg1 = rg1 >> 3 -shl rg0, rg1 ; rg0 = rg0 << (rg1 & 0x1F) +; shl rg0, rg1 ; May not be supported by assembler ``` **Note:** Shift right is logical (zero-fill), not arithmetic @@ -265,6 +287,8 @@ irt ; Return from interrupt handler Pseudo-instructions are assembly-level constructs that expand into one or more hardware instructions. +**⚠️ IMPORTANT:** Many pseudo-instructions use the `acc` register as a scratch register. The value in `acc` is **not preserved** across these operations! + ### Data Definition Directives ```asm @@ -282,7 +306,7 @@ dw stack_base: 0x10000 ; Single word value dw table: 0, 0, 0, 0 ; Array of 4 words ``` -**String Encoding:** Strings are encoded as byte sequences with escape sequences: +**String Encoding:** Strings are automatically null-terminated and encoded as byte sequences with escape sequences: - `\n` = newline (0x0A) - `\t` = tab (0x09) - `\r` = carriage return (0x0D) @@ -290,6 +314,8 @@ dw table: 0, 0, 0, 0 ; Array of 4 words - `\"` = double quote - `\0` = null (0x00) +**⚠️ Endianness Note:** Numeric values in data directives are stored in **big-endian** format by the assembler, unlike the little-endian used for instructions. + ### Memory Reservation Directives ```asm @@ -314,19 +340,19 @@ push reg ; Push register onto stack pop reg ; Pop stack into register ``` +**⚠️ CRITICAL - Stack Direction:** The DSA stack grows **DOWNWARD** (toward lower memory addresses). + **Expansion:** ```asm ; push rg0 expands to: -iadd spr, 4, spr ; spr = spr + 4 (stack grows up) +subi spr, 4, spr ; spr = spr - 4 (allocate space, stack grows down) stw rg0, spr, 0 ; Store rg0 to [spr] ; pop rg0 expands to: ldw spr, rg0, 0 ; Load [spr] into rg0 -isub spr, 4, spr ; spr = spr - 4 +addi spr, 4, spr ; spr = spr + 4 (deallocate space) ``` -**Note:** DSA stack grows upward (toward higher addresses). - **Examples:** ```asm push rg0 ; Save rg0 on stack @@ -336,6 +362,51 @@ pop rg1 ; Restore rg1 pop rg0 ; Restore rg0 ``` +**Stack Diagram:** +``` +Higher Memory Addresses + ↑ + │ (old data) + ├─────────────┤ + │ rg0 value │ ← After first push + ├─────────────┤ ← SPR after first push + │ rg1 value │ ← After second push + ├─────────────┤ ← SPR after second push (stack grew down) + ↓ +Lower Memory Addresses +``` + +### Push/Pop Multiple Registers + +```asm +pusha count ; Push first 'count' general registers (rg0-rgN) +popa count ; Pop first 'count' general registers +``` + +**Examples:** +```asm +pusha 4 ; Push rg0, rg1, rg2, rg3 +; ... do work ... +popa 4 ; Pop rg3, rg2, rg1, rg0 +``` + +**Expansion:** +```asm +; pusha 3 expands to: +subi spr, 12, spr ; Allocate space for 3 words +stw rg0, spr, 0 ; Store rg0 +stw rg1, spr, 4 ; Store rg1 +stw rg2, spr, 8 ; Store rg2 + +; popa 3 expands to: +ldw spr, rg0, 0 ; Load rg0 +ldw spr, rg1, 4 ; Load rg1 +ldw spr, rg2, 8 ; Load rg2 +addi spr, 12, spr ; Deallocate space +``` + +**Note:** Registers are pushed/popped in order (rg0, rg1, rg2, ...). + ### Load Address Pseudo-Instruction ```asm @@ -354,7 +425,7 @@ lui message, rg0 ; Load upper 16 bits of address db message: "Hello!", 0 lwi message, rg0 ; rg0 = address of message -ldb rg0, rg1 ; rg1 = first byte of message ('H') +ldb rg0, rg1, 0 ; rg1 = first byte of message ('H') ``` ### Memory Access with Labels @@ -370,49 +441,66 @@ sth src_reg, label [, offset] stw src_reg, label [, offset] ``` -**Expansion (uses rgf as scratch):** +**⚠️ CRITICAL:** These pseudo-instructions use `acc` as a scratch register! The value in `acc` will be overwritten. + +**Expansion:** ```asm -; ldb buffer, rg2 expands to: -lli buffer, rgf ; Load lower 16 bits of buffer address -lui buffer, rgf ; Load upper 16 bits of buffer address -ldb rgf, rg2, 0 ; Load byte from address in rgf +; ldw buffer, rg2 expands to: +lli buffer, acc ; Load lower 16 bits of buffer address into acc +lui buffer, acc ; Load upper 16 bits of buffer address into acc +ldw acc, rg2, 0 ; Load word from address in acc into rg2 ; stw rg1, current expands to: -lli current, rgf ; Load lower 16 bits of current address -lui current, rgf ; Load upper 16 bits of current address -stw rg1, rgf, 0 ; Store word to address in rgf +lli current, acc ; Load lower 16 bits of current address into acc +lui current, acc ; Load upper 16 bits of current address into acc +stw rg1, acc, 0 ; Store word from rg1 to address in acc ``` -**⚠️ Important:** These pseudo-instructions use `rgf` as a scratch register! Do not use `rgf` for other purposes when using label-based memory access. - **Examples:** ```asm dw counter: 0 -ldw counter, rg0 ; Load value of counter -iadd rg0, 1, rg0 ; Increment -stw rg0, counter ; Store back +ldw counter, rg0 ; Load value of counter (clobbers acc!) +addi rg0, 1, rg0 ; Increment +stw rg0, counter ; Store back (clobbers acc!) ``` -### Function Call Pseudo-Instructions +**⚠️ Warning Example - ACC Clobbering:** +```asm +lli 100, acc ; acc = 100 +ldw data, rg0 ; ACC IS NOW OVERWRITTEN! +; acc now contains garbage (address calculation temp) +; The value 100 is LOST! +``` + +# DSA Assembly Language Reference - Part 2 + +## Function Call Pseudo-Instructions ```asm call namespace::function ; Call function from included module return ; Return from function ``` -**Expansion:** +**⚠️ CRITICAL:** The calling mechanism uses the **STACK** to store return addresses, not just the `ret` register! + +**CALL Expansion:** ```asm ; call print::print expands to: -lwi print::print, ret ; Load function address into ret -jmp 0, ret ; Jump to function (saves return in pcx) -; (The assembler/linker resolves namespace::function to address) - -; return expands to: -jmp 0, ret ; Jump to address in ret register +subi spr, 4, spr ; Decrement stack pointer (allocate space) +stw pcx, spr, 0 ; Store return address (PCX) on stack +jmp function_addr, zero ; Jump to function address ``` -**Note:** The actual return address handling may be more complex depending on the calling convention. +**RETURN Expansion:** +```asm +; return expands to: +ldw spr, ret, 0 ; Load return address from stack into ret +addi spr, 4, spr ; Increment stack pointer (deallocate) +jmp 4, ret ; Jump to (ret + 4) +``` + +**Note on RETURN +4 Offset:** The implementation adds 4 to the return address. This may be to account for instruction sizing or pipeline considerations. Consult CPU documentation if modifying. ### Module System @@ -437,25 +525,44 @@ call math::multiply ## Calling Convention -DSA uses a standard calling convention for function calls. +DSA uses a stack-based calling convention with downward-growing stack. ### Stack Frame Layout +**⚠️ CRITICAL:** Stack grows DOWNWARD (toward lower addresses)! + ``` -Higher Addresses -├─────────────┤ -│ Arg N │ ← spr + (8 + 4*(N-1)) -│ ... │ -│ Arg 2 │ ← spr + 16 -│ Arg 1 │ ← spr + 12 -│ Arg 0 │ ← spr + 8 (first argument) -├─────────────┤ -│ Ret Addr │ ← spr + 4 (return address) -├─────────────┤ -│ Old BPR │ ← spr + 0 (saved base pointer) -├─────────────┤ ← bpr, spr (current frame) -│ Locals │ (local variables, if any) -Lower Addresses +Higher Memory Addresses ↑ + │ + ┌─────────────┐ + │ Caller's │ + │ Frame │ + └─────────────┘ + │ Arg N │ ← Caller pushed (highest address argument) + │ ... │ + │ Arg 2 │ + │ Arg 1 │ + │ Arg 0 │ ← First argument (lowest address argument) + ├─────────────┤ + │ Ret Addr │ ← Return address (pushed by CALL) + ├─────────────┤ + │ Old BPR │ ← Saved base pointer (pushed by callee) + ├─────────────┤ ← BPR (current base), SPR (current top) + │ Locals │ ← Local variables (if any) + │ ... │ + └─────────────┘ ← SPR grows downward + │ +Lower Memory Addresses ↓ +``` + +**Frame Pointer Offsets** (from BPR): +``` +bpr + 0 = Old BPR (saved by function prologue) +bpr + 4 = Return address (pushed by CALL) +bpr + 8 = Argument 0 (first argument) +bpr + 12 = Argument 1 +bpr + 16 = Argument 2 +bpr + 8 + 4*N = Argument N ``` ### Calling Sequence @@ -464,19 +571,19 @@ Lower Addresses 1. **Push arguments in reverse order** (last argument first): ```asm -push arg2 +push arg2 ; Push last argument first push arg1 -push arg0 +push arg0 ; First argument pushed last ``` 2. **Call the function:** ```asm -call namespace::function +call namespace::function ; Pushes return address, jumps to function ``` 3. **Clean up arguments** after return: ```asm -pop zero ; Discard or retrieve arg0 +pop zero ; Discard arg0 (or pop rg0 to get return value) pop zero ; Discard arg1 pop zero ; Discard arg2 ``` @@ -486,15 +593,15 @@ pop zero ; Discard arg2 1. **Set up stack frame:** ```asm function: - push bpr ; Save old base pointer + push bpr ; Save caller's base pointer mov spr, bpr ; Establish new base pointer ``` 2. **Access arguments:** ```asm - ldw bpr, rg0, 8 ; Load arg0 from spr+8 - ldw bpr, rg1, 12 ; Load arg1 from spr+12 - ldw bpr, rg2, 16 ; Load arg2 from spr+16 + ldw bpr, rg0, 8 ; Load arg0 from bpr+8 + ldw bpr, rg1, 12 ; Load arg1 from bpr+12 + ldw bpr, rg2, 16 ; Load arg2 from bpr+16 ``` 3. **Execute function body:** @@ -503,56 +610,101 @@ function: add rg0, rg1, acc ; Example: acc = arg0 + arg1 ``` -4. **Store return value** (optional, overwrites arg0): +4. **Store return value** (optional - overwrites arg0 location): ```asm stw acc, bpr, 8 ; Store result where arg0 was ``` 5. **Restore stack frame:** ```asm - mov bpr, spr ; Restore stack pointer - pop bpr ; Restore old base pointer + mov bpr, spr ; Restore stack pointer to base + pop bpr ; Restore caller's base pointer ``` 6. **Return to caller:** ```asm - return + return ; Pop return address and jump ``` -### Complete Example +### Stack Evolution During Call + +**Before CALL:** +``` + ┌─────────────┐ + │ Arg 2 │ + │ Arg 1 │ + │ Arg 0 │ ← SPR points here + └─────────────┘ +``` + +**After CALL (before function prologue):** +``` + ┌─────────────┐ + │ Arg 2 │ + │ Arg 1 │ + │ Arg 0 │ + │ Ret Addr │ ← SPR points here (CALL pushed this) + └─────────────┘ +``` + +**After Function Prologue:** +``` + ┌─────────────┐ + │ Arg 2 │ + │ Arg 1 │ + │ Arg 0 │ + │ Ret Addr │ + │ Old BPR │ ← SPR and BPR point here + └─────────────┘ +``` + +### Complete Calling Example ```asm ; Function: add two numbers ; Args: arg0, arg1 -; Returns: sum in arg0 position +; Returns: sum (overwrites arg0 position) add_function: - push bpr ; Save base pointer - mov spr, bpr ; Set up stack frame + ; Prologue + push bpr ; Save caller's base pointer + mov spr, bpr ; Set up our stack frame - ldw bpr, rg0, 8 ; Load arg0 - ldw bpr, rg1, 12 ; Load arg1 + ; Load arguments + ldw bpr, rg0, 8 ; rg0 = arg0 + ldw bpr, rg1, 12 ; rg1 = arg1 + + ; Perform operation add rg0, rg1, acc ; acc = arg0 + arg1 - stw acc, bpr, 8 ; Store result + ; Store return value (overwrites arg0 position) + stw acc, bpr, 8 ; Store result at bpr+8 - mov bpr, spr ; Restore stack - pop bpr ; Restore base pointer - return + ; Epilogue + mov bpr, spr ; Restore stack pointer + pop bpr ; Restore caller's base pointer + return ; Return to caller -; Caller: +; Caller example: main: + ; Set up stack lwi stack_base, bpr mov bpr, spr - lli 5, rg0 - lli 7, rg1 + ; Prepare arguments + lli 5, rg0 ; First argument = 5 + lli 7, rg1 ; Second argument = 7 - push rg1 ; Push arg1 (7) - push rg0 ; Push arg0 (5) + ; Push arguments (reverse order!) + push rg1 ; Push arg1 (7) first + push rg0 ; Push arg0 (5) second + + ; Call function call local::add_function - pop rg2 ; Get result (12) - pop zero ; Discard arg1 + + ; Retrieve result and clean up + pop rg2 ; Get result (12) - was at arg0 position + pop zero ; Discard arg1 slot hlt @@ -561,20 +713,45 @@ dw stack_base: 0x10000 ### Register Usage Conventions -| Register(s) | Usage | Preserved? | -|-------------|-------|------------| -| rg0-rg3 | Function arguments, temporaries | No (caller-saved) | -| rg4-rge | Local variables | Yes (callee-saved if used) | -| rgf | Scratch (used by label addressing) | No | -| acc | Temporary calculations | No | -| spr | Stack pointer | Yes (must be restored) | -| bpr | Base pointer | Yes (must be restored) | -| ret | Return address | Managed by call/return | +| Register(s) | Usage | Preserved Across Calls? | +|-------------|-------|------------------------| +| **rg0-rg3** | Function arguments, temporaries | No (caller-saved) | +| **rg4-rge** | Local variables | Conditional (callee-saved if used) | +| **rgf** | General purpose | Conditional (callee-saved if used) | +| **acc** | Temporary calculations, scratch | **Never** (volatile) | +| **spr** | Stack pointer | Yes (must be restored) | +| **bpr** | Base pointer | Yes (must be restored) | +| **ret** | Return address | Managed by call/return | -**Notes:** -- Functions should save and restore rg4-rge if they use them -- rg0-rg3 may be overwritten by called functions -- acc and rgf are volatile - assume they're overwritten +**Preservation Rules:** +- **Caller-saved (rg0-rg3, acc):** Caller must save these before calling if needed after +- **Callee-saved (rg4-rgf):** Callee must save/restore if it uses them +- **Always preserved (spr, bpr):** Must be restored to original values +- **Never preserved (acc):** Assume destroyed by any operation + +**Example - Callee-Saved Registers:** +```asm +my_function: + push bpr + mov spr, bpr + + ; We want to use rg4 and rg5 - must save them! + push rg4 + push rg5 + + ; Use rg4 and rg5 for local work + lli 100, rg4 + lli 200, rg5 + add rg4, rg5, acc + + ; Restore callee-saved registers before returning + pop rg5 + pop rg4 + + mov bpr, spr + pop bpr + return +``` ## Complete Examples @@ -586,18 +763,18 @@ dw stack_base: 0x10000 // // Usage: // include multiply "multiply.dsa" -// push arg1 -// push arg0 +// push multiplicand +// push multiplier // call multiply::multiply -// pop result -// pop zero ; discard second argument +// pop result ; Result is in arg0 position +// pop zero ; Clean up arg1 multiply: push bpr mov spr, bpr - ldw bpr, rg0, 8 ; Load multiplier - ldw bpr, rg1, 12 ; Load multiplicand + ldw bpr, rg0, 8 ; Load multiplier (arg0) + ldw bpr, rg1, 12 ; Load multiplicand (arg1) lli 0, acc ; Initialize result to 0 @@ -606,9 +783,9 @@ loop_start: dec rg1 ; multiplicand-- cmp rg1, zero - jgt loop_start ; Continue if multiplicand > 0 + jgt loop_start, zero ; Continue if multiplicand > 0 - stw acc, bpr, 8 ; Store result for caller + stw acc, bpr, 8 ; Store result for caller (at arg0) mov bpr, spr pop bpr @@ -639,19 +816,19 @@ print: mov spr, bpr ldw bpr, rg0, 8 ; Get string address argument - ldw current, rg1 ; Get current cursor position + ldw current, rg1 ; Get current cursor position (clobbers acc!) print_loop: - ldb rg0, acc ; Load character - stb acc, rg1 ; Store to display + ldb rg0, acc, 0 ; Load character + stb acc, rg1, 0 ; Store to display - iadd rg0, 1, rg0 ; Advance string pointer - iadd rg1, 1, rg1 ; Advance cursor + addi rg0, 1, rg0 ; Advance string pointer + addi rg1, 1, rg1 ; Advance cursor cmp acc, zero ; Check for null terminator - jne print_loop ; Continue if not null + jne print_loop, zero ; Continue if not null - stw rg1, current ; Save cursor position + stw rg1, current ; Save cursor position (clobbers acc!) mov bpr, spr pop bpr @@ -662,8 +839,8 @@ reset: push bpr mov spr, bpr - ldw display, rg1 ; Load display base - stw rg1, current ; Reset cursor to start + ldw display, rg1 ; Load display base (clobbers acc!) + stw rg1, current ; Reset cursor to start (clobbers acc!) mov bpr, spr pop bpr @@ -683,17 +860,17 @@ db string: "'To confuse your enemy, you must first confuse yourself' - Probably init: // Set up stack - ldw stack, bpr + ldw stack, bpr ; Load stack base (clobbers acc!) mov bpr, spr start: // Load string address - lwi string, rg1 + lwi string, rg1 ; Load address of string // Call print function push rg1 call print::print - pop rg1 ; Clean up (rg1 now contains arg we passed) + pop rg1 ; Clean up (or pop zero to discard) hlt ``` @@ -706,27 +883,24 @@ start: dw value: 42 main: - ldw value, rg0 + ldw value, rg0 ; Load value (clobbers acc!) cmp rg0, zero - jeq is_zero - jgt is_positive - jlt is_negative + jeq is_zero, zero + jgt is_positive, zero + jlt is_negative, zero is_zero: - // Handle zero case lwi zero_msg, rg1 - jmp print_and_exit + jmp print_and_exit, zero is_positive: - // Handle positive case lwi positive_msg, rg1 - jmp print_and_exit + jmp print_and_exit, zero is_negative: - // Handle negative case lwi negative_msg, rg1 - jmp print_and_exit + jmp print_and_exit, zero print_and_exit: push rg1 @@ -747,21 +921,21 @@ db negative_msg: "Value is negative", 0 dw stack: 0x10000 main: - ldw stack, bpr + ldw stack, bpr ; Set up stack mov bpr, spr lli 0, rg0 ; Counter = 0 lli 10, rg1 ; Limit = 10 loop: - // Do something with counter in rg0 + // Process counter value push rg0 call process_value pop zero inc rg0 ; Counter++ cmp rg0, rg1 ; Compare with limit - jlt loop ; Loop if counter < limit + jlt loop, zero ; Loop if counter < limit hlt @@ -780,61 +954,73 @@ process_value: ## Best Practices ### 1. Stack Management -- Always balance push/pop operations -- Set up stack frame in every function -- Clean up arguments after function calls +- **Always balance push/pop operations** in the same function +- Set up stack frame (`push bpr; mov spr, bpr`) in every function +- Clean up arguments after function calls (caller's responsibility) - Use `pop zero` to discard unwanted values +- Remember: stack grows DOWNWARD ### 2. Register Usage -- Don't rely on `acc` being preserved -- Don't use `rgf` for variables (used by label addressing) -- Save callee-saved registers if you modify them +- **Never rely on `acc` being preserved** across ANY operation +- Treat `acc` as write-only temporary storage +- Save caller-saved registers (rg0-rg3) before calls if needed +- Restore callee-saved registers (rg4-rgf) if modified - Use `zero` register for zero constants ### 3. Memory Access - Ensure proper alignment for halfword/word access -- Use label-based addressing for clearer code +- Be aware that label-based loads/stores clobber `acc` +- Prefer register-based addressing when `acc` value matters - Check that labels are defined before use ### 4. Function Design -- Document calling convention in comments +- **Document calling convention** in function comments +- Always include prologue and epilogue - Validate input arguments when appropriate -- Use consistent parameter order -- Return values via stack or designated register +- Use consistent parameter order across related functions +- Return values via stack (overwrite arg0) or designated register ### 5. Code Organization -- Use meaningful label names +- Use meaningful label names (e.g., `loop_start` not `l1`) - Comment complex operations - Group related functions in modules - Use includes for code reuse +- Keep functions focused and small -### 6. Performance -- Minimize memory accesses (use registers) +### 6. Performance Considerations +- Minimize memory accesses (use registers when possible) - Avoid unnecessary comparisons -- Use shifts for multiplication/division by powers of 2 -- Consider instruction pipelining if supported +- Use shifts for multiplication/division by powers of 2: + ```asm + shl rg0, 3 ; Multiply by 8 (2^3) + shr rg0, 2 ; Divide by 4 (2^2) + ``` +- Consider instruction pipelining if CPU supports it -## Assembler Directives +### 7. ACC Register Awareness +**⚠️ CRITICAL - Common Mistakes:** -### Alignment (if supported) ```asm -.align 4 ; Align to 4-byte boundary -.align 2 ; Align to 2-byte boundary -``` +; WRONG - acc gets clobbered: +lli 100, acc +ldw data, rg0 ; acc is now GARBAGE! +add acc, rg0, rg1 ; Using garbage value! -### Origin (if supported) -```asm -.org 0x1000 ; Set location counter to 0x1000 -``` +; RIGHT - use a different register: +lli 100, rg2 +ldw data, rg0 ; acc clobbered (don't care) +add rg2, rg0, rg1 ; rg2 still has 100 -### Section Control (if supported) -```asm -.text ; Code section -.data ; Data section -.bss ; Uninitialized data section -``` +; WRONG - assuming acc preserved across call: +lli 42, acc +call some_function +add acc, rg0, rg1 ; acc probably destroyed! -**Note:** Assembler directive support depends on the specific DSA assembler implementation. +; RIGHT - use caller-saved register: +lli 42, rg0 +call some_function +add rg0, rg1, rg2 ; rg0 might be destroyed, so save it first! +``` ## Common Patterns @@ -842,82 +1028,139 @@ process_value: ```asm lli lower_16_bits, reg lui upper_16_bits, reg + +; Example: +lli 0x1234, rg0 ; rg0 = 0x00001234 +lui 0xABCD, rg0 ; rg0 = 0xABCD1234 ``` ### Zero a Register ```asm -mov zero, reg ; Method 1 -xor reg, reg, reg ; Method 2 -lli 0, reg ; Method 3 +mov zero, reg ; Method 1 (preferred - clearest) +xor reg, reg, reg ; Method 2 (clever but less clear) +lli 0, reg ; Method 3 (works but wastes upper bits clear) ``` ### Copy Memory ```asm -ldw src_addr, rg0 ; Load from source -stw rg0, dest_addr ; Store to destination +ldw src_addr, rg0 ; Load from source (clobbers acc) +stw rg0, dest_addr, 0 ; Store to destination (clobbers acc) + +; If you need acc preserved: +lli 0x1234, acc ; Some value in acc +push acc ; Save it +ldw src_addr, rg0 ; acc clobbered +stw rg0, dest_addr, 0 ; acc clobbered again +pop acc ; Restore acc ``` -### Multiply by Power of 2 +### Multiply/Divide by Power of 2 ```asm -shl reg, 3 ; Multiply by 8 (2^3) -``` - -### Divide by Power of 2 -```asm -shr reg, 2 ; Divide by 4 (2^2) +shl reg, 3 ; Multiply by 8 (2^3) +shr reg, 2 ; Divide by 4 (2^2) ``` ### Boolean NOT ```asm cmp reg, zero -jeq was_zero ; If reg == 0, result is 1 +jeq was_zero, zero ; If reg == 0, result is 1 lli 0, reg -jmp done +jmp done, zero was_zero: -lli 1, reg + lli 1, reg done: ``` ### Min/Max ```asm ; max(rg0, rg1) -> rg2 -mov rg0, rg2 ; Assume rg0 is max +mov rg0, rg2 ; Assume rg0 is max cmp rg0, rg1 -jge done -mov rg1, rg2 ; rg1 was larger +jge done, zero ; If rg0 >= rg1, we're done +mov rg1, rg2 ; rg1 was larger done: ``` +### Array Indexing +```asm +; Access array[i] where array is 32-bit words +; rg0 = array base address +; rg1 = index i +; Result in rg2 + +shl rg1, 2 ; Convert index to byte offset (i * 4) +add rg0, rg1, acc ; acc = base + offset (use acc for temp) +ldw acc, rg2, 0 ; Load array[i] +``` + ## Troubleshooting ### Common Errors **Alignment Fault:** -- Check that halfword loads/stores use even addresses -- Check that word loads/stores use addresses divisible by 4 +- Symptom: Exception when loading/storing halfword or word +- Cause: Address not properly aligned +- Fix: Ensure halfword addresses are even, word addresses divisible by 4 **Illegal Instruction:** -- Verify opcode is valid -- Check that shift amount is 0 for non-shift instructions -- Ensure you're not using `noreg` as a source/destination +- Symptom: Unexpected halt or exception +- Cause: Invalid opcode or malformed instruction +- Fix: Check assembly syntax, verify instruction encoding **Stack Corruption:** -- Verify push/pop balance -- Check that functions restore `bpr` before returning -- Ensure caller cleans up arguments +- Symptom: Function returns to wrong address, registers have wrong values +- Cause: Unbalanced push/pop, incorrect stack frame management +- Fix: + - Verify push/pop balance + - Check function epilogue restores bpr + - Ensure caller cleans up arguments + - Verify stack grows downward (push decrements) -**Wrong Results:** -- Verify `lli` is called before `lui` when loading constants -- Check that you're not relying on `acc` or `rgf` being preserved -- Verify signed vs. unsigned loads (ldb vs. ldbs) +**Wrong Results / Unexpected Values:** +- Symptom: Calculations produce incorrect results +- Cause: ACC clobbering, wrong register assumptions +- Fix: + - Check if acc was assumed preserved (it never is!) + - Verify lli called before lui for 32-bit constants + - Check signed vs unsigned loads (ldb vs ldbs) + - Verify register preservation across calls + +**Label Not Found:** +- Symptom: Assembler error about undefined label +- Cause: Label used before definition, typo in label name +- Fix: Define labels before use, check spelling ### Debugging Tips -1. Add `nop` instructions as breakpoint markers -2. Print register values using display memory -3. Use single-step execution to trace program flow -4. Verify stack pointer values at function boundaries -5. Check label addresses in disassembly +1. **Add NOP markers** at key points for breakpoints: + ```asm + nop ; Breakpoint here + ``` + +2. **Print register values** using display memory: + ```asm + stw rg0, debug_out ; Store to known location + ``` + +3. **Use single-step execution** to trace program flow + +4. **Verify stack pointer** values at function boundaries: + ```asm + ; After prologue, check: + ; spr == bpr (should point to same location) + ; After epilogue, check: + ; spr restored to entry value + ``` + +5. **Check label addresses** in disassembly output + +6. **Trace ACC usage**: + ```asm + lli 0xDEAD, acc ; Marker value + ; ... your code ... + cmp acc, zero + jeq acc_was_clobbered, zero ; If acc==0, it was overwritten + ``` ## Appendix: Instruction Quick Reference @@ -933,12 +1176,24 @@ done: | **Logical** | and, or, xor, not, nand, nor, xnor | | **Shift** | shl, shr | | **System** | hlt, nop, int, irt | -| **Pseudo** | db, dh, dw, resb, resh, resw, push, pop, lwi, call, return, include | +| **Pseudo - Data** | db, dh, dw, resb, resh, resw | +| **Pseudo - Stack** | push, pop, pusha, popa | +| **Pseudo - Memory** | lwi, ldb/ldh/ldw with labels, stb/sth/stw with labels | +| **Pseudo - Control** | call, return | +| **Pseudo - Module** | include | ## Version History -- **v1.0** - Initial comprehensive reference - - Combined hardware instructions and pseudo-instructions - - Added complete calling convention - - Included practical examples - - Documented common patterns and best practices +- **v2.0** - Corrected comprehensive reference based on implementation + - **CORRECTED:** Stack grows downward (not upward) + - **CORRECTED:** CALL/RETURN use stack for return addresses + - **CORRECTED:** Pseudo-instructions use ACC as scratch (not rgf) + - **CORRECTED:** Label loads use ACC for address calculation + - **CORRECTED:** IADD/ISUB require destination (not optional) + - **ADDED:** PUSHA/POPA documentation + - **ADDED:** Detailed ACC volatility warnings throughout + - **ADDED:** Stack evolution diagrams + - **CLARIFIED:** Big-endian data directive encoding + - **CLARIFIED:** Jump instruction semantics + - All examples updated to reflect correct stack direction + - Added extensive troubleshooting for ACC-related issues diff --git a/docs/DSA_ISA_Specification.md b/docs/DSA_ISA_Specification.md index c2973ee..fdb709d 100644 --- a/docs/DSA_ISA_Specification.md +++ b/docs/DSA_ISA_Specification.md @@ -12,7 +12,9 @@ The Damn Simple Architecture (DSA) is a 32-bit RISC-style architecture designed | Halfword | 16 bits | 2-byte aligned | | Word | 32 bits | 4-byte aligned | -All multi-byte values use little-endian byte order. +**Note on Endianness:** +- Instructions and numeric data in memory: Little-endian +- Data defined via `db/dh/dw` directives: Big-endian (assembler-specific) ## Registers @@ -23,33 +25,32 @@ DSA provides 32 programmer-accessible registers plus several internal system reg | Hex | Register | Type | Description | |-----|----------|------|-------------| | 0x00-0x0F | **rg0-rgf** | General Purpose | 16 general-purpose registers for variables and temporary values | -| 0x10 | **acc** | Special | Accumulator for calculations and temporary storage
⚠️ May be overwritten by pseudo-instructions | +| 0x10 | **acc** | Special | Accumulator for calculations and temporary storage
⚠️ Used as scratch by pseudo-instructions - volatile | | 0x11 | **spr** | Special | Stack pointer - points to top of stack | | 0x12 | **bpr** | Special | Base pointer - used for stack frame management | -| 0x13 | **ret** | Special | Return address register - stores function return addresses | +| 0x13 | **ret** | Special | Return address register - used for function returns | | 0x14 | **idr** | Privileged | Interrupt descriptor table address
Read/write triggers protection fault in user mode | | 0x15 | **mmr** | Privileged | Hardware memory map table address
Read/write triggers protection fault in user mode | | 0x16 | **zero** | Read-only | Constant zero value
Reads always return 0, writes are discarded | -| 0x17 | **noreg** | Placeholder | Indicates unused register field
Read/write triggers illegal instruction fault | +| 0x17 | **noreg** | Placeholder | Indicates unused register field
Read/write triggers illegal instruction fault
Can also be referenced as **null** | | 0x18-0x1F | - | Reserved | Reserved for future use | +**System Registers (indices 0x18-0x1C):** +These exist in the encoding space but are internal to the CPU implementation: + +| Hex | Register | Description | +|-----|----------|-------------| +| 0x18 | **mar** | Memory Address Register (CPU internal) | +| 0x19 | **mdr** | Memory Data Register (CPU internal) | +| 0x1A | **sts** | Status Register (CPU internal) | +| 0x1B | **cir** | Current Instruction Register (CPU internal) | +| 0x1C | **pcx** | Program Counter (read-only, special access) | + **Note on PCX (Program Counter):** -- PCX is a read-only system register that can be accessed in some contexts +- PCX can be read in certain contexts (e.g., stored during CALL) - Writing to PCX triggers a protection fault - PCX is automatically updated by jump and branch instructions -### System Registers (Internal) - -These registers are used internally by the CPU and are not directly accessible via assembly instructions: - -| Register | Description | -|----------|-------------| -| **MAR** | Memory Address Register - holds address for memory operations | -| **MDR** | Memory Data Register - holds data for memory transfers | -| **CIR** | Current Instruction Register - holds instruction being executed | -| **STS** | Status Register - stores comparison and arithmetic flags | -| **PCX** | Program Counter - stores address of next instruction | - ### Status Register (STS) Layout The status register is a 32-bit register with the following flag bits: @@ -108,7 +109,7 @@ Used for operations with a 16-bit immediate value. **Usage:** - Arithmetic: Immediate is a signed value - Memory access: Immediate is a signed byte offset from base address -- Branches: Immediate is a signed offset from current PCX +- Branches: Immediate is a signed offset added to base register - Literal loads: Immediate is unsigned 16-bit value ### J-Type (Jump) Instructions @@ -131,7 +132,7 @@ Used for absolute jumps with large address ranges. **Jump Range:** 256MB region around current PC (±128MB) -**Note:** J-type instructions are defined but currently unused. Use I-type JMP with register addressing for long jumps. +**Note:** J-type instructions are defined but currently unused. Use I-type JMP with register addressing for all jumps. ## Hardware Instructions @@ -197,8 +198,8 @@ In machine code: SrcReg (SrcReg field), BaseReg (DestReg field), Offset (Immedia | Hex | Mnemonic | Type | Operands | Description | |-----|----------|------|----------|-------------| -| 0x0B | **LLI** | I | DestReg, Value | Load 16-bit value into lower 16 bits
⚠️ **CLEARS upper 16 bits!** | -| 0x0C | **LUI** | I | DestReg, Value | Load 16-bit value into upper 16 bits
Lower 16 bits unchanged | +| 0x0B | **LLI** | I | Value, DestReg | Load 16-bit value into lower 16 bits
⚠️ **CLEARS upper 16 bits!** | +| 0x0C | **LUI** | I | Value, DestReg | Load 16-bit value into upper 16 bits
Lower 16 bits unchanged | **Usage for 32-bit Values:** ``` @@ -208,29 +209,38 @@ LUI 0xABCD, rg0 ; rg0 = 0xABCD1234 **⚠️ CRITICAL:** Always execute LLI before LUI, as LLI clears the upper 16 bits! +**Note on LUI:** The assembler may shift the immediate value right by 16 bits when encoding, so specify the upper 16 bits directly (e.g., `LUI 0xABCD, rg0` not `LUI 0xABCD0000, rg0`). + **Encoding Note:** -In machine code: Value (Immediate field), DestReg field (SrcReg unused, set to noreg) +In machine code: Value (Immediate field), DestReg (SrcReg field for LLI, SrcReg field for LUI) ### Jump and Branch Instructions | Hex | Mnemonic | Type | Operands | Description | |-----|----------|------|----------|-------------| -| 0x0D | **JMP** | I | DestReg, Offset | Unconditional jump to (DestReg + Offset) | -| 0x0E | **JEQ** | I | DestReg, Offset | Jump if Equal flag set | -| 0x0F | **JNE** | I | DestReg, Offset | Jump if Equal flag NOT set | -| 0x10 | **JGT** | I | DestReg, Offset | Jump if GreaterThan flag set | -| 0x11 | **JGE** | I | DestReg, Offset | Jump if GreaterThan OR Equal flag set | -| 0x12 | **JLT** | I | DestReg, Offset | Jump if LessThan flag set | -| 0x13 | **JLE** | I | DestReg, Offset | Jump if LessThan OR Equal flag set | +| 0x0D | **JMP** | I | Offset, BaseReg | Unconditional jump to (BaseReg + Offset) | +| 0x0E | **JEQ** | I | Offset, BaseReg | Jump if Equal flag set | +| 0x0F | **JNE** | I | Offset, BaseReg | Jump if Equal flag NOT set | +| 0x10 | **JGT** | I | Offset, BaseReg | Jump if GreaterThan flag set | +| 0x11 | **JGE** | I | Offset, BaseReg | Jump if GreaterThan OR Equal flag set | +| 0x12 | **JLT** | I | Offset, BaseReg | Jump if LessThan flag set | +| 0x13 | **JLE** | I | Offset, BaseReg | Jump if LessThan OR Equal flag set | **Jump Calculation:** -- Target address = DestReg + SignExtend(Offset) -- If DestReg = zero, this becomes absolute addressing with Offset -- If DestReg = pcx, this becomes PC-relative addressing +- Target address = BaseReg + SignExtend(Offset) +- If BaseReg = zero, this becomes absolute addressing with Offset +- If BaseReg = ret, this becomes return-style addressing - Conditional jumps check flags in STS register +**Common Patterns:** +``` +JMP label, zero ; Absolute jump to label address +JMP 0, ret ; Jump to address in ret register +JMP 4, ret ; Jump to (ret + 4) +``` + **Encoding Note:** -In machine code: DestReg field, Offset (Immediate field) (SrcReg unused, set to noreg) +In machine code: Offset (Immediate field), BaseReg (SrcReg field) (DestReg unused, set to noreg) ### Comparison @@ -265,8 +275,8 @@ DestReg and ShiftAmt fields unused (set to noreg and 0) - Other flags undefined after arithmetic (use CMP for comparisons) **Encoding Notes:** -- INC/DEC: Reg in SrcReg1 field, also copied to DestReg field -- IADD/ISUB: Immediate is signed 16-bit value +- INC/DEC: Reg in SrcReg1 field, DestReg set to noreg +- IADD/ISUB: Immediate is signed 16-bit value, all three operands required ### Bitwise Logical Operations @@ -285,7 +295,7 @@ DestReg and ShiftAmt fields unused (set to noreg and 0) - Other flags undefined **Encoding Note:** -NOT uses only Src and Dest; SrcReg2 unused (set to noreg) +NOT uses only Src (SrcReg1) and Dest (DestReg); SrcReg2 unused (set to noreg) ### Shift Operations @@ -295,17 +305,22 @@ NOT uses only Src and Dest; SrcReg2 unused (set to noreg) | 0x18 | **SHR** | R | Reg, ShiftAmount | Shift Reg right by ShiftAmount bits
Zero-fill from left (logical shift) | **Shift Amount:** -- Can be a 5-bit literal (0-31) in ShiftAmt field -- Can be a register value (low 5 bits used) - - If using register: Place in SrcReg2, set ShiftAmt to 0 - - If using literal: Place in ShiftAmt field, set SrcReg2 to noreg +- **Literal shifts**: ShiftAmount is a 5-bit literal (0-31) in assembly + - Stored in ShiftAmt field of instruction + - SrcReg2 set to noreg +- **Register shifts**: ShiftAmount is a register containing shift value + - Register specified in SrcReg2 field + - ShiftAmt field must be 0 + - Only low 5 bits of register value used + +**Note:** Current assembler implementation may only support literal shifts. Check assembler documentation. **Flag Effects:** - Zero flag set if result is zero **Encoding Notes:** -- Reg in both SrcReg1 and DestReg fields -- For literal shifts: ShiftAmt field contains shift count +- Reg in both SrcReg1 and DestReg fields (shifted in place) +- For literal shifts: ShiftAmt field contains shift count, SrcReg2 = noreg - For register shifts: SrcReg2 contains register, ShiftAmt must be 0 ### System and Control Instructions @@ -331,6 +346,17 @@ NOT uses only Src and Dest; SrcReg2 unused (set to noreg) - INT: InterruptCode in low 8 bits of Immediate field - IRT/HLT: All register fields set to noreg, ShiftAmt to 0 +### Meta Instructions (Assembler/Linker) + +These instructions are used by the assembler and linker but may not represent real CPU operations. + +| Hex | Mnemonic | Description | +|-----|----------|-------------| +| 0x27 | **SEGMENT** | Segment marker (implementation-specific) | +| 0x3E | **DATA** | Raw data embedding | + +**Note:** The SEGMENT instruction opcode may vary between implementations (0x27 in assembler, 0x3F in some contexts). Consult your specific toolchain documentation. + ## Instruction Summary Table | Opcode | Mnemonic | Type | Category | @@ -374,6 +400,8 @@ NOT uses only Src and Dest; SrcReg2 unused (set to noreg) | 0x24 | HLT | R | System | | 0x25 | IADD | I | Arithmetic | | 0x26 | ISUB | I | Arithmetic | +| 0x27 | SEGMENT | - | Meta | +| 0x3E | DATA | - | Meta | ## Exception Conditions @@ -393,9 +421,9 @@ See the DSA Assembly Language Reference for the complete calling convention and ## Notes on Design 1. **Word Size:** All addresses and general computation is 32-bit -2. **Endianness:** Little-endian byte order -3. **Stack Growth:** Stack grows upward (incrementing addresses) +2. **Endianness:** Little-endian for instructions and runtime data; assembler data directives may use big-endian +3. **Stack Growth:** Stack grows **downward** (toward lower addresses) - PUSH decrements SPR 4. **Alignment:** Natural alignment required for halfword and word accesses 5. **Sign Extension:** All immediate values are sign-extended unless noted 6. **Zero Register:** Provides constant zero, writes are legal but discarded -7. **Reserved Encodings:** Opcodes 0x27-0x3F reserved for future use +7. **Reserved Encodings:** Opcodes 0x27-0x3D and 0x3F reserved or implementation-specific diff --git a/docs/DSA_Project_Roadmap.md b/docs/DSA_Project_Roadmap.md index 9ad612a..19ecaf7 100644 --- a/docs/DSA_Project_Roadmap.md +++ b/docs/DSA_Project_Roadmap.md @@ -263,12 +263,12 @@ - [ ] Array syntax - [ ] Struct syntax - [x] Pointer syntax - - [ ] Namespaced call syntax + - [x] Namespaced call syntax - [x] AST node definitions - [ ] Error recovery mechanisms - [ ] Comprehensive parser tests - [ ] Syntax error message quality testing -- [ ] Implement C frontend by moving lexer/parser from `c_compiler` to the new `compiler` project structure +- [x] Implement C frontend by moving lexer/parser from `c_compiler` to the new `compiler` project structure - [ ] Evaluate possible memory management strategies (e.g., keep all variables on the stack vs spill only when calling functions) --- @@ -290,7 +290,7 @@ - [ ] Optimize register allocation further - [x] Implement proper function calling conventions - [ ] Add constant folding optimization -- [ ] Dead code elimination +- [x] Dead code elimination - [ ] Test each feature thoroughly --- @@ -376,7 +376,7 @@ **Dependencies:** None **Deliverable:** `docs/build-system-design.md` -- [ ] Define project structure conventions +- [x] Define project structure conventions - [ ] Design build manifest format (`dsa-project.toml` or similar) - [ ] Dependency resolution strategy - [ ] Build cache design @@ -391,12 +391,12 @@ **Dependencies:** 3.1.1, 1.2.2, 1.1.3, 2.1.3 **Deliverable:** `dsa-build` executable -- [ ] Create crate: `dsa-build` +- [x] Create crate: `dsa-build` - [ ] Manifest parser - [ ] Dependency graph builder - [ ] Task orchestrator - - [ ] Compilation tasks - - [ ] Assembly tasks + - [x] Compilation tasks + - [x] Assembly tasks - [ ] Linking tasks - [ ] Build cache implementation - [ ] Parallel build support @@ -412,11 +412,11 @@ **Dependencies:** 3.1.2 **Deliverable:** Enhanced `dsa-build` with project management -- [ ] `dsa new ` — Create new project -- [ ] `dsa init` — Initialize in existing directory +- [x] `dsa new ` — Create new project +- [x] `dsa init` — Initialize in existing directory - [ ] `dsa add ` — Add dependency - [ ] Binary vs library project types -- [ ] Template system for project scaffolding +- [x] Template system for project scaffolding - [ ] Documentation for each command --- -- 2.47.3 From 6699333b2c7bd8b002d56a6a197bd1512080245d Mon Sep 17 00:00:00 2001 From: zxq5 Date: Sun, 8 Feb 2026 00:14:18 +0000 Subject: [PATCH 36/53] - C frontend broken for now - If statements work properly now (hopefully) - still issues with while loops pushing vars to the stack. need scoping implemented to fix this! - refactored registers.rs and fixed faulty logic. - made register allocation optimisations --- compiler/src/backend/dsa/codegen.rs | 145 +++--- compiler/src/backend/dsa/registers.rs | 463 ++++++++++++------ compiler/src/frontend/c/parser.rs | 15 +- compiler/src/frontend/dsc/mod.rs | 8 +- compiler/src/frontend/dsc/parser.rs | 31 +- .../src/frontend/dsc/semantic_analyser.rs | 221 ++++++++- compiler/src/frontend/mod.rs | 4 +- compiler/src/lib.rs | 2 + compiler/src/model.rs | 88 +++- 9 files changed, 745 insertions(+), 232 deletions(-) diff --git a/compiler/src/backend/dsa/codegen.rs b/compiler/src/backend/dsa/codegen.rs index e6a4dca..680f9ad 100644 --- a/compiler/src/backend/dsa/codegen.rs +++ b/compiler/src/backend/dsa/codegen.rs @@ -5,11 +5,12 @@ use std::time::SystemTime; use chrono::{DateTime, Local}; use super::registers::RegisterAllocator; +use crate::backend::dsa::registers::Register; use crate::{block, comment, dsa}; use crate::model::{ - BinaryOperator, CompilerError, ConstExpr, Declaration, Dependency, Expression, - Program, Statement, UnaryOperator, Variable, + BinaryOperator, Call, CompilerError, ConstExpr, Declaration, Dependency, Expression, + Program, Statement, TypeId, UnaryOperator, Variable, }; pub struct CodeGenerator { @@ -149,9 +150,14 @@ impl CodeGenerator { self.generate_global(&var.name, init) } Declaration::Function { - name, params, body, .. + name, + params, + body, + return_type, } => { - let func = self.generate_function(&name, ¶ms, &body).join("\n"); + let func = self + .generate_function(&name, ¶ms, &body, return_type) + .join("\n"); self.functions.push(format!("{func}\n")); } @@ -169,12 +175,23 @@ impl CodeGenerator { name: &str, params: &[Variable], body: &[Statement], + return_type: TypeId, ) -> Vec { let mut code = Vec::new(); // Reset allocator for new function self.allocator.reset(); + code.push(format!( + "// fn {name}({}) -> {}", + params + .iter() + .map(|p| format!("{}: {}", p.name, p.type_id)) + .collect::>() + .join(", "), + return_type + )); + // Function prologue code.push(format!("{}:", name)); code.push("\tpush bpr".to_string()); @@ -192,7 +209,7 @@ impl CodeGenerator { // Generate code for function body for stmt in body { - let stmt_code = self.generate_statement(stmt).unwrap(); + let stmt_code = self.generate_statement(stmt, &mut code).unwrap(); code.extend(stmt_code); } @@ -211,6 +228,7 @@ impl CodeGenerator { fn generate_statement( &mut self, stmt: &Statement, + func_body: &mut Vec, ) -> Result, CompilerError> { let mut code = Vec::new(); @@ -218,7 +236,8 @@ impl CodeGenerator { Statement::Declaration { var, value } => { if let Some(expr) = value { // Evaluate expression - let (result_reg, expr_code) = self.generate_expression(expr, true)?; + let (result_reg, expr_code) = + self.generate_expression(expr, true, func_body)?; code.extend(expr_code); // Store result in variable @@ -233,14 +252,17 @@ impl CodeGenerator { } } - Statement::Break => unimplemented!(), - Statement::Continue => unimplemented!(), + Statement::Break => unimplemented!("need scope tracking first!"), + Statement::Continue => unimplemented!("need scope tracking first!"), + Statement::Defer(_func) => unimplemented!("we need scope tracking first!"), Statement::PtrWrite { ptr, value } => { - let (result_reg, expr_code) = self.generate_expression(value, true)?; + let (result_reg, expr_code) = + self.generate_expression(value, true, func_body)?; code.extend(expr_code); - let (ptr_reg, ptr_code) = self.generate_expression(ptr, true)?; + let (ptr_reg, ptr_code) = + self.generate_expression(ptr, true, func_body)?; code.extend(ptr_code); code.push(format!("\tstw {}, {}", result_reg, ptr_reg)); @@ -251,7 +273,8 @@ impl CodeGenerator { Statement::Assign { varname, value } => { // Evaluate expression - let (result_reg, expr_code) = self.generate_expression(value, true)?; + let (result_reg, expr_code) = + self.generate_expression(value, true, func_body)?; code.extend(expr_code); // Check if this is a global variable @@ -270,7 +293,8 @@ impl CodeGenerator { Statement::Return(expr) => { if let Some(e) = expr { - let (result_reg, expr_code) = self.generate_expression(e, true)?; + let (result_reg, expr_code) = + self.generate_expression(e, true, func_body)?; code.extend(expr_code); code.push(format!("\tstw {}, bpr, 8", result_reg)); code.push(format!("\tjmp _ret")); @@ -284,7 +308,8 @@ impl CodeGenerator { else_stmt, } => { // Generate condition - let (cond_reg, cond_code) = self.generate_expression(condition, true)?; + let (cond_reg, cond_code) = + self.generate_expression(condition, true, func_body)?; code.extend(cond_code); // Compare with zero @@ -302,7 +327,7 @@ impl CodeGenerator { // Then block code.push(format!("{}:", then_label)); for s in then_stmt { - code.extend(self.generate_statement(s)?); + code.extend(self.generate_statement(s, func_body)?); } if then_stmt.len() == 0 { @@ -314,7 +339,7 @@ impl CodeGenerator { // Else block code.push(format!("{}:", else_label)); for s in else_stmt { - code.extend(self.generate_statement(s)?); + code.extend(self.generate_statement(s, func_body)?); } if else_stmt.len() == 0 { @@ -331,7 +356,8 @@ impl CodeGenerator { code.push(format!("{}:", loop_start)); // Generate condition - let (cond_reg, cond_code) = self.generate_expression(condition, true)?; + let (cond_reg, cond_code) = + self.generate_expression(condition, true, func_body)?; code.extend(cond_code); code.push(format!("\tcmp {}, zero", cond_reg)); @@ -341,7 +367,7 @@ impl CodeGenerator { // Loop body for s in body { - code.extend(self.generate_statement(s)?); + code.extend(self.generate_statement(s, func_body)?); } code.push(format!("\tjmp {}", loop_start)); @@ -354,21 +380,22 @@ impl CodeGenerator { code.push(format!("{}:", loop_start)); for s in body { - code.extend(self.generate_statement(s)?); + code.extend(self.generate_statement(s, func_body)?); } code.push(format!("\tjmp {}", loop_start)); } Statement::Expression { expr } => { - let (result_reg, expr_code) = self.generate_expression(expr, false)?; + let (result_reg, expr_code) = + self.generate_expression(expr, false, func_body)?; code.extend(expr_code); self.allocator.free_temp(&result_reg); } Statement::Block(statements) => { for s in statements { - code.extend(self.generate_statement(s)?); + code.extend(self.generate_statement(s, func_body)?); } } } @@ -382,14 +409,10 @@ impl CodeGenerator { &mut self, expr: &Expression, use_result: bool, - ) -> Result<(String, Vec), CompilerError> { + func_body: &mut Vec, + ) -> Result<(Register, Vec), CompilerError> { let mut code = Vec::new(); - // optimisation to prevent generating dead code! - if expr.is_pure() && !use_result { - return Ok((String::new(), code)); - } - match expr { Expression::StringLiteral(value) => { let (reg, alloc_code) = self.allocator.alloc_temp()?; @@ -397,7 +420,7 @@ impl CodeGenerator { // write string into memory let uuid = self.get_unique_label(); - code.push(format!("\tdb str_{uuid}: \"{value}\"")); + func_body.insert(0, format!("db str_{uuid}: \"{value}\"")); // Load pointer to string code.push(format!("\tlwi str_{uuid}, {reg}")); @@ -415,7 +438,7 @@ impl CodeGenerator { Ok((reg, code)) } - Expression::Number(value) => { + Expression::Number { value, .. } => { let (reg, alloc_code) = self.allocator.alloc_temp()?; code.extend(alloc_code); @@ -446,13 +469,17 @@ impl CodeGenerator { } } - Expression::Binary { op, left, right } => { + Expression::Binary { + op, left, right, .. + } => { // Evaluate left operand - let (left_reg, left_code) = self.generate_expression(left, true)?; + let (left_reg, left_code) = + self.generate_expression(left, true, func_body)?; code.extend(left_code); // Evaluate right operand - let (right_reg, right_code) = self.generate_expression(right, true)?; + let (right_reg, right_code) = + self.generate_expression(right, true, func_body)?; code.extend(right_code); // Allocate result register @@ -485,50 +512,50 @@ impl CodeGenerator { // Comparison operators - return 1 (true) or 0 (false) BinaryOperator::Eq => { code.push(format!("\tcmp {}, {}", left_reg, right_reg)); - code.push(format!("\tlli 0, {}", result_reg)); - let end_label = format!("_cmp_end_{}", self.get_unique_label()); - code.push(format!("\tjne {}", end_label)); // If not equal, skip setting to 1 code.push(format!("\tlli 1, {}", result_reg)); + let end_label = format!("_cmp_end_{}", self.get_unique_label()); + code.push(format!("\tjeq {}", end_label)); + code.push(format!("\tlli 0, {}", result_reg)); code.push(format!("{}:", end_label)); } BinaryOperator::Ne => { code.push(format!("\tcmp {}, {}", left_reg, right_reg)); - code.push(format!("\tlli 0, {}", result_reg)); - let end_label = format!("_cmp_end_{}", self.get_unique_label()); - code.push(format!("\tjeq {}", end_label)); // If equal, skip setting to 1 code.push(format!("\tlli 1, {}", result_reg)); + let end_label = format!("_cmp_end_{}", self.get_unique_label()); + code.push(format!("\tjne {}", end_label)); + code.push(format!("\tlli 0, {}", result_reg)); code.push(format!("{}:", end_label)); } BinaryOperator::Lt => { code.push(format!("\tcmp {}, {}", left_reg, right_reg)); - code.push(format!("\tlli 0, {}", result_reg)); - let end_label = format!("_cmp_end_{}", self.get_unique_label()); - code.push(format!("\tjge {}", end_label)); // If greater or equal, skip setting to 1 code.push(format!("\tlli 1, {}", result_reg)); + let end_label = format!("_cmp_end_{}", self.get_unique_label()); + code.push(format!("\tjlt {}", end_label)); + code.push(format!("\tlli 0, {}", result_reg)); code.push(format!("{}:", end_label)); } BinaryOperator::Le => { code.push(format!("\tcmp {}, {}", left_reg, right_reg)); - code.push(format!("\tlli 0, {}", result_reg)); - let end_label = format!("_cmp_end_{}", self.get_unique_label()); - code.push(format!("\tjgt {}", end_label)); // If greater than, skip setting to 1 code.push(format!("\tlli 1, {}", result_reg)); + let end_label = format!("_cmp_end_{}", self.get_unique_label()); + code.push(format!("\tjle {}", end_label)); + code.push(format!("\tlli 0, {}", result_reg)); code.push(format!("{}:", end_label)); } BinaryOperator::Gt => { code.push(format!("\tcmp {}, {}", left_reg, right_reg)); - code.push(format!("\tlli 0, {}", result_reg)); - let end_label = format!("_cmp_end_{}", self.get_unique_label()); - code.push(format!("\tjle {}", end_label)); // If less or equal, skip setting to 1 code.push(format!("\tlli 1, {}", result_reg)); + let end_label = format!("_cmp_end_{}", self.get_unique_label()); + code.push(format!("\tjgt {}", end_label)); + code.push(format!("\tlli 0, {}", result_reg)); code.push(format!("{}:", end_label)); } BinaryOperator::Ge => { code.push(format!("\tcmp {}, {}", left_reg, right_reg)); - code.push(format!("\tlli 0, {}", result_reg)); - let end_label = format!("_cmp_end_{}", self.get_unique_label()); - code.push(format!("\tjlt {}", end_label)); // If less than, skip setting to 1 code.push(format!("\tlli 1, {}", result_reg)); + let end_label = format!("_cmp_end_{}", self.get_unique_label()); + code.push(format!("\tjge {}", end_label)); + code.push(format!("\tlli 8, {}", result_reg)); code.push(format!("{}:", end_label)); } _ => unimplemented!(), @@ -541,11 +568,15 @@ impl CodeGenerator { Ok((result_reg, code)) } - Expression::Call { name, args } => { + Expression::Call { + func: Call { name, args }, + .. + } => { // first evaluate all the args we're going to need let mut arg_regs = Vec::new(); for arg in args.iter().rev() { - let (arg_reg, arg_code) = self.generate_expression(arg, true)?; + let (arg_reg, arg_code) = + self.generate_expression(arg, true, func_body)?; code.extend(arg_code); arg_regs.push(arg_reg); } @@ -561,7 +592,7 @@ impl CodeGenerator { let saved_regs = self.allocator.get_caller_saved_registers(); for reg in &saved_regs { // spill variables to stack - code.extend(self.allocator.spill_register(reg).unwrap()); + code.extend(self.allocator.free_register(reg).unwrap()); } // Evaluate and push arguments in reverse order @@ -587,7 +618,7 @@ impl CodeGenerator { return Err(CompilerError::Undefined(name.clone())); } - let result_reg: String; + let result_reg: Register; if use_result { let (temp_result_reg, result_alloc) = self.allocator.alloc_temp()?; @@ -603,7 +634,7 @@ impl CodeGenerator { } } } else { - result_reg = "zero".to_string(); + result_reg = Register::Zero; // Clean up arguments if args.len() > 0 { @@ -626,9 +657,9 @@ impl CodeGenerator { Ok((result_reg, code)) } - Expression::Unary { op, operand } => { + Expression::Unary { op, operand, .. } => { let (operand_reg, operand_code) = - self.generate_expression(operand, true)?; + self.generate_expression(operand, true, func_body)?; code.extend(operand_code); let (result_reg, result_alloc) = self.allocator.alloc_temp()?; @@ -660,7 +691,7 @@ impl CodeGenerator { Ok((result_reg, code)) } - Expression::Empty => Ok(("zero".to_string(), code)), + Expression::Empty => Ok((Register::Null, code)), } } diff --git a/compiler/src/backend/dsa/registers.rs b/compiler/src/backend/dsa/registers.rs index 00f59e7..3e3453b 100644 --- a/compiler/src/backend/dsa/registers.rs +++ b/compiler/src/backend/dsa/registers.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::{collections::HashMap, fmt}; use crate::model::CompilerError; @@ -6,78 +6,109 @@ use crate::model::CompilerError; /// Manages general-purpose registers (rg0-rgf) and handles stack spilling pub struct RegisterAllocator { /// Available general-purpose registers - available_registers: Vec, /// Maps variable names to their current location (register or stack offset) variable_locations: HashMap, /// Maps registers to the variables they currently hold - register_contents: HashMap, + register_contents: HashMap, /// Current stack offset for local variables (relative to bpr) /// Starts at -4 (going downward from base pointer) stack_offset: i32, /// Track which registers are currently in use - in_use: HashMap, + in_use: HashMap, } #[derive(Debug, Clone)] -pub enum Location { - Register(String), - Stack(i32), // offset from bpr +pub struct Location { + register: Option, + stack: Option, +} + +impl Location { + pub fn stack(offset: i32) -> Self { + Location { + register: None, + stack: Some(offset), + } + } + + pub fn register(register: Register) -> Self { + Location { + register: Some(register), + stack: None, + } + } } impl RegisterAllocator { pub fn new() -> Self { // Initialize with available GP registers (rg0-rgf = 16 registers) - let registers = vec![ - "rg0", "rg1", "rg2", "rg3", "rg4", "rg5", "rg6", "rg7", "rg8", "rg9", "rga", - "rgb", "rgc", "rgd", "rge", "rgf", + let in_use = vec![ + Register::Rg0, + Register::Rg1, + Register::Rg2, + Register::Rg3, + Register::Rg4, + Register::Rg5, + Register::Rg6, + Register::Rg7, + Register::Rg8, + Register::Rg9, + Register::Rga, + Register::Rgb, + Register::Rgc, + Register::Rgd, + Register::Rge, + Register::Rgf, ] - .into_iter() - .map(String::from) + .iter() + .map(|®| (reg, false)) .collect(); RegisterAllocator { - available_registers: registers, + // available_registers: registers, variable_locations: HashMap::new(), register_contents: HashMap::new(), stack_offset: -4, // Start at -4 (first local below saved bpr) - in_use: HashMap::new(), + in_use, } } /// Allocate a temporary register for expression evaluation /// Returns the register name and optionally assembly code to save it - pub fn alloc_temp(&mut self) -> Result<(String, Vec), CompilerError> { - let mut code = Vec::new(); - + pub fn alloc_temp(&mut self) -> Result<(Register, Vec), CompilerError> { // Try to find an unused register - for reg in &self.available_registers { - if !self.in_use.get(reg).unwrap_or(&false) { - self.in_use.insert(reg.clone(), true); - return Ok((reg.clone(), code)); - } + + println!("finding! {:#?}", self.in_use); + + if let Some(reg) = self.find_free_register() { + self.in_use.insert(reg, true); + return Ok((reg, Vec::new())); } // All registers in use - need to spill one // Choose the first register with a variable we can spill // Find a register to spill - let reg_to_spill = self - .available_registers - .iter() - .find(|reg| self.register_contents.contains_key(*reg)) - .cloned(); - if let Some(reg) = reg_to_spill { - // Spill this variable to stack - let spill_code = self.spill_register(®)?; - code.extend(spill_code); + // let reg_to_spill = self + // .available_registers + // .iter() + // .find(|reg| self.register_contents.contains_key(*reg)) + // .cloned(); - self.in_use.insert(reg.clone(), true); - return Ok((reg, code)); - } + // if let Some(reg) = reg_to_spill { + // // Spill this variable to stack + // let spill_code = self.spill_register(®)?; + // code.extend(spill_code); + + // self.in_use.insert(reg.clone(), true); + // return Ok((reg, code)); + // } + + todo!("an efficient stack spilling algorithm. needs scope awareness."); Err(CompilerError::Generic( "All registers are used up yet there are no variables to spill to the stack" @@ -88,7 +119,7 @@ impl RegisterAllocator { /// Free a temporary register after use /// NOTE: This will NOT free registers that contain variables! /// Variables persist throughout their scope and must not be freed - pub fn free_temp(&mut self, reg: &str) { + pub fn free_temp(&mut self, reg: &Register) { // Check if this register contains a variable if self.register_contents.contains_key(reg) { // This register holds a variable - don't free it! @@ -97,7 +128,19 @@ impl RegisterAllocator { } // This is a true temporary - safe to free - self.in_use.insert(reg.to_string(), false); + self.in_use.insert(*reg, false); + } + + pub fn free_var(&mut self, var: &str) { + // Check if this variable is in a register + if let Some(location) = self.variable_locations.get(var).cloned() { + if let Some(reg) = location.register { + self.register_contents.remove(®); + self.in_use.insert(reg, false); + } + + self.variable_locations.remove(var); + } } /// Allocate a register for a named variable @@ -105,41 +148,46 @@ impl RegisterAllocator { pub fn alloc_var( &mut self, var_name: &str, - ) -> Result<(String, Vec), CompilerError> { - if let Some(location) = self.variable_locations.get(var_name).cloned() { - match location { - Location::Register(reg) => { - return Ok((reg.clone(), Vec::new())); - } - Location::Stack(offset) => { - // Variable was pushed, need to calculate actual position - let (reg, mut code) = self.alloc_temp()?; + ) -> Result<(Register, Vec), CompilerError> { + if let Some(mut location) = self.variable_locations.get(var_name).cloned() { + // if the var is in a register we can use it already. + if let Some(reg) = location.register { + return Ok((reg, Vec::new())); + } - // Load from bpr + offset (offset is negative) - code.push(format!("\tsubi bpr {} {}", -(offset + 4), reg)); - code.push(format!( - "\tldw {}, {} // bpr{}: {}", - reg, - reg, - offset - 4, - var_name - )); + // if the variable is on the stack only, we need to get it in a register. + if let Some(offset) = location.stack { + // Variable was pushed, need to calculate actual position and update its + // location. + let (reg, mut code) = self.alloc_temp()?; - // Update location to register - self.variable_locations - .insert(var_name.to_string(), Location::Register(reg.clone())); - self.register_contents - .insert(reg.clone(), var_name.to_string()); + // acknowledge var is now in a reg as well. + location.register = Some(reg); - return Ok((reg, code)); - } + // Load from bpr + offset (offset is negative) + code.push(format!("\tsubi bpr {} {}", -(offset + 4), reg)); + code.push(format!( + "\tldw {}, {} // bpr{}: {}", + reg, + reg, + offset - 4, + var_name + )); + + // Update location to register + self.variable_locations + .insert(var_name.to_string(), location); + self.register_contents + .insert(reg.clone(), var_name.to_string()); + + return Ok((reg, code)); } } // Variable doesn't have a location yet, allocate a new register let (reg, code) = self.alloc_temp()?; self.variable_locations - .insert(var_name.to_string(), Location::Register(reg.clone())); + .insert(var_name.to_string(), Location::register(reg)); self.register_contents .insert(reg.clone(), var_name.to_string()); @@ -156,83 +204,89 @@ impl RegisterAllocator { pub fn load_var( &mut self, var_name: &str, - ) -> Result<(String, Vec), CompilerError> { + ) -> Result<(Register, Vec), CompilerError> { self.alloc_var(var_name) } /// Store a value from a register into a variable /// Updates tracking and returns any necessary assembly code - pub fn store_var(&mut self, var_name: &str, source_reg: &str) -> Vec { - let mut code = Vec::new(); - + pub fn store_var(&mut self, var_name: &str, source_reg: &Register) -> Vec { // Check if variable already has a location if let Some(location) = self.variable_locations.get(var_name) { - match location { - Location::Register(dest_reg) => { - if dest_reg != source_reg { - code.push(format!( - "\tmov {}, {} // var {}", - source_reg, dest_reg, var_name - )); - } - } - Location::Stack(offset) => { - code.push(format!( - "\tstw {}, bpr, {} // var {}", - source_reg, offset, var_name - )); + // if the variable exists in a register we write to that. + if let Some(reg) = location.register { + if reg == *source_reg { + return vec![format!( + "\tmov {}, {} // var {}", + source_reg, reg, var_name + )]; } } - } else { - // Variable doesn't exist yet, we can just use the same reg. - // self.variable_locations.insert( - // var_name.to_string(), - // Location::Register(source_reg.to_string()), - // ); - // self.register_contents - // .insert(source_reg.to_string(), var_name.to_string()); - // self.in_use.insert(source_reg.to_string(), true); - - let source_reg = source_reg.to_string(); - - // if we can avoid a move, absolutely do that. - if self.available_registers.contains(&source_reg) { - self.variable_locations - .insert(var_name.to_string(), Location::Register(source_reg.clone())); - self.register_contents - .insert(source_reg.clone(), var_name.to_string()); - self.in_use.insert(source_reg, true); - } else if let Some(free_reg) = self.find_free_register() { - code.push(format!("\tmov {}, {}", source_reg, free_reg)); - self.variable_locations - .insert(var_name.to_string(), Location::Register(free_reg.clone())); - self.register_contents - .insert(free_reg.clone(), var_name.to_string()); - self.in_use.insert(free_reg, true); - } else { - // No free registers - allocate on stack - // code.push(format!("\tstw {}, bpr, {}", source_reg, self.stack_offset)); - // self.variable_locations - // .insert(var_name.to_string(), Location::Stack(self.stack_offset)); - // self.stack_offset -= 4; // Move to next stack slot - // - todo!( - "we should spill other registers and keep this variable on the stack as it's more recent!" - ); + // if the variable exists on the stack but not a register we write here. + if let Some(offset) = location.stack { + return vec![format!( + "\tstw {}, bpr, {} // var {}", + source_reg, offset, var_name + )]; } } - code + // Variable doesn't exist yet, we can just use the same reg. + // if we can avoid a move, absolutely do that. + + // if this is true then there's no permanent variable here so it's safe to use. + if !self.register_contents.contains_key(source_reg) { + self.variable_locations + .insert(var_name.to_string(), Location::register(*source_reg)); + self.register_contents + .insert(*source_reg, var_name.to_string()); + self.in_use.insert(*source_reg, true); + + return Vec::new(); + } + + // if current register isn't free, (eg is another variable) we assign somewhere + // else. + if let Some(free_reg) = self.find_free_register() { + self.variable_locations + .insert(var_name.to_string(), Location::register(free_reg)); + self.register_contents + .insert(free_reg.clone(), var_name.to_string()); + self.in_use.insert(free_reg, true); + + return vec![format!("\tmov {}, {}", source_reg, free_reg)]; + } + + // No free registers - allocate on stack + // code.push(format!("\tstw {}, bpr, {}", source_reg, self.stack_offset)); + // self.variable_locations + // .insert(var_name.to_string(), Location::Stack(self.stack_offset)); + // self.stack_offset -= 4; // Move to next stack slot + // + todo!("an efficient stack spilling algorithm. needs scope awareness."); } - /// Spill a register to the stack - /// Returns assembly code to perform the spill - pub fn spill_register(&mut self, reg: &str) -> Result, CompilerError> { + /// spill a register to the stack (WITHOUT FREEING) + pub fn spill_register( + &mut self, + reg: &Register, + ) -> Result, CompilerError> { let mut code = Vec::new(); - if let Some(var_name) = self.register_contents.get(reg).cloned() { - // PUSH register to stack (spr decrements automatically) + // check if the variable is declared. + if let Some(var_name) = self.register_contents.get(reg).cloned() + && let Some(location) = self.variable_locations.get_mut(&var_name) + { + // check if var is on the stack + if let Some(offset) = location.stack { + // ensure stack value is up to date with register value. + code.push(format!("\tstw {}, {}", reg, offset)); + return Ok(code); + } + + // if the variable is not on the stack: + // push register to stack (spr decrements automatically) code.push(format!( "\tpush {} // bpr{}: {}", reg, self.stack_offset, var_name @@ -240,37 +294,83 @@ impl RegisterAllocator { // Track that we pushed one word self.stack_offset -= 4; - // Update variable location - it's now at current spr // Note: We track offset from bpr for consistency - self.variable_locations - .insert(var_name.clone(), Location::Stack(self.stack_offset)); - - // Remove from register tracking - self.register_contents.remove(reg); + location.stack = Some(self.stack_offset); + Ok(code) + } else { + Err(CompilerError::Generic(format!( + "Register {} does not contain a variable to spill!", + reg + ))) } + } - Ok(code) + /// free a register by spilling it to the stack. + /// Returns assembly code to perform the spill + pub fn free_register( + &mut self, + reg: &Register, + ) -> Result, CompilerError> { + let mut code = Vec::new(); + + // check if the variable is declared. + if let Some(var_name) = self.register_contents.get(reg).cloned() + && let Some(location) = self.variable_locations.get_mut(&var_name) + { + // check if var name is on the stack + if let Some(offset) = location.stack { + // store current register value in stack location + code.push(format!("\tstw {}, {}", reg, offset)); + + // free the register. + location.register = None; + self.register_contents.remove(reg); + return Ok(code); + } + + // if the variable is not on the stack: + // push register to stack (spr decrements automatically) + code.push(format!( + "\tpush {} // bpr{}: {}", + reg, self.stack_offset, var_name + )); + + // Track that we pushed one word + self.stack_offset -= 4; + // Update variable location - it's now at current spr + // Note: We track offset from bpr for consistency + location.stack = Some(self.stack_offset); + location.register = None; + self.register_contents.remove(reg); + + Ok(code) + } else { + Err(CompilerError::Generic(format!( + "Register {} does not contain a variable to spill!", + reg + ))) + } } /// Find a free register (not currently in use) - fn find_free_register(&self) -> Option { - for reg in &self.available_registers { - if !self.in_use.get(reg).unwrap_or(&false) { - return Some(reg.clone()); - } - } - None + fn find_free_register(&self) -> Option { + self.in_use + .iter() + .filter(|(_, in_use)| !**in_use) + .map(|(reg, _)| *reg) + .next() } /// Spill all registers to stack (useful before function calls) pub fn _spill_all(&mut self) -> Vec { let mut code = Vec::new(); - let regs_to_spill: Vec = self.register_contents.keys().cloned().collect(); + let regs_to_spill: Vec = + self.register_contents.keys().cloned().collect(); for reg in regs_to_spill { - if let Ok(spill_code) = self.spill_register(®) { + if let Ok(spill_code) = self.free_register(®) { code.extend(spill_code); } } @@ -293,23 +393,43 @@ impl RegisterAllocator { self.variable_locations.clear(); self.register_contents.clear(); self.stack_offset = -4; - self.in_use.clear(); + self.in_use = vec![ + Register::Rg0, + Register::Rg1, + Register::Rg2, + Register::Rg3, + Register::Rg4, + Register::Rg5, + Register::Rg6, + Register::Rg7, + Register::Rg8, + Register::Rg9, + Register::Rga, + Register::Rgb, + Register::Rgc, + Register::Rgd, + Register::Rge, + Register::Rgf, + ] + .iter() + .map(|®| (reg, false)) + .collect(); } /// Mark a variable as dead (no longer needed) /// Frees its register if it's in one - pub fn _free_var(&mut self, var_name: &str) { - if let Some(Location::Register(reg)) = self.variable_locations.get(var_name) { - let reg = reg.clone(); - self.register_contents.remove(®); - self.in_use.insert(reg, false); - } - self.variable_locations.remove(var_name); - } + // pub fn _free_var(&mut self, var_name: &str) { + // if let Some(Location::Register(reg)) = self.variable_locations.get(var_name) { + // let reg = reg.clone(); + // self.register_contents.remove(®); + // self.in_use.insert(reg, false); + // } + // self.variable_locations.remove(var_name); + // } /// Get list of registers that contain variables and are in use /// These need to be saved before function calls - pub fn get_caller_saved_registers(&self) -> Vec { + pub fn get_caller_saved_registers(&self) -> Vec { self.register_contents .iter() .filter(|(reg, _)| *self.in_use.get(*reg).unwrap_or(&false)) @@ -346,3 +466,50 @@ impl RegisterAllocator { code } } + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum Register { + Rg0, + Rg1, + Rg2, + Rg3, + Rg4, + Rg5, + Rg6, + Rg7, + Rg8, + Rg9, + Rga, + Rgb, + Rgc, + Rgd, + Rge, + Rgf, + Zero, + Null, +} + +impl fmt::Display for Register { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Rg0 => write!(f, "rg0"), + Self::Rg1 => write!(f, "rg1"), + Self::Rg2 => write!(f, "rg2"), + Self::Rg3 => write!(f, "rg3"), + Self::Rg4 => write!(f, "rg4"), + Self::Rg5 => write!(f, "rg5"), + Self::Rg6 => write!(f, "rg6"), + Self::Rg7 => write!(f, "rg7"), + Self::Rg8 => write!(f, "rg8"), + Self::Rg9 => write!(f, "rg9"), + Self::Rga => write!(f, "rga"), + Self::Rgb => write!(f, "rgb"), + Self::Rgc => write!(f, "rgc"), + Self::Rgd => write!(f, "rgd"), + Self::Rge => write!(f, "rge"), + Self::Rgf => write!(f, "rgf"), + Self::Zero => write!(f, "zero"), + Self::Null => write!(f, "null"), + } + } +} diff --git a/compiler/src/frontend/c/parser.rs b/compiler/src/frontend/c/parser.rs index 9d4c2bf..ec12efb 100644 --- a/compiler/src/frontend/c/parser.rs +++ b/compiler/src/frontend/c/parser.rs @@ -351,6 +351,7 @@ impl Parser { op, left: Box::new(expr), right, + type_id: None, }; } @@ -371,6 +372,7 @@ impl Parser { op, left: Box::new(expr), right, + type_id: None, }; } @@ -391,6 +393,7 @@ impl Parser { op, left: Box::new(expr), right, + type_id: None, }; } @@ -407,7 +410,11 @@ impl Parser { if let Some(op) = op { self.advance(); let operand = Box::new(self.parse_unary()?); - return Ok(Expression::Unary { op, operand }); + return Ok(Expression::Unary { + op, + operand, + type_id: None, + }); } self.parse_primary() @@ -418,7 +425,10 @@ impl Parser { TokenType::Number(n) => { let value = *n; self.advance(); - Ok(Expression::Number(value as isize)) + Ok(Expression::Number { + value: value as isize, + type_id: None, + }) } TokenType::Identifier(name) => { let name = name.clone(); @@ -445,6 +455,7 @@ impl Parser { namespace: None, }, args, + type_id: None, }) } else { Ok(Expression::Variable { diff --git a/compiler/src/frontend/dsc/mod.rs b/compiler/src/frontend/dsc/mod.rs index b4a6666..5718e6f 100644 --- a/compiler/src/frontend/dsc/mod.rs +++ b/compiler/src/frontend/dsc/mod.rs @@ -2,11 +2,11 @@ use common::logging::log; use crate::model::{CompilerError, Program}; use parser::{ParseResult, Parser}; -use semantic_analyser::Analyser; +// use semantic_analyser::Analyser; pub mod lexer; pub mod parser; -pub mod semantic_analyser; +// pub mod semantic_analyser; pub fn generate_ast(input: &str) -> Result { log("Tokenising Input..."); @@ -30,8 +30,8 @@ pub fn generate_ast(input: &str) -> Result { log("Analyzing AST..."); log("Checking Type Information..."); - let analyser = Analyser::new(); - analyser.analyse(ast.clone()).unwrap(); + // let mut analyser = Analyser::new(); + // analyser.analyse(ast.clone()).unwrap(); log("Type Checking Complete..."); Ok(ast) diff --git a/compiler/src/frontend/dsc/parser.rs b/compiler/src/frontend/dsc/parser.rs index 752fbba..2fa533b 100644 --- a/compiler/src/frontend/dsc/parser.rs +++ b/compiler/src/frontend/dsc/parser.rs @@ -1,7 +1,7 @@ use super::lexer::Token; use crate::model::{ - BinaryOperator, Block, CompilerError, ConstExpr, Declaration, Dependency, Expression, - Program, Statement, TypeId, UnaryOperator, Variable, + BinaryOperator, Block, Call, CompilerError, ConstExpr, Declaration, Dependency, + Expression, Program, Statement, TypeId, UnaryOperator, Variable, }; use crate::{expect_tt, expect_value}; use std::ops::{ControlFlow, FromResidual, Try}; @@ -353,7 +353,7 @@ impl Parser { let mut expr = self.parse_additive()?; while let Some(op) = match self.peek_next()? { - Token::EqualEqual => Some(BinaryOperator::Ne), + Token::EqualEqual => Some(BinaryOperator::Eq), Token::BangEqual => Some(BinaryOperator::Ne), Token::Less => Some(BinaryOperator::Lt), Token::Greater => Some(BinaryOperator::Gt), @@ -367,7 +367,8 @@ impl Parser { op, left: Box::new(expr), right, - } + type_id: Some(TypeId::Bool), + }; } ParseResult::Accept(expr) @@ -387,6 +388,7 @@ impl Parser { op, left: Box::new(left), right: Box::new(self.parse_additive()?), + type_id: Some(TypeId::U32), }) } @@ -404,6 +406,7 @@ impl Parser { op, left: Box::new(left), right: Box::new(self.parse_multiplicative()?), + type_id: None, }) } @@ -418,14 +421,21 @@ impl Parser { self.next()?; let operand = Box::new(self.parse_unary()?); - ParseResult::Accept(Expression::Unary { op, operand }) + ParseResult::Accept(Expression::Unary { + op, + operand, + type_id: None, + }) } fn parse_primary(&mut self) -> ParseResult { match self.peek_next()? { Token::Integer(value) => { self.next()?; - ParseResult::Accept(Expression::Number(value as isize)) + ParseResult::Accept(Expression::Number { + value: value as isize, + type_id: None, + }) } Token::String(value) => { self.next()?; @@ -450,7 +460,14 @@ impl Parser { let _ = expect_tt!(self.next()?, RightParen)?; - ParseResult::Accept(Expression::Call { name, args }) + ParseResult::Accept(Expression::Call { + func: Call { + name: name.clone(), + args, + }, + + type_id: None, + }) } else { ParseResult::Accept(Expression::Variable { name, diff --git a/compiler/src/frontend/dsc/semantic_analyser.rs b/compiler/src/frontend/dsc/semantic_analyser.rs index 2b18e2c..9daf780 100644 --- a/compiler/src/frontend/dsc/semantic_analyser.rs +++ b/compiler/src/frontend/dsc/semantic_analyser.rs @@ -1,13 +1,226 @@ -use crate::model::{CompilerError, Program}; +use std::collections::HashMap; -pub struct Analyser; +use crate::model::{ + BinaryOperator, // You'll need to add this to your imports + CompilerError, + Declaration, + Dependency, + Expression, + Program, + TypeId, + UnaryOperator, +}; + +pub struct Analyser { + symbol_table: HashMap, +} + +const NUMERIC_TYPES: &[TypeId] = &[ + TypeId::U32, + TypeId::I32, + TypeId::I16, + TypeId::U16, + TypeId::I8, + TypeId::U8, +]; impl Analyser { pub fn new() -> Self { - Self + Self { + symbol_table: HashMap::new(), + } } - pub fn analyse(&self, _ast: Program) -> Result<(), CompilerError> { + pub fn analyse(&mut self, ast: Program) -> Result<(), CompilerError> { + // build table of global symbols. + for dec in ast.declarations { + let name = match dec.clone() { + Declaration::Function { name, .. } => name, + Declaration::Variable { var, .. } => var.name, + Declaration::Dependency(Dependency { name, .. }) => name, + }; + + self.symbol_table.insert(name, dec); + } + Ok(()) } + + fn match_type( + actual: TypeId, + expected: Option, + ) -> Result { + match expected { + Some(id) => { + if id != actual { + Err(CompilerError::TypeMismatch(id, actual)) + } else { + Ok(actual) + } + } + None => Ok(actual), + } + } + + fn get_type( + &mut self, // Changed from &self to &mut self since we modify expr + expr: &mut Expression, + expected_type: Option, + ) -> Result { + match expr { + // Correct IFF we're expecting a void type + Expression::Empty => Self::match_type(TypeId::Void, expected_type), + + // Correct IFF we're expecting a char type + Expression::CharLiteral(_) => Self::match_type(TypeId::Char, expected_type), + + // Correct IFF we're expecting a string slice type + Expression::StringLiteral(_) => { + Self::match_type(TypeId::Ptr(Box::new(TypeId::Char)), expected_type) + } + + Expression::Variable { name, expr_type } => { + let actual = expr_type.clone().ok_or(CompilerError::UnknownType)?; + Self::match_type(actual, expected_type) + } + + Expression::Number { value, type_id } => { + // If we already know the TypeId + if let Some(id) = type_id { + return Self::match_type(id.clone(), expected_type); + } + + // If we're expecting a type id, check it's numeric. + // TODO: add checks to make sure it's valid for its size eg u8 cant be + // more than 255 + if let Some(expected) = expected_type { + if NUMERIC_TYPES.contains(&expected) { + *type_id = Some(expected.clone()); + return Ok(expected); + } else { + return Err(CompilerError::TypeMismatch(expected, TypeId::U32)); + } + } + + // Default to i32 if no type information is available + *type_id = Some(TypeId::I32); + Ok(TypeId::I32) + } + + Expression::Binary { + op, + left, + right, + type_id, + } => { + // For binary operations, both operands should have compatible types + // and the result type depends on the operation + let left_type = self.get_type(left, None)?; + let right_type = self.get_type(right, Some(left_type.clone()))?; + + // For numeric operations, result has the same type as operands + if NUMERIC_TYPES.contains(&left_type) + && NUMERIC_TYPES.contains(&right_type) + { + *type_id = Some(left_type); + Self::match_type(left_type, expected_type) + } else { + Err(CompilerError::TypeMismatch(left_type, right_type)) + } + } + + Expression::Unary { + op, + operand, + type_id, + } => { + match op { + UnaryOperator::Plus | UnaryOperator::Minus => { + // Unary +/- require numeric operands + let inner_type = self.get_type(operand, None)?; + + if NUMERIC_TYPES.contains(&inner_type) { + *type_id = Some(inner_type.clone()); + Self::match_type(inner_type, expected_type) + } else { + Err(CompilerError::TypeMismatch(inner_type, TypeId::I32)) + } + } + + UnaryOperator::Dereference => { + // For dereference (*ptr), the operand must be a pointer + // and the result type is what the pointer points to + let inner_type = self.get_type(operand, None)?; + + match inner_type { + TypeId::Ptr(inner) => { + let deref_type = *inner; + *type_id = Some(deref_type.clone()); + Self::match_type(deref_type, expected_type) + } + _ => Err(CompilerError::Generic(format!( + "Cannot dereference non-pointer type: {:?}", + inner_type + ))), + } + } + + UnaryOperator::Reference => { + // For reference (&var), we need to determine what we're taking + // a reference to, then wrap it in a Ptr + // If expected_type is Ptr(T), then operand should have type T + let expected_inner = match expected_type.clone() { + Some(TypeId::Ptr(inner)) => Some(*inner), + _ => None, + }; + + let inner_type = self.get_type(operand, expected_inner)?; + let ref_type = TypeId::Ptr(Box::new(inner_type)); + *type_id = Some(ref_type.clone()); + Self::match_type(ref_type, expected_type) + } + } + } + + Expression::Call { + name, + args, + type_id, + } => match self.symbol_table.get(&name.name) { + Some(Declaration::Function { + params, + return_type, + .. + }) => { + // check that we've given the right number of arguments. + if args.len() != params.len() { + return Err(CompilerError::Generic(format!( + "Function {} expected {} arguments but received {}", + name.name, + params.len(), + args.len() + ))); + } + + for (arg, param) in args.iter_mut().zip(params.iter()) { + // check that the argument type matches the parameter type. + let provided_type = self.get_type(arg, Some(param.type_id))?; + if provided_type != param.type_id { + return Err(CompilerError::TypeMismatch( + param.type_id, + provided_type, + )); + } + } + + *type_id = Some(return_type.clone()); + Self::match_type(return_type.clone(), expected_type) + } + _ => Err(CompilerError::Generic(format!( + "Function {} not found in symbol table", + name.name + ))), + }, + } + } } diff --git a/compiler/src/frontend/mod.rs b/compiler/src/frontend/mod.rs index a17ba62..a9ee921 100644 --- a/compiler/src/frontend/mod.rs +++ b/compiler/src/frontend/mod.rs @@ -1,12 +1,12 @@ use crate::model::{CompilerError, Program}; -mod c; +// mod c; mod dsc; pub fn compiler_frontend(ext: &str, data: &str) -> Result { match ext { "dsc" => Ok(dsc::generate_ast(&data)?), - "c" => Ok(c::generate_ast(&data)?), + // "c" => Ok(c::generate_ast(&data)?), _ => Err(CompilerError::Generic(format!( "File type {} not supported", ext diff --git a/compiler/src/lib.rs b/compiler/src/lib.rs index 41fb48b..004cfea 100644 --- a/compiler/src/lib.rs +++ b/compiler/src/lib.rs @@ -46,6 +46,8 @@ pub fn compile_file( Err(err) => return Err(format!("Compilation failed: {err:?}").into()), }; + println!("Parsed AST: {:#?}", ast); + let output_ext = output_path .extension() .and_then(|s| s.to_str()) diff --git a/compiler/src/model.rs b/compiler/src/model.rs index 9c7e68e..df1269f 100644 --- a/compiler/src/model.rs +++ b/compiler/src/model.rs @@ -9,9 +9,11 @@ pub enum CompilerError { Undefined(Name), InvalidSyntax(String), Generic(String), + UnknownType, + TypeMismatch(TypeId, TypeId), } -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Eq, Clone)] pub struct Name { pub name: String, pub namespace: Option, @@ -46,7 +48,7 @@ pub struct Dependency { } #[allow(unused)] -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub enum TypeId { U8, U16, @@ -54,18 +56,45 @@ pub enum TypeId { I8, I16, I32, + Bool, Char, Void, Ptr(Box), Ref(Box), Array(Box, usize), - Struct { name: Name, fields: Vec }, + Struct { name: Name, fields: Vec }, +} + +impl fmt::Display for TypeId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::U8 => write!(f, "u8"), + Self::U16 => write!(f, "u16"), + Self::U32 => write!(f, "u32"), + Self::I8 => write!(f, "i8"), + Self::I16 => write!(f, "i16"), + Self::I32 => write!(f, "i32"), + Self::Bool => write!(f, "bool"), + Self::Char => write!(f, "char"), + Self::Void => write!(f, "void"), + Self::Ptr(t) => write!(f, "*{}", t), + Self::Ref(t) => write!(f, "&{}", t), + Self::Array(t, len) => write!(f, "[{}; {}]", t, len), + Self::Struct { name, fields } => { + write!(f, "struct {} {{", name)?; + for (i, field) in fields.iter().enumerate() { + write!(f, "{}: {}", i, field)?; + } + write!(f, "}}") + } + } + } } pub type Block = Vec; #[allow(unused)] -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct Variable { pub name: String, pub type_id: TypeId, @@ -100,6 +129,7 @@ pub enum Statement { body: Vec, }, Loop(Block), + Defer(Call), Break, Continue, Return(Option), @@ -128,28 +158,47 @@ pub enum Expression { op: BinaryOperator, left: Box, right: Box, + + // Post-Semantic Analysis + type_id: Option, }, Unary { op: UnaryOperator, operand: Box, + + // Post-Semantic Analysis + type_id: Option, }, Variable { name: Name, expr_type: Option, }, Call { - name: Name, - args: Vec, + func: Call, + + // Post-Semantic Analysis + type_id: Option, + }, + Number { + value: isize, + + // Post-Semantic Analysis + type_id: Option, }, - Number(isize), StringLiteral(String), CharLiteral(char), } +#[derive(Debug, Clone)] +pub struct Call { + pub name: Name, + pub args: Vec, +} + impl Expression { pub fn is_pure(&self) -> bool { match self { - Expression::Number(_) => true, + Expression::Number { .. } => true, Expression::StringLiteral(_) => true, Expression::CharLiteral(_) => true, Expression::Call { .. } => false, @@ -159,6 +208,29 @@ impl Expression { Expression::Variable { .. } => true, } } + + pub fn type_id(&self) -> Result { + match self { + Expression::Number { type_id, .. } => { + type_id.clone().ok_or(CompilerError::UnknownType) + } + Expression::StringLiteral(_) => Ok(TypeId::Ptr(Box::new(TypeId::Char))), + Expression::CharLiteral(_) => Ok(TypeId::Char), + Expression::Call { type_id, .. } => { + type_id.clone().ok_or(CompilerError::UnknownType) + } + Expression::Binary { type_id, .. } => { + type_id.clone().ok_or(CompilerError::UnknownType) + } + Expression::Unary { type_id, .. } => { + type_id.clone().ok_or(CompilerError::UnknownType) + } + Expression::Empty => Ok(TypeId::Void), + Expression::Variable { expr_type, .. } => { + expr_type.clone().ok_or(CompilerError::UnknownType) + } + } + } } #[allow(unused)] -- 2.47.3 From 828f5bfb2d861a5f40e206e6f263ee52c911ac96 Mon Sep 17 00:00:00 2001 From: zxq5 Date: Sun, 8 Feb 2026 11:45:26 +0000 Subject: [PATCH 37/53] fixed pointers and stuff. --- compiler/src/backend/dsa/codegen.rs | 36 +++---- compiler/src/backend/dsa/registers.rs | 139 +++++++++++++++----------- 2 files changed, 102 insertions(+), 73 deletions(-) diff --git a/compiler/src/backend/dsa/codegen.rs b/compiler/src/backend/dsa/codegen.rs index 680f9ad..3072ade 100644 --- a/compiler/src/backend/dsa/codegen.rs +++ b/compiler/src/backend/dsa/codegen.rs @@ -245,7 +245,7 @@ impl CodeGenerator { code.extend(store_code); // Free temporary register - self.allocator.free_temp(&result_reg); + self.allocator.free_temp(result_reg); } else { // Just declaring variable without initialization self.allocator.alloc_var(&var.name)?; @@ -267,8 +267,8 @@ impl CodeGenerator { code.push(format!("\tstw {}, {}", result_reg, ptr_reg)); - self.allocator.free_temp(&result_reg); - self.allocator.free_temp(&ptr_reg); + self.allocator.free_temp(result_reg); + self.allocator.free_temp(ptr_reg); } Statement::Assign { varname, value } => { @@ -288,7 +288,7 @@ impl CodeGenerator { } // Free temporary register - self.allocator.free_temp(&result_reg); + self.allocator.free_temp(result_reg); } Statement::Return(expr) => { @@ -298,7 +298,7 @@ impl CodeGenerator { code.extend(expr_code); code.push(format!("\tstw {}, bpr, 8", result_reg)); code.push(format!("\tjmp _ret")); - self.allocator.free_temp(&result_reg); + self.allocator.free_temp(result_reg); } } @@ -314,7 +314,7 @@ impl CodeGenerator { // Compare with zero code.push(format!("\tcmp {}, zero", cond_reg)); - self.allocator.free_temp(&cond_reg); + self.allocator.free_temp(cond_reg); // Generate unique labels let then_label = format!("_then_{}", self.get_unique_label()); @@ -361,7 +361,7 @@ impl CodeGenerator { code.extend(cond_code); code.push(format!("\tcmp {}, zero", cond_reg)); - self.allocator.free_temp(&cond_reg); + self.allocator.free_temp(cond_reg); code.push(format!("\tjeq {}", loop_end)); @@ -390,7 +390,7 @@ impl CodeGenerator { let (result_reg, expr_code) = self.generate_expression(expr, false, func_body)?; code.extend(expr_code); - self.allocator.free_temp(&result_reg); + self.allocator.free_temp(result_reg); } Statement::Block(statements) => { @@ -562,8 +562,8 @@ impl CodeGenerator { } // Free operand registers (allocator will protect variables) - self.allocator.free_temp(&left_reg); - self.allocator.free_temp(&right_reg); + self.allocator.free_temp(left_reg); + self.allocator.free_temp(right_reg); Ok((result_reg, code)) } @@ -592,7 +592,7 @@ impl CodeGenerator { let saved_regs = self.allocator.get_caller_saved_registers(); for reg in &saved_regs { // spill variables to stack - code.extend(self.allocator.free_register(reg).unwrap()); + code.extend(self.allocator.free_register(reg).unwrap().1); } // Evaluate and push arguments in reverse order @@ -651,7 +651,7 @@ impl CodeGenerator { // Free argument registers for reg in arg_regs { - self.allocator.free_temp(®); + self.allocator.free_temp(reg); } Ok((result_reg, code)) @@ -678,16 +678,18 @@ impl CodeGenerator { code.push(format!("\tldw {}, {}", operand_reg, result_reg)); } UnaryOperator::Reference => { - code.extend(self.allocator.spill_register(&operand_reg)?); + let (offset, alloc_code) = + self.allocator.free_register(&operand_reg)?; + code.extend(alloc_code); code.push(format!( - "\tsubi bpr {} {}", - -(4 + self.allocator.get_stack_offset()), + "\taddi spr, {}, {}", + offset - self.allocator.get_stack_offset(), result_reg - )) + )); } } - self.allocator.free_temp(&operand_reg); + self.allocator.free_temp(operand_reg); Ok((result_reg, code)) } diff --git a/compiler/src/backend/dsa/registers.rs b/compiler/src/backend/dsa/registers.rs index 3e3453b..abf5df3 100644 --- a/compiler/src/backend/dsa/registers.rs +++ b/compiler/src/backend/dsa/registers.rs @@ -82,7 +82,7 @@ impl RegisterAllocator { pub fn alloc_temp(&mut self) -> Result<(Register, Vec), CompilerError> { // Try to find an unused register - println!("finding! {:#?}", self.in_use); + // println!("finding! {:#?}", self.in_use); if let Some(reg) = self.find_free_register() { self.in_use.insert(reg, true); @@ -116,25 +116,33 @@ impl RegisterAllocator { )) } + // fn set_in_use(&mut self, reg: Register, in_use: bool) { + // self.in_use[reg as usize].1 = in_use; + // } + /// Free a temporary register after use /// NOTE: This will NOT free registers that contain variables! /// Variables persist throughout their scope and must not be freed - pub fn free_temp(&mut self, reg: &Register) { + pub fn free_temp(&mut self, reg: Register) { // Check if this register contains a variable - if self.register_contents.contains_key(reg) { + if self.register_contents.contains_key(®) { // This register holds a variable - don't free it! // Variables are only freed when they go out of scope via free_var() return; } // This is a true temporary - safe to free - self.in_use.insert(*reg, false); + if reg != Register::Zero { + self.in_use.insert(reg, false); + } } pub fn free_var(&mut self, var: &str) { // Check if this variable is in a register if let Some(location) = self.variable_locations.get(var).cloned() { - if let Some(reg) = location.register { + if let Some(reg) = location.register + && reg != Register::Zero + { self.register_contents.remove(®); self.in_use.insert(reg, false); } @@ -165,12 +173,12 @@ impl RegisterAllocator { location.register = Some(reg); // Load from bpr + offset (offset is negative) - code.push(format!("\tsubi bpr {} {}", -(offset + 4), reg)); + // code.push(format!("\tsubi bpr {} {}", -(offset + 4), reg)); code.push(format!( - "\tldw {}, {} // bpr{}: {}", + "\tldw spr, {}, {} // spr+{}: {}", reg, - reg, - offset - 4, + offset - self.stack_offset, + offset - self.stack_offset, var_name )); @@ -217,8 +225,8 @@ impl RegisterAllocator { if let Some(reg) = location.register { if reg == *source_reg { return vec![format!( - "\tmov {}, {} // var {}", - source_reg, reg, var_name + "\tmov {}, {} // save var:{} reg:{}", + source_reg, reg, var_name, reg )]; } } @@ -226,8 +234,11 @@ impl RegisterAllocator { // if the variable exists on the stack but not a register we write here. if let Some(offset) = location.stack { return vec![format!( - "\tstw {}, bpr, {} // var {}", - source_reg, offset, var_name + "\tstw {}, spr, {} // save var:{} offset:{}", + source_reg, + offset - self.stack_offset, + var_name, + offset )]; } } @@ -268,7 +279,8 @@ impl RegisterAllocator { } /// spill a register to the stack (WITHOUT FREEING) - pub fn spill_register( + /// DO NOT USE this if it's for a pointer!!!! + pub fn _spill_register( &mut self, reg: &Register, ) -> Result, CompilerError> { @@ -281,22 +293,31 @@ impl RegisterAllocator { // check if var is on the stack if let Some(offset) = location.stack { // ensure stack value is up to date with register value. - code.push(format!("\tstw {}, {}", reg, offset)); + code.push(format!( + "\tstw {}, spr, {} // save var:{} offset:{}", + reg, + offset - self.stack_offset, + var_name, + offset + )); return Ok(code); } - // if the variable is not on the stack: - // push register to stack (spr decrements automatically) - code.push(format!( - "\tpush {} // bpr{}: {}", - reg, self.stack_offset, var_name - )); - // Track that we pushed one word self.stack_offset -= 4; + + // if the variable is not on the stack: + // push register to stack (spr decrements automatically) + let offset = self.stack_offset; + code.push(format!( + "\tpush {} // free var:{} offset:{}", + reg, var_name, offset + )); + // Update variable location - it's now at current spr // Note: We track offset from bpr for consistency - location.stack = Some(self.stack_offset); + location.stack = Some(offset); + Ok(code) } else { Err(CompilerError::Generic(format!( @@ -311,7 +332,7 @@ impl RegisterAllocator { pub fn free_register( &mut self, reg: &Register, - ) -> Result, CompilerError> { + ) -> Result<(i32, Vec), CompilerError> { let mut code = Vec::new(); // check if the variable is declared. @@ -321,30 +342,36 @@ impl RegisterAllocator { // check if var name is on the stack if let Some(offset) = location.stack { // store current register value in stack location - code.push(format!("\tstw {}, {}", reg, offset)); + code.push(format!( + "\tstw {}, spr, {} // save var:{} offset:{}", + reg, + offset - self.stack_offset, + var_name, + offset + )); // free the register. location.register = None; self.register_contents.remove(reg); - return Ok(code); + return Ok((offset, code)); } - // if the variable is not on the stack: - // push register to stack (spr decrements automatically) - code.push(format!( - "\tpush {} // bpr{}: {}", - reg, self.stack_offset, var_name - )); - // Track that we pushed one word self.stack_offset -= 4; - // Update variable location - it's now at current spr + + let offset = self.stack_offset; + code.push(format!( + "\tpush {} // free var:{} offset:{}", + reg, var_name, offset + )); + + // Update variable location // Note: We track offset from bpr for consistency - location.stack = Some(self.stack_offset); + location.stack = Some(offset); location.register = None; self.register_contents.remove(reg); - Ok(code) + Ok((offset, code)) } else { Err(CompilerError::Generic(format!( "Register {} does not contain a variable to spill!", @@ -357,7 +384,7 @@ impl RegisterAllocator { fn find_free_register(&self) -> Option { self.in_use .iter() - .filter(|(_, in_use)| !**in_use) + .filter(|(_, in_use)| !*in_use) .map(|(reg, _)| *reg) .next() } @@ -371,7 +398,7 @@ impl RegisterAllocator { for reg in regs_to_spill { if let Ok(spill_code) = self.free_register(®) { - code.extend(spill_code); + code.extend(spill_code.1); } } @@ -469,24 +496,24 @@ impl RegisterAllocator { #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum Register { - Rg0, - Rg1, - Rg2, - Rg3, - Rg4, - Rg5, - Rg6, - Rg7, - Rg8, - Rg9, - Rga, - Rgb, - Rgc, - Rgd, - Rge, - Rgf, - Zero, - Null, + Rg0 = 0, + Rg1 = 1, + Rg2 = 2, + Rg3 = 3, + Rg4 = 4, + Rg5 = 5, + Rg6 = 6, + Rg7 = 7, + Rg8 = 8, + Rg9 = 9, + Rga = 10, + Rgb = 11, + Rgc = 12, + Rgd = 13, + Rge = 14, + Rgf = 15, + Zero = 16, + Null = 17, } impl fmt::Display for Register { -- 2.47.3 From 9f35fc941537ad104e277e855e5d48cdd298b50d Mon Sep 17 00:00:00 2001 From: zxq5 Date: Sun, 8 Feb 2026 12:36:49 +0000 Subject: [PATCH 38/53] block allocator implementation and example --- resources/dsa/lib/memory/block_alloc.dsa | 216 +++++++++++++++++++++++ resources/dsa/main.dsc | 36 +++- 2 files changed, 248 insertions(+), 4 deletions(-) create mode 100644 resources/dsa/lib/memory/block_alloc.dsa diff --git a/resources/dsa/lib/memory/block_alloc.dsa b/resources/dsa/lib/memory/block_alloc.dsa new file mode 100644 index 0000000..69eaa7e --- /dev/null +++ b/resources/dsa/lib/memory/block_alloc.dsa @@ -0,0 +1,216 @@ +// block_alloc.dsa +// Fixed-size block allocator +// +// Memory layout: +// [base + 0]: free list head pointer (pointer to first free block, or 0 if none) +// [base + 4]: block size +// [base + 8]: total blocks +// [base + 12]: base address of block pool +// [base + 16+]: block pool (each block starts with a 4-byte next pointer) +// +// Usage: +// include block_alloc "./lib/memory/block_alloc.dsa" +// +// For init: +// push num_blocks (e.g., 32) +// push block_size (e.g., 64 bytes) +// call block_alloc::init +// pop block_size +// pop num_blocks +// ; result in spr+8 (allocator handle) +// +// For alloc: +// push allocator_handle +// call block_alloc::alloc +// pop allocator_handle +// ; result in spr+8 (pointer to block, or 0 if out of memory) +// +// For free: +// push block_pointer +// push allocator_handle +// call block_alloc::free +// pop allocator_handle +// pop block_pointer + +dw heap_start: 0x30000 // Start of our heap area + +// Initialize the allocator +// Args: block_size, num_blocks +// Returns: allocator handle (pointer to metadata) +init: + push bpr + mov spr, bpr + + ldw bpr, rg0, 8 // block_size + ldw bpr, rg1, 12 // num_blocks + + // Allocate metadata (16 bytes) + pool space + ldw heap_start, rg2 // base address for this allocator + mov rg2, rg3 // save base in rg3 + + // Calculate total size needed: 16 + (block_size * num_blocks) + // We'll use a simple multiplication by repeated addition + mov rg0, rg4 // block_size to rg4 + mov rg1, rg5 // num_blocks to rg5 + lli 0, acc // accumulator for multiplication + +_multiply_loop: + cmp rg5, zero + jeq _multiply_done + add acc, rg4, acc + dec rg5 + jmp _multiply_loop + +_multiply_done: + // acc now contains block_size * num_blocks + addi acc, 16 // add metadata size + + // Update heap_start for next allocation + add rg2, acc, acc + stw acc, heap_start + + // Now set up metadata at rg3 (base) + // [base + 0]: free list head (will point to first block) + // [base + 4]: block_size + // [base + 8]: total blocks + // [base + 12]: pool base address + + addi rg3, 16, rg6 // rg6 = pool base + + stw rg6, rg3 // store pool base as free list head initially + stw rg0, rg3, 4 // store block_size + stw rg1, rg3, 8 // store total blocks + stw rg6, rg3, 12 // store pool base address + + // Now initialize the free list + // Each block's first 4 bytes point to the next block + // rg6 = current block pointer + // rg0 = block_size + // rg1 = num_blocks (counter) + + dec rg1 // we'll count down from num_blocks-1 + +_init_loop: + cmp rg1, zero + jeq _init_loop_done + + // Calculate next block address: current + block_size + add rg6, rg0, rg7 // rg7 = next block address + + // Store next pointer at current block + stw rg7, rg6 + + // Move to next block + mov rg7, rg6 + dec rg1 + jmp _init_loop + +_init_loop_done: + // Last block points to null (0) + lli 0, acc + stw acc, rg6 + + // Return allocator handle (base address - 16 to get back to metadata start) + stw rg3, bpr, 8 + + mov bpr, spr + pop bpr + return + + +// Allocate a block +// Args: allocator_handle +// Returns: pointer to block (or 0 if out of memory) +alloc: + push bpr + mov spr, bpr + + ldw bpr, rg0, 8 // allocator handle (metadata base) + + // Load free list head + ldw rg0, rg1 // rg1 = free list head + + // Check if free list is empty + cmp rg1, zero + jeq _alloc_fail + + // Free list is not empty, pop the first block + // Load the next pointer from the block we're allocating + ldw rg1, rg2 // rg2 = next free block + + // Update free list head to point to next block + stw rg2, rg0 + + // Return the allocated block (rg1) + stw rg1, bpr, 8 + jmp _alloc_done + +_alloc_fail: + // No free blocks, return 0 + lli 0, acc + stw acc, bpr, 8 + +_alloc_done: + mov bpr, spr + pop bpr + return + + +// Free a block +// Args: allocator_handle, block_pointer +// Returns: nothing (but could return error code if block is invalid) +free: + push bpr + mov spr, bpr + + ldw bpr, rg0, 8 // allocator handle + ldw bpr, rg6, 12 // pointer to the block pointer to free + ldw rg6, rg1 // rg1 = block pointer to free + + // Load current free list head + ldw rg0, rg2 // rg2 = current head + + // Set the freed block's next pointer to current head + stw rg2, rg1 + + // Update free list head to point to freed block + stw rg1, rg0 + + // Update the freed block's previous pointer to NULL + lli 0, rg1 + stw rg1, rg6 + + mov bpr, spr + pop bpr + return + + +// Debug function: get stats +// Args: allocator_handle +// Returns: nothing (but could populate a stats structure) +get_stats: + push bpr + mov spr, bpr + + ldw bpr, rg0, 8 // allocator handle + + // Count free blocks by traversing the free list + ldw rg0, rg1 // rg1 = free list head + lli 0, rg2 // rg2 = counter + +count_loop: + cmp rg1, zero + jeq count_done + + inc rg2 + ldw rg1, rg1 // move to next block + jmp count_loop + +count_done: + // rg2 now contains number of free blocks + // Could store this somewhere or return it + stw rg2, bpr, 8 + + mov bpr, spr + pop bpr + return diff --git a/resources/dsa/main.dsc b/resources/dsa/main.dsc index 0e0b119..72cfd71 100644 --- a/resources/dsa/main.dsc +++ b/resources/dsa/main.dsc @@ -1,9 +1,37 @@ include print: "./lib/io/print.dsa"; -include fib: "./lib/maths/fib.dsa"; +include alloc: "./lib/memory/block_alloc.dsa"; fn main() -> u32 { - let x: u32 = 6; + let allocator: u32 = alloc::init(64, 32); - let y: u32 = fib::fib_n(x); - print::print_num(y); + print::print_hex_word(allocator); + + print::print_newline(); + + let ptr: u32 = alloc::alloc(allocator); + print::print_hex_word(ptr); + *ptr = 200; + + print::print_newline(); + + let p2: u32 = alloc::alloc(allocator); + print::print_hex_word(p2); + print::print_newline(); + print::print_num(*ptr); + + alloc::free(allocator, &ptr); + let ptr3: u32 = alloc::alloc(allocator); + + print::print_newline(); + + print::print_hex_word(ptr3); + + print::print_newline(); + print::print_hex_word(ptr); + + if ptr == 0 { + print::print("successful free of ptr"); + } + + return 0; } \ No newline at end of file -- 2.47.3 From 328741eb51bd19d18ec73b5a50a6e2cc129cd2c7 Mon Sep 17 00:00:00 2001 From: zxq5 Date: Sun, 8 Feb 2026 20:03:31 +0000 Subject: [PATCH 39/53] updated compiler with support for more operators. (only the unary operators from this are implemented for now) --- compiler/src/backend/dsa/codegen.rs | 55 +- compiler/src/backend/dsa/registers.rs | 4 +- compiler/src/frontend/dsc/lexer.rs | 834 +++++++++++++++++--------- compiler/src/frontend/dsc/parser.rs | 179 ++++-- compiler/src/model.rs | 154 ++++- 5 files changed, 868 insertions(+), 358 deletions(-) diff --git a/compiler/src/backend/dsa/codegen.rs b/compiler/src/backend/dsa/codegen.rs index 3072ade..64fa0a2 100644 --- a/compiler/src/backend/dsa/codegen.rs +++ b/compiler/src/backend/dsa/codegen.rs @@ -510,7 +510,7 @@ impl CodeGenerator { code.push("\tpop zero".to_string()); } // Comparison operators - return 1 (true) or 0 (false) - BinaryOperator::Eq => { + BinaryOperator::Equal => { code.push(format!("\tcmp {}, {}", left_reg, right_reg)); code.push(format!("\tlli 1, {}", result_reg)); let end_label = format!("_cmp_end_{}", self.get_unique_label()); @@ -518,7 +518,7 @@ impl CodeGenerator { code.push(format!("\tlli 0, {}", result_reg)); code.push(format!("{}:", end_label)); } - BinaryOperator::Ne => { + BinaryOperator::NotEqual => { code.push(format!("\tcmp {}, {}", left_reg, right_reg)); code.push(format!("\tlli 1, {}", result_reg)); let end_label = format!("_cmp_end_{}", self.get_unique_label()); @@ -526,7 +526,7 @@ impl CodeGenerator { code.push(format!("\tlli 0, {}", result_reg)); code.push(format!("{}:", end_label)); } - BinaryOperator::Lt => { + BinaryOperator::LessThan => { code.push(format!("\tcmp {}, {}", left_reg, right_reg)); code.push(format!("\tlli 1, {}", result_reg)); let end_label = format!("_cmp_end_{}", self.get_unique_label()); @@ -534,7 +534,7 @@ impl CodeGenerator { code.push(format!("\tlli 0, {}", result_reg)); code.push(format!("{}:", end_label)); } - BinaryOperator::Le => { + BinaryOperator::LessOrEqual => { code.push(format!("\tcmp {}, {}", left_reg, right_reg)); code.push(format!("\tlli 1, {}", result_reg)); let end_label = format!("_cmp_end_{}", self.get_unique_label()); @@ -542,7 +542,7 @@ impl CodeGenerator { code.push(format!("\tlli 0, {}", result_reg)); code.push(format!("{}:", end_label)); } - BinaryOperator::Gt => { + BinaryOperator::GreaterThan => { code.push(format!("\tcmp {}, {}", left_reg, right_reg)); code.push(format!("\tlli 1, {}", result_reg)); let end_label = format!("_cmp_end_{}", self.get_unique_label()); @@ -550,7 +550,7 @@ impl CodeGenerator { code.push(format!("\tlli 0, {}", result_reg)); code.push(format!("{}:", end_label)); } - BinaryOperator::Ge => { + BinaryOperator::GreaterOrEqual => { code.push(format!("\tcmp {}, {}", left_reg, right_reg)); code.push(format!("\tlli 1, {}", result_reg)); let end_label = format!("_cmp_end_{}", self.get_unique_label()); @@ -581,13 +581,6 @@ impl CodeGenerator { arg_regs.push(arg_reg); } - // Save caller-saved registers and track which ones we saved - // old method, inefficient. - // let saved_regs = self.allocator.get_caller_saved_registers(); - // for reg in &saved_regs { - // code.push(format!("\tpush {}", reg)); - // } - // Save caller-saved registers and track which ones we saved let saved_regs = self.allocator.get_caller_saved_registers(); for reg in &saved_regs { @@ -604,9 +597,6 @@ impl CodeGenerator { )); } - // if GLOBAL_METHODS.contains_key(name.name.as_str()) { - // code.push(format!("\tcall {}", - // GLOBAL_METHODS[name.name.as_str()])); } else if self.symbols.contains(&name.name) { // Call local function code.push(format!("\tcall {}", name)); @@ -644,11 +634,6 @@ impl CodeGenerator { } } - // Restore caller-saved registers in reverse order (LIFO) - // for reg in saved_regs.iter().rev() { - // code.push(format!("\tpop {}", reg)); - // } - // Free argument registers for reg in arg_regs { self.allocator.free_temp(reg); @@ -677,7 +662,9 @@ impl CodeGenerator { UnaryOperator::Dereference => { code.push(format!("\tldw {}, {}", operand_reg, result_reg)); } - UnaryOperator::Reference => { + UnaryOperator::AddressOf => { + // ensure the referenced variable is on the stack and return its + // address. let (offset, alloc_code) = self.allocator.free_register(&operand_reg)?; code.extend(alloc_code); @@ -687,6 +674,28 @@ impl CodeGenerator { result_reg )); } + UnaryOperator::SizeOf => { + if let Ok(id) = operand.type_id() { + let size = id.size(); + code.push(format!("\tmov {}, {}", size, result_reg)); + } + } + UnaryOperator::CastAs => {} /* this should be removed once the */ + // semantic analyser can handle it! + UnaryOperator::Increment => { + // prefix increment + code.push(format!("\tmov {}, {}", operand_reg, result_reg)); + code.push(format!("\taddi {}, {}, 1", operand_reg, operand_reg)); + } + UnaryOperator::Decrement => { + // prefix decrement + code.push(format!("\tmov {}, {}", operand_reg, result_reg)); + code.push(format!("\tsubi {}, {}, 1", operand_reg, operand_reg)); + } + UnaryOperator::BitwiseNot => { + code.push(format!("\tnot {}, {}", operand_reg, result_reg)); + } + UnaryOperator::LogicalNot => unimplemented!(), } self.allocator.free_temp(operand_reg); @@ -694,6 +703,8 @@ impl CodeGenerator { } Expression::Empty => Ok((Register::Null, code)), + + _ => unimplemented!(), } } diff --git a/compiler/src/backend/dsa/registers.rs b/compiler/src/backend/dsa/registers.rs index abf5df3..90da055 100644 --- a/compiler/src/backend/dsa/registers.rs +++ b/compiler/src/backend/dsa/registers.rs @@ -132,7 +132,7 @@ impl RegisterAllocator { } // This is a true temporary - safe to free - if reg != Register::Zero { + if !matches!(reg, Register::Zero | Register::Null) { self.in_use.insert(reg, false); } } @@ -141,7 +141,7 @@ impl RegisterAllocator { // Check if this variable is in a register if let Some(location) = self.variable_locations.get(var).cloned() { if let Some(reg) = location.register - && reg != Register::Zero + && !matches!(reg, Register::Zero | Register::Null) { self.register_contents.remove(®); self.in_use.insert(reg, false); diff --git a/compiler/src/frontend/dsc/lexer.rs b/compiler/src/frontend/dsc/lexer.rs index c41a62b..5805f6a 100644 --- a/compiler/src/frontend/dsc/lexer.rs +++ b/compiler/src/frontend/dsc/lexer.rs @@ -16,6 +16,8 @@ pub enum Token { Include, Static, Const, + As, + SizeOf, // Identifiers and literals Identifier(Name), @@ -23,38 +25,68 @@ pub enum Token { Integer(u64), Char(char), - // Symbols - LeftParen, // ( - RightParen, // ) - LeftBrace, // { - RightBrace, // } - Semicolon, // ; - Colon, // : - Comma, // , + // Delimiters + LeftParen, // ( + RightParen, // ) + LeftBrace, // { + RightBrace, // } + LeftBracket, // [ + RightBracket, // ] + Semicolon, // ; + Colon, // : + Comma, // , + Dot, // . + RightArrow, // -> - // Operators - Plus, // + - Minus, // - - Star, // * - Amphersand, // & - Slash, // / - Assign, // = + // Arithmetic operators + Plus, // + + Minus, // - + Star, // * + Slash, // / + Percent, // % + PlusPlus, // ++ + MinusMinus, // -- + + // Bitwise operators + Ampersand, // & + Pipe, // | + Caret, // ^ + Tilde, // ~ + LeftShift, // << + RightShift, // >> + + // Logical operators + Bang, // ! + LogicalAnd, // && + LogicalOr, // || + + // Comparison operators EqualEqual, // == - Bang, // ! BangEqual, // != Less, // < LessEqual, // <= Greater, // > GreaterEqual, // >= - RightArrow, // -> + + // Assignment operators + Assign, // = + PlusEqual, // += + MinusEqual, // -= + StarEqual, // *= + SlashEqual, // /= + PercentEqual, // %= + AndEqual, // &= + OrEqual, // |= + XorEqual, // ^= + ShlEqual, // <<= + ShrEqual, // >>= // Special Eof, } -use std::fmt; - use crate::model::Name; +use std::fmt; impl fmt::Display for Name { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -81,6 +113,7 @@ impl Token { Token::Break => "Break", Token::Return => "Return", Token::Continue => "Continue", + Token::As => "As", Token::Identifier(_) => "Identifier", Token::String(_) => "String", Token::Integer(_) => "UnsignedInt", @@ -89,29 +122,52 @@ impl Token { Token::RightParen => "RightParen", Token::LeftBrace => "LeftBrace", Token::RightBrace => "RightBrace", + Token::LeftBracket => "LeftBracket", + Token::RightBracket => "RightBracket", Token::Semicolon => "Semicolon", Token::Colon => "Colon", Token::Comma => "Comma", + Token::Dot => "Dot", Token::RightArrow => "RightArrow", Token::Plus => "Plus", Token::Minus => "Minus", Token::Star => "Star", - Token::Amphersand => "Amphersand", Token::Slash => "Slash", - Token::Assign => "Assign", - Token::EqualEqual => "EqualEqual", + Token::Percent => "Percent", + Token::PlusPlus => "PlusPlus", + Token::MinusMinus => "MinusMinus", + Token::Ampersand => "Ampersand", + Token::Pipe => "Pipe", + Token::Caret => "Caret", + Token::Tilde => "Tilde", + Token::LeftShift => "LeftShift", + Token::RightShift => "RightShift", Token::Bang => "Bang", + Token::LogicalAnd => "LogicalAnd", + Token::LogicalOr => "LogicalOr", + Token::EqualEqual => "EqualEqual", Token::BangEqual => "BangEqual", Token::Less => "Less", Token::LessEqual => "LessEqual", Token::Greater => "Greater", Token::GreaterEqual => "GreaterEqual", + Token::Assign => "Assign", + Token::PlusEqual => "PlusEqual", + Token::MinusEqual => "MinusEqual", + Token::StarEqual => "StarEqual", + Token::SlashEqual => "SlashEqual", + Token::PercentEqual => "PercentEqual", + Token::AndEqual => "AndEqual", + Token::OrEqual => "OrEqual", + Token::XorEqual => "XorEqual", + Token::ShlEqual => "ShlEqual", + Token::ShrEqual => "ShrEqual", + Token::SizeOf => "SizeOf", Token::Eof => "Eof", } } } -#[derive(Debug)] pub struct Lexer<'a> { chars: Peekable>, current: Option, @@ -130,31 +186,59 @@ impl<'a> Lexer<'a> { } } + // ======================================================================== + // Character Navigation + // ======================================================================== + + /// Advance to the next character and return it fn advance(&mut self) -> Option { self.current = self.chars.next(); self.current } - fn peek(&mut self) -> Option<&char> { - self.chars.peek() + /// Peek at the next character without consuming it + fn peek(&mut self) -> Option { + self.chars.peek().copied() } + /// Peek two characters ahead + fn peek_second(&mut self) -> Option { + let mut temp = self.chars.clone(); + temp.next(); // Skip the first peek + temp.next() + } + + /// Check if the next character matches expected, and consume it if so + fn match_next(&mut self, expected: char) -> bool { + if self.peek() == Some(expected) { + self.advance(); + true + } else { + false + } + } + + // ======================================================================== + // Whitespace and Comments + // ======================================================================== + fn skip_whitespace(&mut self) { while let Some(c) = self.current { - if !c.is_whitespace() { + if c.is_whitespace() { + if c == '\n' { + self.line += 1; + } + self.advance(); + } else { break; } - if c == '\n' { - self.line += 1; - } - self.advance(); } } fn skip_line_comment(&mut self) { - // Skip the two slashes - self.advance(); // first / - self.advance(); // second / + // We're at the first '/', advance past '//' + self.advance(); // consume first '/' + self.advance(); // consume second '/' // Skip until newline or EOF while let Some(c) = self.current { @@ -168,26 +252,22 @@ impl<'a> Lexer<'a> { } fn skip_block_comment(&mut self) -> Result<(), String> { - // Skip the /* - self.advance(); // / - self.advance(); // * - let start_line = self.line; - // Look for */ + // We're at '/', advance past '/*' + self.advance(); // consume '/' + self.advance(); // consume '*' + + // Look for closing '*/' while let Some(c) = self.current { if c == '\n' { self.line += 1; } - if c == '*' { - if let Some(&next) = self.peek() { - if next == '/' { - self.advance(); // * - self.advance(); // / - return Ok(()); - } - } + if c == '*' && self.peek() == Some('/') { + self.advance(); // consume '*' + self.advance(); // consume '/' + return Ok(()); } self.advance(); @@ -204,21 +284,19 @@ impl<'a> Lexer<'a> { self.skip_whitespace(); // Check for comments - if let Some('/') = self.current { - if let Some(&next) = self.peek() { - match next { - '/' => { - self.skip_line_comment(); - continue; - } - '*' => { - if let Err(e) = self.skip_block_comment() { - eprintln!("Lexer error: {}", e); - } - continue; - } - _ => break, + if self.current == Some('/') { + match self.peek() { + Some('/') => { + self.skip_line_comment(); + continue; } + Some('*') => { + if let Err(e) = self.skip_block_comment() { + self.error(&e); + } + continue; + } + _ => break, } } @@ -226,18 +304,20 @@ impl<'a> Lexer<'a> { } } + // ======================================================================== + // Identifiers and Keywords + // ======================================================================== + fn read_identifier(&mut self) -> String { let mut ident = String::new(); - // Include the current character if it's valid + // Include the current character (already validated as alphabetic or '_') if let Some(c) = self.current { - if c.is_alphabetic() || c == '_' { - ident.push(c); - } + ident.push(c); } - // Read remaining characters - while let Some(&c) = self.peek() { + // Read remaining alphanumeric or underscore characters + while let Some(c) = self.peek() { if c.is_alphanumeric() || c == '_' { self.advance(); ident.push(c); @@ -249,89 +329,93 @@ impl<'a> Lexer<'a> { ident } - fn keyword_or_identifier(&mut self) -> Token { - let first_ident = self.read_identifier(); + fn scan_identifier_or_keyword(&mut self) -> Token { + let first_part = self.read_identifier(); - // Check if it's a keyword first (keywords can't have namespaces) - let keyword = match first_ident.as_str() { + // Check if it's a keyword (keywords cannot have namespaces) + if let Some(keyword) = self.match_keyword(&first_part) { + return keyword; + } + + // Check for namespace separator '::' + if self.peek() == Some(':') && self.peek_second() == Some(':') { + // Consume '::' + self.advance(); // consume first ':' + self.advance(); // consume second ':' + self.advance(); // move to the first character of the next identifier + + // Read the second part (the actual name) + let second_part = self.read_identifier(); + + return Token::Identifier(Name { + namespace: Some(first_part), + name: second_part, + }); + } + + // Plain identifier without namespace + Token::Identifier(Name { + namespace: None, + name: first_part, + }) + } + + fn match_keyword(&self, word: &str) -> Option { + match word { "fn" => Some(Token::Fn), + "let" => Some(Token::Let), "if" => Some(Token::If), "else" => Some(Token::Else), - "while" => Some(Token::While), "loop" => Some(Token::Loop), + "while" => Some(Token::While), "break" => Some(Token::Break), "return" => Some(Token::Return), "continue" => Some(Token::Continue), "include" => Some(Token::Include), - "let" => Some(Token::Let), "const" => Some(Token::Const), "static" => Some(Token::Static), + "as" => Some(Token::As), + "sizeof" => Some(Token::SizeOf), _ => None, - }; - - if let Some(kw) = keyword { - return kw; } + } - // Not a keyword - check for namespace separator (::) - // We need to peek TWO characters ahead without consuming anything - if let Some(&':') = self.peek() { - // We see one colon, but we need to check if there's another one after it - // We can't peek two ahead directly, so we need a different approach + // ======================================================================== + // Numbers + // ======================================================================== - // Save the current position by using a temporary peekable iterator - // Actually, we can't do that easily. Instead, let's just check: - // If we see ':', temporarily advance and check the next char - - // Create a temporary check - let mut temp_chars = self.chars.clone(); - let _ = temp_chars.next(); // This is the ':' we already saw - let second_peek = temp_chars.peek(); - - if let Some(&':') = second_peek { - // It's :: - consume both colons - self.advance(); // consume first : - self.advance(); // consume second : - - // Read the second identifier (the actual name) - let second_ident = self.read_identifier(); - - // Return namespaced identifier - return Token::Identifier(Name { - namespace: Some(first_ident), - name: second_ident, - }); + fn scan_number(&mut self) -> Token { + match self.read_number() { + Ok(num) => Token::Integer(num), + Err(e) => { + self.error(&e); + // Skip the invalid number + while let Some(c) = self.peek() { + if !c.is_alphanumeric() && c != '_' { + break; + } + self.advance(); + } + Token::Integer(0) } - // else: It's a single colon (type annotation) - DON'T consume it - // Just fall through and return the identifier } - - // No namespace separator - just a regular identifier - Token::Identifier(Name { - namespace: None, - name: first_ident, - }) } fn read_number(&mut self) -> Result { - let current = self.current.unwrap(); - // Check for hex (0x) or binary (0b) prefix - if current == '0' { - if let Some(&next_char) = self.peek() { - match next_char { - 'x' | 'X' => { - self.advance(); // consume '0' - self.advance(); // consume 'x' - return self.read_hex_number(); - } - 'b' | 'B' => { - self.advance(); // consume '0' - self.advance(); // consume 'b' - return self.read_binary_number(); - } - _ => {} + if self.current == Some('0') { + match self.peek() { + Some('x') | Some('X') => { + self.advance(); // consume '0' + self.advance(); // consume 'x' + return self.read_hex_number(); } + Some('b') | Some('B') => { + self.advance(); // consume '0' + self.advance(); // consume 'b' + return self.read_binary_number(); + } + _ => {} } } @@ -346,10 +430,13 @@ impl<'a> Lexer<'a> { num_str.push(c); } - while let Some(&c) = self.peek() { + while let Some(c) = self.peek() { if c.is_ascii_digit() { self.advance(); num_str.push(c); + } else if c == '_' { + // Allow underscores as separators (like Rust) + self.advance(); } else { break; } @@ -363,17 +450,19 @@ impl<'a> Lexer<'a> { fn read_hex_number(&mut self) -> Result { let mut num_str = String::new(); - // Read current character if it's a hex digit + // Read the first hex digit (current character) if let Some(c) = self.current { if c.is_ascii_hexdigit() { num_str.push(c); } } - while let Some(&c) = self.peek() { + while let Some(c) = self.peek() { if c.is_ascii_hexdigit() { self.advance(); num_str.push(c); + } else if c == '_' { + self.advance(); // Allow underscores as separators } else { break; } @@ -390,17 +479,19 @@ impl<'a> Lexer<'a> { fn read_binary_number(&mut self) -> Result { let mut num_str = String::new(); - // Read current character if it's a binary digit + // Read the first binary digit (current character) if let Some(c) = self.current { if c == '0' || c == '1' { num_str.push(c); } } - while let Some(&c) = self.peek() { + while let Some(c) = self.peek() { if c == '0' || c == '1' { self.advance(); num_str.push(c); + } else if c == '_' { + self.advance(); // Allow underscores as separators } else { break; } @@ -414,6 +505,27 @@ impl<'a> Lexer<'a> { .map_err(|_| format!("Invalid binary number: {}", num_str)) } + // ======================================================================== + // String and Character Literals + // ======================================================================== + + fn scan_string(&mut self) -> Token { + match self.read_string() { + Ok(s) => Token::String(s), + Err(e) => { + self.error(&e); + // Skip to the end of the string or newline + while let Some(c) = self.current { + if c == '"' || c == '\n' { + break; + } + self.advance(); + } + Token::String(String::new()) + } + } + } + fn read_string(&mut self) -> Result { self.advance(); // Skip the opening quote let mut s = String::new(); @@ -423,6 +535,10 @@ impl<'a> Lexer<'a> { return Ok(s); } + if c == '\n' { + return Err("Unterminated string literal (newline)".to_string()); + } + // Handle escape sequences if c == '\\' { self.advance(); @@ -433,7 +549,14 @@ impl<'a> Lexer<'a> { 'r' => '\r', '\\' => '\\', '"' => '"', - _ => escaped, // For now, just use the character as-is + '\'' => '\'', + '0' => '\0', + _ => { + return Err(format!( + "Invalid escape sequence: \\{}", + escaped + )); + } }; s.push(escaped_char); } else { @@ -449,81 +572,213 @@ impl<'a> Lexer<'a> { Err("Unterminated string literal".to_string()) } - fn match_next(&mut self, expected: char) -> bool { - match self.peek() { - Some(&c) if c == expected => { - self.advance(); - true - } - _ => false, - } - } - - fn scan_single_char_token(&mut self, c: char) -> Option { - match c { - '(' => Some(Token::LeftParen), - ')' => Some(Token::RightParen), - '{' => Some(Token::LeftBrace), - '}' => Some(Token::RightBrace), - ';' => Some(Token::Semicolon), - ',' => Some(Token::Comma), - '&' => Some(Token::Amphersand), - '+' => Some(Token::Plus), - '*' => Some(Token::Star), - _ => None, - } - } - - fn scan_operator(&mut self, c: char) -> Option { - match c { - '-' => Some(if self.match_next('>') { - Token::RightArrow - } else { - Token::Minus - }), - '!' => Some(if self.match_next('=') { - Token::BangEqual - } else { - Token::Bang - }), - '=' => Some(if self.match_next('=') { - Token::EqualEqual - } else { - Token::Assign - }), - '<' => Some(if self.match_next('=') { - Token::LessEqual - } else { - Token::Less - }), - '>' => Some(if self.match_next('=') { - Token::GreaterEqual - } else { - Token::Greater - }), - ':' => { - // Single colon (for type annotations) - // Note: :: is handled in keyword_or_identifier for namespaces - Some(Token::Colon) - } - '/' => { - // Check if it's a comment or division - if let Some(&next) = self.peek() { - if next == '/' || next == '*' { - // It's a comment, don't consume it here - // Let skip_whitespace_and_comments handle it - None - } else { - Some(Token::Slash) + fn scan_char(&mut self) -> Token { + match self.read_char() { + Ok(ch) => Token::Char(ch), + Err(e) => { + self.error(&e); + // Skip to the end of the char literal + while let Some(c) = self.current { + if c == '\'' || c == '\n' { + break; + } + self.advance(); + } + Token::Char('\0') + } + } + } + + fn read_char(&mut self) -> Result { + self.advance(); // Skip opening quote + + let ch = match self.current { + Some('\\') => { + // Handle escape sequences + self.advance(); + match self.current { + Some('n') => '\n', + Some('t') => '\t', + Some('r') => '\r', + Some('\\') => '\\', + Some('\'') => '\'', + Some('"') => '"', + Some('0') => '\0', + Some(c) => return Err(format!("Invalid escape sequence: \\{}", c)), + None => { + return Err( + "Unexpected end after escape in char literal".to_string() + ); } - } else { - Some(Token::Slash) } } - _ => None, + Some('\'') => return Err("Empty character literal".to_string()), + Some('\n') => return Err("Unterminated character literal".to_string()), + Some(c) => c, + None => return Err("Unterminated character literal".to_string()), + }; + + self.advance(); // Move to closing quote + + if self.current != Some('\'') { + return Err( + "Character literal must contain exactly one character".to_string() + ); + } + + Ok(ch) + } + + // ======================================================================== + // Operators and Punctuation + // ======================================================================== + + fn scan_operator(&mut self, c: char) -> Token { + match c { + // Single-character tokens that can't be extended + '(' => Token::LeftParen, + ')' => Token::RightParen, + '{' => Token::LeftBrace, + '}' => Token::RightBrace, + '[' => Token::LeftBracket, + ']' => Token::RightBracket, + ';' => Token::Semicolon, + ',' => Token::Comma, + '.' => Token::Dot, + '~' => Token::Tilde, + ':' => Token::Colon, // '::' is handled in identifier scanning + + // Operators that may have compound forms + '+' => { + if self.match_next('+') { + Token::PlusPlus + } else if self.match_next('=') { + Token::PlusEqual + } else { + Token::Plus + } + } + + '-' => { + if self.match_next('-') { + Token::MinusMinus + } else if self.match_next('>') { + Token::RightArrow + } else if self.match_next('=') { + Token::MinusEqual + } else { + Token::Minus + } + } + + '*' => { + if self.match_next('=') { + Token::StarEqual + } else { + Token::Star + } + } + + '/' => { + // Comments are handled in skip_whitespace_and_comments + if self.match_next('=') { + Token::SlashEqual + } else { + Token::Slash + } + } + + '%' => { + if self.match_next('=') { + Token::PercentEqual + } else { + Token::Percent + } + } + + '&' => { + if self.match_next('&') { + Token::LogicalAnd + } else if self.match_next('=') { + Token::AndEqual + } else { + Token::Ampersand + } + } + + '|' => { + if self.match_next('|') { + Token::LogicalOr + } else if self.match_next('=') { + Token::OrEqual + } else { + Token::Pipe + } + } + + '^' => { + if self.match_next('=') { + Token::XorEqual + } else { + Token::Caret + } + } + + '!' => { + if self.match_next('=') { + Token::BangEqual + } else { + Token::Bang + } + } + + '=' => { + if self.match_next('=') { + Token::EqualEqual + } else { + Token::Assign + } + } + + '<' => { + if self.match_next('<') { + if self.match_next('=') { + Token::ShlEqual + } else { + Token::LeftShift + } + } else if self.match_next('=') { + Token::LessEqual + } else { + Token::Less + } + } + + '>' => { + if self.match_next('>') { + if self.match_next('=') { + Token::ShrEqual + } else { + Token::RightShift + } + } else if self.match_next('=') { + Token::GreaterEqual + } else { + Token::Greater + } + } + + _ => { + self.error(&format!("Unexpected character: '{}'", c)); + Token::Eof // This shouldn't happen + } } } + // ======================================================================== + // Main Token Scanning + // ======================================================================== + pub fn next_token(&mut self) -> Token { self.skip_whitespace_and_comments(); @@ -531,90 +786,40 @@ impl<'a> Lexer<'a> { return Token::Eof; }; - // Try single-character tokens first - if let Some(token) = self.scan_single_char_token(c) { - self.advance(); - return token; - } + let token = match c { + // Identifiers and keywords + 'a'..='z' | 'A'..='Z' | '_' => self.scan_identifier_or_keyword(), - // Try operators (may be multi-character) - if let Some(token) = self.scan_operator(c) { - self.advance(); - return token; - } + // Numbers + '0'..='9' => self.scan_number(), - // Char literals - if c == '\'' { - let mut value = ' '; - self.advance(); - if let Some(ch) = self.current { - value = ch; - self.advance(); - } - if self.current == Some('\'') { - self.advance(); - return Token::Char(value); - } - eprintln!("Lexer error on line {}: Invalid char literal", self.line); - } + // String literals + '"' => self.scan_string(), - // String literals - if c == '"' { - let token = match self.read_string() { - Ok(s) => Token::String(s), - Err(e) => { - eprintln!("Lexer error on line {}: {}", self.line, e); - // Skip to next quote or end - while let Some(ch) = self.current { - if ch == '"' || ch == '\n' { - break; - } - self.advance(); - } - Token::String(String::new()) - } - }; - self.advance(); - return token; - } + // Character literals + '\'' => self.scan_char(), - // Identifiers and keywords (including namespaced identifiers) - if c.is_alphabetic() || c == '_' { - let token = self.keyword_or_identifier(); - self.advance(); - return token; - } + // Operators and punctuation + _ => self.scan_operator(c), + }; - // Numbers (decimal, hex, binary) - if c.is_ascii_digit() { - let token = match self.read_number() { - Ok(num) => Token::Integer(num), - Err(e) => { - eprintln!("Lexer error on line {}: {}", self.line, e); - // Skip invalid number - while let Some(&ch) = self.peek() { - if !ch.is_alphanumeric() { - break; - } - self.advance(); - } - Token::Integer(0) - } - }; - self.advance(); - return token; - } - - // Unknown character - skip it - eprintln!( - "Lexer warning on line {}: Skipping unknown character '{}'", - self.line, c - ); self.advance(); - self.next_token() + token + } + + // ======================================================================== + // Error Handling + // ======================================================================== + + fn error(&self, message: &str) { + eprintln!("Lexer error on line {}: {}", self.line, message); } } +// ======================================================================== +// Iterator Implementation +// ======================================================================== + impl<'a> Iterator for Lexer<'a> { type Item = Token; @@ -625,3 +830,78 @@ impl<'a> Iterator for Lexer<'a> { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_operators() { + let input = "+ ++ += - -- -= * *= / /= % %= & &= && | |= || ^ ^= ! != = == < <= << <<= > >= >> >>="; + let mut lexer = Lexer::new(input); + + let expected = vec![ + Token::Plus, + Token::PlusPlus, + Token::PlusEqual, + Token::Minus, + Token::MinusMinus, + Token::MinusEqual, + Token::Star, + Token::StarEqual, + Token::Slash, + Token::SlashEqual, + Token::Percent, + Token::PercentEqual, + Token::Ampersand, + Token::AndEqual, + Token::LogicalAnd, + Token::Pipe, + Token::OrEqual, + Token::LogicalOr, + Token::Caret, + Token::XorEqual, + Token::Bang, + Token::BangEqual, + Token::Assign, + Token::EqualEqual, + Token::Less, + Token::LessEqual, + Token::LeftShift, + Token::ShlEqual, + Token::Greater, + Token::GreaterEqual, + Token::RightShift, + Token::ShrEqual, + ]; + + for expected_token in expected { + assert_eq!(lexer.next_token(), expected_token); + } + } + + #[test] + fn test_numbers() { + let input = "42 0x2A 0b101010 123_456"; + let mut lexer = Lexer::new(input); + + assert_eq!(lexer.next_token(), Token::Integer(42)); + assert_eq!(lexer.next_token(), Token::Integer(42)); + assert_eq!(lexer.next_token(), Token::Integer(42)); + assert_eq!(lexer.next_token(), Token::Integer(123456)); + } + + #[test] + fn test_namespaced_identifier() { + let input = "print::println std::io::read"; + let mut lexer = Lexer::new(input); + + let first = lexer.next_token(); + if let Token::Identifier(name) = first { + assert_eq!(name.namespace, Some("print".to_string())); + assert_eq!(name.name, "println"); + } else { + panic!("Expected namespaced identifier"); + } + } +} diff --git a/compiler/src/frontend/dsc/parser.rs b/compiler/src/frontend/dsc/parser.rs index 2fa533b..a922a8e 100644 --- a/compiler/src/frontend/dsc/parser.rs +++ b/compiler/src/frontend/dsc/parser.rs @@ -353,12 +353,12 @@ impl Parser { let mut expr = self.parse_additive()?; while let Some(op) = match self.peek_next()? { - Token::EqualEqual => Some(BinaryOperator::Eq), - Token::BangEqual => Some(BinaryOperator::Ne), - Token::Less => Some(BinaryOperator::Lt), - Token::Greater => Some(BinaryOperator::Gt), - Token::LessEqual => Some(BinaryOperator::Le), - Token::GreaterEqual => Some(BinaryOperator::Ge), + Token::EqualEqual => Some(BinaryOperator::Equal), + Token::BangEqual => Some(BinaryOperator::NotEqual), + Token::Less => Some(BinaryOperator::LessThan), + Token::Greater => Some(BinaryOperator::GreaterThan), + Token::LessEqual => Some(BinaryOperator::LessOrEqual), + Token::GreaterEqual => Some(BinaryOperator::GreaterOrEqual), _ => None, } { self.next()?; @@ -412,11 +412,27 @@ impl Parser { fn parse_unary(&mut self) -> ParseResult { let op = match self.peek_next()? { + // prefix inc/dec + Token::PlusPlus => UnaryOperator::Increment, + Token::MinusMinus => UnaryOperator::Decrement, + + // arithmetic Token::Plus => UnaryOperator::Plus, Token::Minus => UnaryOperator::Minus, + + // pointer Token::Star => UnaryOperator::Dereference, - Token::Amphersand => UnaryOperator::Reference, - _ => return ParseResult::Accept(self.parse_primary()?), + Token::Ampersand => UnaryOperator::AddressOf, + + // boolean + Token::Bang => UnaryOperator::LogicalNot, + Token::Tilde => UnaryOperator::BitwiseNot, + + Token::SizeOf => UnaryOperator::SizeOf, + _ => { + let expr = self.parse_primary()?; + return self.parse_postfix(expr); + } }; self.next()?; @@ -428,6 +444,99 @@ impl Parser { }) } + fn parse_postfix( + &mut self, + mut expr: Expression, + ) -> ParseResult { + loop { + match self.peek_next()? { + // Type cast: expr as Type + Token::As => { + self.next()?; // consume 'as' + let target_type = self.parse_type()?; + expr = Expression::TypeCast { + expr: Box::new(expr), + target_type, + type_id: None, + }; + } + + // Postfix increment/decrement + Token::PlusPlus => { + self.next()?; + expr = Expression::UnaryPostfix { + op: UnaryOperator::Increment, + operand: Box::new(expr), + type_id: None, + }; + } + Token::MinusMinus => { + self.next()?; + expr = Expression::UnaryPostfix { + op: UnaryOperator::Decrement, + operand: Box::new(expr), + type_id: None, + }; + } + + // Array indexing: expr[index] + Token::LeftBracket => { + self.next()?; // consume '[' + let index = Box::new(self.parse_expression()?); + + let _ = expect_tt!(self.next()?, RightBracket)?; + + expr = Expression::IndexAccess { + expr: Box::new(expr), + index, + type_id: None, + }; + } + + // Function call: expr(args...) + Token::LeftParen => { + self.next()?; // consume '(' + let mut args = Vec::new(); + + if !matches!(self.peek_next()?, Token::RightParen) { + loop { + args.push(self.parse_expression()?); + if !matches!(self.peek_next()?, Token::Comma) { + break; + } + self.next()?; // consume comma + } + } + + let _ = expect_tt!(self.next()?, RightParen)?; + + if let Expression::Variable { name, .. } = expr { + expr = Expression::Call { + func: Call { name, args }, + type_id: None, + }; + } + } + + // Member access: expr.member (if you support structs) + Token::Dot => { + self.next()?; + let field_name = expect_value!(self.next()?, Identifier)?; + expr = Expression::MemberAccess { + expr: Box::new(expr), + field_name, + type_id: None, + }; + } + + // No more postfix operations + _ => break, + } + } + + ParseResult::Accept(expr) + } + fn parse_primary(&mut self) -> ParseResult { match self.peek_next()? { Token::Integer(value) => { @@ -441,39 +550,37 @@ impl Parser { self.next()?; ParseResult::Accept(Expression::StringLiteral(value)) } - Token::Identifier(_) => { - let name = expect_value!(self.next()?, Identifier)?; + Token::Char(value) => { + self.next()?; + ParseResult::Accept(Expression::CharLiteral(value)) + } - if matches!(self.peek_next()?, Token::LeftParen) { - // Function call - self.next()?; - let mut args = Vec::new(); + Token::Identifier(name) => { + self.next()?; + ParseResult::Accept(Expression::Variable { + name, + expr_type: None, + }) + } + Token::LeftBracket => { + self.next()?; // consume '[' + let mut elements = Vec::new(); - if !matches!(self.peek_next()?, Token::RightParen) { - args.push(self.parse_expression()?); - - while matches!(self.peek_next()?, Token::Comma) { - self.next()?; - args.push(self.parse_expression()?); + if !matches!(self.peek_next()?, Token::RightBracket) { + loop { + elements.push(self.parse_expression()?); + if !matches!(self.peek_next()?, Token::Comma) { + break; } + self.next()?; // consume comma } - - let _ = expect_tt!(self.next()?, RightParen)?; - - ParseResult::Accept(Expression::Call { - func: Call { - name: name.clone(), - args, - }, - - type_id: None, - }) - } else { - ParseResult::Accept(Expression::Variable { - name, - expr_type: None, - }) } + + expect_tt!(self.next()?, RightBracket)?; + ParseResult::Accept(Expression::ArrayLiteral { + elements, + type_id: None, + }) } Token::LeftParen => { self.next()?; diff --git a/compiler/src/model.rs b/compiler/src/model.rs index df1269f..a1abd87 100644 --- a/compiler/src/model.rs +++ b/compiler/src/model.rs @@ -65,6 +65,26 @@ pub enum TypeId { Struct { name: Name, fields: Vec }, } +impl TypeId { + pub fn size(&self) -> usize { + match self { + Self::U8 => 1, + Self::U16 => 2, + Self::U32 => 4, + Self::I8 => 1, + Self::I16 => 2, + Self::I32 => 4, + Self::Bool => 1, + Self::Char => 1, + Self::Void => 0, + Self::Ptr(t) => t.size(), + Self::Ref(t) => t.size(), + Self::Array(t, size) => t.size() * size, + Self::Struct { fields, .. } => fields.iter().map(|t| t.size()).sum(), + } + } +} + impl fmt::Display for TypeId { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { @@ -169,10 +189,38 @@ pub enum Expression { // Post-Semantic Analysis type_id: Option, }, + UnaryPostfix { + op: UnaryOperator, + operand: Box, + + // Post-Semantic Analysis + type_id: Option, + }, Variable { name: Name, expr_type: Option, }, + TypeCast { + expr: Box, + target_type: TypeId, + + // Post-Semantic Analysis + type_id: Option, + }, + IndexAccess { + expr: Box, + index: Box, + + // Post-Semantic Analysis + type_id: Option, + }, + MemberAccess { + expr: Box, + field_name: Name, + + // Post-Semantic Analysis + type_id: Option, + }, Call { func: Call, @@ -187,6 +235,10 @@ pub enum Expression { }, StringLiteral(String), CharLiteral(char), + ArrayLiteral { + elements: Vec, + type_id: Option, + }, } #[derive(Debug, Clone)] @@ -204,8 +256,17 @@ impl Expression { Expression::Call { .. } => false, Expression::Binary { left, right, .. } => left.is_pure() && right.is_pure(), Expression::Unary { operand, .. } => operand.is_pure(), + Expression::UnaryPostfix { operand, .. } => operand.is_pure(), Expression::Empty => true, Expression::Variable { .. } => true, + Expression::TypeCast { expr, .. } => expr.is_pure(), + Expression::IndexAccess { expr, index, .. } => { + expr.is_pure() && index.is_pure() + } + Expression::MemberAccess { expr, .. } => expr.is_pure(), + Expression::ArrayLiteral { elements, type_id } => { + elements.iter().all(|element| element.is_pure()) + } } } @@ -225,10 +286,24 @@ impl Expression { Expression::Unary { type_id, .. } => { type_id.clone().ok_or(CompilerError::UnknownType) } + Expression::UnaryPostfix { type_id, .. } => { + type_id.clone().ok_or(CompilerError::UnknownType) + } Expression::Empty => Ok(TypeId::Void), Expression::Variable { expr_type, .. } => { expr_type.clone().ok_or(CompilerError::UnknownType) } + Expression::TypeCast { type_id, .. } => { + type_id.clone().ok_or(CompilerError::UnknownType) + } + Expression::IndexAccess { expr, .. } => expr.type_id(), + Expression::MemberAccess { expr, .. } => expr.type_id(), + Expression::ArrayLiteral { elements, .. } => { + let element_type = elements + .first() + .map_or(TypeId::Void, |e| e.type_id().unwrap_or(TypeId::Void)); + Ok(TypeId::Array(Box::new(element_type), elements.len())) + } } } } @@ -236,31 +311,56 @@ impl Expression { #[allow(unused)] #[derive(Debug, Clone, PartialEq)] pub enum BinaryOperator { + // arithmetic Add, Sub, Mul, Div, - Eq, - Ne, - Lt, - Gt, - Le, - Ge, + Mod, + + // comparison + Equal, + NotEqual, + LessThan, + GreaterThan, + LessOrEqual, + GreaterOrEqual, + + // bitwise + BitwiseAnd, + BitwiseOr, + BitwiseXor, + + // logical + LogicalAnd, + LogicalOr, + + // shift + LeftShift, + RightShift, } impl fmt::Display for BinaryOperator { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - BinaryOperator::Add => write!(f, "+"), - BinaryOperator::Sub => write!(f, "-"), - BinaryOperator::Mul => write!(f, "*"), - BinaryOperator::Div => write!(f, "/"), - BinaryOperator::Eq => write!(f, "=="), - BinaryOperator::Ne => write!(f, "!="), - BinaryOperator::Lt => write!(f, "<"), - BinaryOperator::Gt => write!(f, ">"), - BinaryOperator::Le => write!(f, "<="), - BinaryOperator::Ge => write!(f, ">="), + Self::Add => write!(f, "+"), + Self::Sub => write!(f, "-"), + Self::Mul => write!(f, "*"), + Self::Div => write!(f, "/"), + Self::Mod => write!(f, "%"), + Self::Equal => write!(f, "=="), + Self::NotEqual => write!(f, "!="), + Self::LessThan => write!(f, "<"), + Self::GreaterThan => write!(f, ">"), + Self::LessOrEqual => write!(f, "<="), + Self::GreaterOrEqual => write!(f, ">="), + Self::BitwiseAnd => write!(f, "&"), + Self::BitwiseOr => write!(f, "|"), + Self::BitwiseXor => write!(f, "^"), + Self::LogicalAnd => write!(f, "&&"), + Self::LogicalOr => write!(f, "||"), + Self::LeftShift => write!(f, "<<"), + Self::RightShift => write!(f, ">>"), } } } @@ -269,17 +369,29 @@ impl fmt::Display for BinaryOperator { pub enum UnaryOperator { Plus, Minus, - Reference, + AddressOf, Dereference, + CastAs, + BitwiseNot, + LogicalNot, + Increment, + Decrement, + SizeOf, } impl fmt::Display for UnaryOperator { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - UnaryOperator::Plus => write!(f, "+"), - UnaryOperator::Minus => write!(f, "-"), - UnaryOperator::Dereference => write!(f, "*"), - UnaryOperator::Reference => write!(f, "&"), + Self::Increment => write!(f, "++"), + Self::Decrement => write!(f, "--"), + Self::Plus => write!(f, "+"), + Self::Minus => write!(f, "-"), + Self::Dereference => write!(f, "*"), + Self::AddressOf => write!(f, "&"), + Self::CastAs => write!(f, "as"), + Self::BitwiseNot => write!(f, "~"), + Self::LogicalNot => write!(f, "!"), + Self::SizeOf => write!(f, "sizeof"), } } } -- 2.47.3 From f7ed764e96ba75d06ad3ee41f1140e43f2414e1e Mon Sep 17 00:00:00 2001 From: zxq5 Date: Mon, 9 Feb 2026 00:04:19 +0000 Subject: [PATCH 40/53] renamed NoReg to Null in common --- common/src/instructions.rs | 10 +++++----- common/src/instructions/encode.rs | 6 +++--- common/src/instructions/encode/tests.rs | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/common/src/instructions.rs b/common/src/instructions.rs index 032e4ae..c3999ff 100644 --- a/common/src/instructions.rs +++ b/common/src/instructions.rs @@ -69,7 +69,7 @@ pub enum Register { Idr, Mmr, Zero, - NoReg, + Null, // Invalid - Triggers a fault if accessed // system registers - can't be written to by instructions. Mar, @@ -106,7 +106,7 @@ impl Register { impl Default for Register { fn default() -> Self { - Self::NoReg + Self::Null } } @@ -144,7 +144,7 @@ impl TryFrom for Register { 0x14 => Self::Idr, 0x15 => Self::Mmr, 0x16 => Self::Zero, - 0x17 => Self::NoReg, + 0x17 => Self::Null, 0x18 => Self::Mar, 0x19 => Self::Mdr, 0x1A => Self::Sts, @@ -183,7 +183,7 @@ impl TryFrom<&str> for Register { "idr" => Ok(Self::Idr), "mmr" => Ok(Self::Mmr), "zero" => Ok(Self::Zero), - "null" => Ok(Self::NoReg), + "null" => Ok(Self::Null), "pcx" => Ok(Self::Pcx), _ => Err(RegisterParseError::InvalidName(value.to_string())), } @@ -216,7 +216,7 @@ impl std::fmt::Display for Register { Self::Idr => write!(f, "idr"), Self::Mmr => write!(f, "mmr"), Self::Zero => write!(f, "zero"), - Self::NoReg => write!(f, "noreg"), + Self::Null => write!(f, "null"), Self::Mar => write!(f, "mar"), Self::Mdr => write!(f, "mdr"), Self::Sts => write!(f, "sts"), diff --git a/common/src/instructions/encode.rs b/common/src/instructions/encode.rs index 7b11a55..0a02e7a 100644 --- a/common/src/instructions/encode.rs +++ b/common/src/instructions/encode.rs @@ -8,9 +8,9 @@ pub trait Encode { /// Encodes a zero argument instruction. fn encode_no_args(opcode: u8) -> u32 { let opcode = u32::from(opcode); - let sr1 = Register::NoReg as u32; - let sr2 = Register::NoReg as u32; - let dr = Register::NoReg as u32; + let sr1 = Register::Null as u32; + let sr2 = Register::Null as u32; + let dr = Register::Null as u32; let shamt = 0; (opcode << 26) | (sr1 << 21) | (sr2 << 16) | (dr << 11) | (shamt << 6) diff --git a/common/src/instructions/encode/tests.rs b/common/src/instructions/encode/tests.rs index e3bc63c..17707c2 100644 --- a/common/src/instructions/encode/tests.rs +++ b/common/src/instructions/encode/tests.rs @@ -2,7 +2,7 @@ use crate::prelude::*; #[test] fn test_encode_nop() { - let no_reg = Register::NoReg as u32; + let no_reg = Register::Null as u32; let no_op = u32::from(Instruction::Nop.opcode()); let expected = (no_op << 26) | (no_reg << 21) | (no_reg << 16) | (no_reg << 11); @@ -15,7 +15,7 @@ fn test_encode_nop() { fn test_encode_mov() { let rg0 = Register::Rg0 as u32; let rg1 = Register::Rg1 as u32; - let no_reg = Register::NoReg as u32; + let no_reg = Register::Null as u32; let instruction = Instruction::Mov(RTypeArgs::new( Some(Register::Rg0), @@ -53,7 +53,7 @@ fn test_encode_load_byte() { #[test] fn test_encode_shift_left_shamt() { let rg0 = Register::Rg0 as u32; - let no_reg = Register::NoReg as u32; + let no_reg = Register::Null as u32; let shift_amount = 5; @@ -80,7 +80,7 @@ fn test_encode_shift_left_shamt() { fn test_encode_shift_left_reg() { let rg0 = Register::Rg0 as u32; let rg1 = Register::Rg1 as u32; - let no_reg = Register::NoReg as u32; + let no_reg = Register::Null as u32; let instruction = Instruction::ShiftLeft(RTypeArgs::new( Some(Register::Rg0), -- 2.47.3 From e2be83414b872ecc9a9f7b1a68c8efb13ee88082 Mon Sep 17 00:00:00 2001 From: zxq5 Date: Mon, 9 Feb 2026 00:05:45 +0000 Subject: [PATCH 41/53] - updated assembler to support new shift implementation - updated emulator to support new shift implementation - updated emulator to rename NoReg to Null as in the common lib --- assembler/src/assembler/codegen.rs | 24 +++++-- assembler/src/assembler/parser.rs | 63 +++++++++++++++---- emulator/src/emulator/system/model.rs | 4 +- emulator/src/emulator/system/processor/mod.rs | 8 +-- 4 files changed, 72 insertions(+), 27 deletions(-) diff --git a/assembler/src/assembler/codegen.rs b/assembler/src/assembler/codegen.rs index 4d85ffc..a95c093 100644 --- a/assembler/src/assembler/codegen.rs +++ b/assembler/src/assembler/codegen.rs @@ -223,19 +223,31 @@ fn build_shift_instruction( opcode: Opcode, args: &[crate::assembler::model::Token], ) -> Result { - let Some(reg_token) = args.first() else { + let Some(src_reg) = args.first() else { return Err(AssembleError::MissingArgument(0)); }; - let Some(amount_token) = args.get(1) else { + let Some(r_shamt) = args.get(1) else { + return Err(AssembleError::MissingArgument(0)); + }; + let Some(i_shamt) = args.get(2) else { + return Err(AssembleError::MissingArgument(1)); + }; + let Some(dest_reg) = args.get(3) else { return Err(AssembleError::MissingArgument(1)); }; - let reg = expect_token!(reg_token, Register)?; - let amount = expect_token!(amount_token, Immediate)? as u8; + let src = expect_token!(src_reg, Register)?; + let r_shamt = expect_token!(r_shamt, Register)?; + let i_shamt = expect_token!(i_shamt, Immediate)? as u8; + let dest = expect_token!(dest_reg, Register)?; match opcode { - Opcode::Shl => Ok(Instruction::ShiftLeft(args!(R, sr1: reg, shamt: amount))), - Opcode::Shr => Ok(Instruction::ShiftRight(args!(R, sr1: reg, shamt: amount))), + Opcode::Shl => Ok(Instruction::ShiftLeft( + args!(R, sr1: src, sr2: r_shamt, shamt: i_shamt, dr: dest), + )), + Opcode::Shr => Ok(Instruction::ShiftRight( + args!(R, sr1: src, sr2: r_shamt, shamt: i_shamt, dr: dest), + )), _ => unreachable!(), } } diff --git a/assembler/src/assembler/parser.rs b/assembler/src/assembler/parser.rs index 7ac26b2..d8d50a5 100644 --- a/assembler/src/assembler/parser.rs +++ b/assembler/src/assembler/parser.rs @@ -1,5 +1,6 @@ use std::path::{Path, PathBuf}; +use crate::assembler::TokenType; use crate::{assembler::AssembleError, expect_token, expect_type, node}; use crate::assembler::model::{Node, Opcode, Token}; @@ -114,9 +115,10 @@ impl Parser { let mut offset = Token::Immediate(0); if let Ok(next) = self.peek_next() - && expect_type!(next, Immediate).is_ok() { - offset = self.next()?; - } + && expect_type!(next, Immediate).is_ok() + { + offset = self.next()?; + } args = vec![base, dest, offset]; } @@ -125,9 +127,10 @@ impl Parser { let dest = expect_type!(self.next()?, Register, Symbol)?; let mut offset = Token::Immediate(0); if let Ok(next) = self.peek_next() - && expect_type!(next, Immediate).is_ok() { - offset = self.next()?; - } + && expect_type!(next, Immediate).is_ok() + { + offset = self.next()?; + } args = vec![base, dest, offset]; } @@ -146,15 +149,49 @@ impl Parser { } Opcode::Not | Opcode::Cmp => { - let reg1 = expect_type!(self.next()?, Register, Symbol)?; - let reg2 = expect_type!(self.next()?, Register, Symbol)?; - args = vec![reg1, reg2]; + let src = expect_type!(self.next()?, Register, Symbol)?; + let dest = expect_type!(self.next()?, Register, Symbol)?; + args = vec![src, dest]; } - Opcode::Shl | Opcode::Shr => { - let reg = expect_type!(self.next()?, Register, Symbol)?; - let num = expect_type!(self.next()?, Immediate)?; - args = vec![reg, num]; + let src = expect_type!(self.next()?, Register, Symbol)?; + + // First operand after src: could be immediate or register + let first = self.next()?; + + let (r_shamt, i_shamt) = match first { + Token::Register(_) => ( + first, + if let Ok(tok) = self.peek_next() { + if expect_type!(tok, Immediate).is_ok() { + self.next()? + } else { + Token::Immediate(0) + } + } else { + Token::Immediate(0) + }, + ), + Token::Immediate(_) => (Token::Register(Register::Zero), first), + _ => { + return Err(AssembleError::UnexpectedToken( + first, + TokenType::Immediate, + )); + } + }; + + let dest = if let Ok(tok) = self.peek_next() { + if expect_type!(tok, Register).is_ok() { + self.next()? + } else { + src.clone() // Default to src if no dest specified + } + } else { + src.clone() // Default to src if no dest specified + }; + + args = vec![src, r_shamt, i_shamt, dest]; } Opcode::Inc | Opcode::Dec => { diff --git a/emulator/src/emulator/system/model.rs b/emulator/src/emulator/system/model.rs index 2d49a94..5df1891 100644 --- a/emulator/src/emulator/system/model.rs +++ b/emulator/src/emulator/system/model.rs @@ -286,7 +286,7 @@ impl RegFile { Register::Sts => &mut self.sts, Register::Cir => &mut self.cir, Register::Pcx => &mut self.pcx, - _ => return Err(ProcessorError::InvalidRegister(Register::NoReg as u8)), + _ => return Err(ProcessorError::InvalidRegister(Register::Null as u8)), }) } @@ -321,7 +321,7 @@ impl RegFile { Register::Cir => self.cir, Register::Pcx => self.pcx, Register::Zero => 0, - _ => return Err(ProcessorError::InvalidRegister(Register::NoReg as u8)), + _ => return Err(ProcessorError::InvalidRegister(Register::Null as u8)), }) } } diff --git a/emulator/src/emulator/system/processor/mod.rs b/emulator/src/emulator/system/processor/mod.rs index d48b80d..fb50926 100644 --- a/emulator/src/emulator/system/processor/mod.rs +++ b/emulator/src/emulator/system/processor/mod.rs @@ -349,17 +349,13 @@ impl Executable for Instruction { // Left shifts the value in Reg by the given amount (either a register, or a // literal value) Self::ShiftLeft(a) => { - let reg = cpu.get(a.sr1)?; - let val = a.shamt; - *cpu.reg(a.sr1)? = shl(reg, val); + *cpu.reg(a.dr)? = shl(cpu.get(a.sr1)?, a.shamt + cpu.get(a.sr2)? as u8); } // Right shifts the value in Reg by the given amount (either a register, or a // literal value). Self::ShiftRight(a) => { - let regval = cpu.get(a.sr1)?; - let val = a.shamt; - *cpu.reg(a.sr1)? = shr(regval, val); + *cpu.reg(a.dr)? = shr(cpu.get(a.sr1)?, a.shamt + cpu.get(a.sr2)? as u8); } // Adds the value of Src2 to Src1 and writes the result to a.dr -- 2.47.3 From 22241a5633713e8b874f188e3f069e6c481dde93 Mon Sep 17 00:00:00 2001 From: zxq5 Date: Mon, 9 Feb 2026 00:10:37 +0000 Subject: [PATCH 42/53] - implementation of ` = ` type statements such as `x += 5` - implementation of logical and shift operations in parser and codegen. - implementation of sizeof keyword as unary operator in progress (non functional) - implementation of prefix and postfix inc/dec operators - array access by index (implemented, untested as arrays aren't implemented yet). essentially just a pointer write with offset. - struct/member access (parsing implemented, untested.) --- compiler/src/backend/dsa/codegen.rs | 378 ++++++++++++++++++++++------ compiler/src/frontend/dsc/parser.rs | 169 +++++++++++-- compiler/src/model.rs | 19 +- 3 files changed, 457 insertions(+), 109 deletions(-) diff --git a/compiler/src/backend/dsa/codegen.rs b/compiler/src/backend/dsa/codegen.rs index 64fa0a2..7ba886c 100644 --- a/compiler/src/backend/dsa/codegen.rs +++ b/compiler/src/backend/dsa/codegen.rs @@ -9,8 +9,8 @@ use crate::backend::dsa::registers::Register; use crate::{block, comment, dsa}; use crate::model::{ - BinaryOperator, Call, CompilerError, ConstExpr, Declaration, Dependency, Expression, - Program, Statement, TypeId, UnaryOperator, Variable, + AssignmentOperator, BinaryOperator, Call, CompilerError, ConstExpr, Declaration, + Dependency, Expression, Program, Statement, TypeId, UnaryOperator, Variable, }; pub struct CodeGenerator { @@ -271,24 +271,106 @@ impl CodeGenerator { self.allocator.free_temp(ptr_reg); } - Statement::Assign { varname, value } => { + Statement::Assign { + varname, + value, + operator, + } => { // Evaluate expression let (result_reg, expr_code) = self.generate_expression(value, true, func_body)?; code.extend(expr_code); + if *operator == AssignmentOperator::Assign { + // Check if this is a global variable + if self.is_global(varname) { + // Store to global label + code.push(format!("\tstw {}, {}", result_reg, varname)); + } else { + // Store result in local variable + let store_code = self.allocator.store_var(varname, &result_reg); + code.extend(store_code); + } + + // Free temporary register + self.allocator.free_temp(result_reg); + + return Ok(code); + } + + // for more complex assignment cases we need an intermediate register. + let (temp_reg, temp_code) = self.allocator.alloc_temp()?; + code.extend(temp_code); + + // fetch the value of the variable + let (var_reg, var_code) = if self.is_global(varname) { + (temp_reg, vec![format!("\tldw {}, {}", varname, temp_reg)]) + } else { + self.allocator.load_var(varname)? + }; + code.extend(var_code); + + let assign_code = match operator { + AssignmentOperator::Assign => { + unreachable!("assignment was already checked earlier.") + } + AssignmentOperator::AddAssign => { + format!("\tadd {var_reg}, {result_reg}, {temp_reg}") + } + AssignmentOperator::SubAssign => { + format!("\tsub {var_reg}, {result_reg}, {temp_reg}") + } + AssignmentOperator::MulAssign => { + return Err(CompilerError::Unimplemented(format!( + "TODO: implement multiplication for assignment" + ))); + } + AssignmentOperator::DivAssign => { + return Err(CompilerError::Unimplemented(format!( + "TODO: write proper div function for DSA" + ))); + } + AssignmentOperator::ModAssign => { + format!("\tmod {var_reg}, {result_reg}, {temp_reg}") + } + AssignmentOperator::AndAssign => { + format!("\tand {var_reg}, {result_reg}, {temp_reg}") + } + AssignmentOperator::OrAssign => { + format!("\tor {var_reg}, {result_reg}, {temp_reg}") + } + AssignmentOperator::XorAssign => { + format!("\txor {var_reg}, {result_reg}, {temp_reg}") + } + AssignmentOperator::LeftShiftAssign => { + // this is only useful if we optimise out the register allocation + // inside value. if let Expression::Number + // { value, .. } = *value { format!("\ + // tshl {var_reg}, {value}, {temp_reg}") } + format!("\tshl {var_reg}, {result_reg}, 0, {temp_reg}") + } + AssignmentOperator::RightShiftAssign => { + // this is only useful if we optimise out the register allocation + // if let Expression::Number { value, .. } = *value { + // format!("\tshr {var_reg}, {value}, {temp_reg}") + // } + format!("\tshr {var_reg}, {result_reg}, 0, {temp_reg}") + } + }; + code.push(assign_code); + // Check if this is a global variable if self.is_global(varname) { // Store to global label - code.push(format!("\tstw {}, {}", result_reg, varname)); + code.push(format!("\tstw {}, {}", temp_reg, varname)); } else { // Store result in local variable - let store_code = self.allocator.store_var(varname, &result_reg); + let store_code = self.allocator.store_var(varname, &temp_reg); code.extend(store_code); } - // Free temporary register self.allocator.free_temp(result_reg); + self.allocator.free_temp(temp_reg); } Statement::Return(expr) => { @@ -414,16 +496,17 @@ impl CodeGenerator { let mut code = Vec::new(); match expr { - Expression::StringLiteral(value) => { + Expression::Empty => Ok((Register::Null, code)), + + Expression::Number { value, .. } => { let (reg, alloc_code) = self.allocator.alloc_temp()?; code.extend(alloc_code); - // write string into memory - let uuid = self.get_unique_label(); - func_body.insert(0, format!("db str_{uuid}: \"{value}\"")); - - // Load pointer to string - code.push(format!("\tlwi str_{uuid}, {reg}")); + // Load immediate value + code.push(format!("\tlli {}, {}", value & 0xFFFF, reg)); + if *value > 0xFFFF || *value < 0 { + code.push(format!("\tlui {}, {}", (value >> 16) & 0xFFFF, reg)); + } Ok((reg, code)) } @@ -438,19 +521,22 @@ impl CodeGenerator { Ok((reg, code)) } - Expression::Number { value, .. } => { + Expression::StringLiteral(value) => { let (reg, alloc_code) = self.allocator.alloc_temp()?; code.extend(alloc_code); - // Load immediate value - code.push(format!("\tlli {}, {}", value & 0xFFFF, reg)); - if *value > 0xFFFF || *value < 0 { - code.push(format!("\tlui {}, {}", (value >> 16) & 0xFFFF, reg)); - } + // write string into memory + let uuid = self.get_unique_label(); + func_body.insert(0, format!("db str_{uuid}: \"{value}\"")); + + // Load pointer to string + code.push(format!("\tlwi str_{uuid}, {reg}")); Ok((reg, code)) } + Expression::ArrayLiteral { elements, type_id } => todo!(), + Expression::Variable { name, .. } => { if self.is_global(&name.name) { // Allocate a temporary register for the global @@ -489,16 +575,14 @@ impl CodeGenerator { // Generate operation match op { BinaryOperator::Add => { - code.push(format!( - "\tadd {}, {}, {}", - left_reg, right_reg, result_reg - )); + code.push( + format!("\tadd {left_reg}, {right_reg}, {result_reg}",), + ); } BinaryOperator::Sub => { - code.push(format!( - "\tsub {}, {}, {}", - left_reg, right_reg, result_reg - )); + code.push( + format!("\tsub {left_reg}, {right_reg}, {result_reg}",), + ); } BinaryOperator::Mul => { self.include("maths", "./lib/maths/core.dsa"); @@ -509,6 +593,53 @@ impl CodeGenerator { code.push(format!("\tpop {}", result_reg)); code.push("\tpop zero".to_string()); } + BinaryOperator::Div => { + return Err(CompilerError::Unimplemented(format!( + "TODO: write proper div function for DSA" + ))); + // self.include("maths", "./lib/maths/core.dsa"); + // // Call divide function + // code.push(format!("\tpush {}", right_reg)); + // code.push(format!("\tpush {}", left_reg)); + // code.push("\tcall maths::divide".to_string()); + // code.push(format!("\tpop {}", result_reg)); + // code.push("\tpop zero".to_string()); + } + BinaryOperator::Mod => { + return Err(CompilerError::Unimplemented(format!( + "TODO: write proper mod function for DSA" + ))); + // self.include("maths", "./lib/maths/core.dsa"); + // // Call modulo function + // code.push(format!("\tpush {}", right_reg)); + // code.push(format!("\tpush {}", left_reg)); + // code.push("\tcall maths::modulo".to_string()); + // code.push(format!("\tpop {}", result_reg)); + // code.push("\tpop zero".to_string()); + } + BinaryOperator::BitwiseAnd => { + code.push(format!("\tand {left_reg}, {right_reg}, {result_reg}")); + } + BinaryOperator::BitwiseOr => { + code.push(format!("\tor {left_reg}, {right_reg}, {result_reg}")); + } + BinaryOperator::BitwiseXor => { + code.push(format!("\txor {left_reg}, {right_reg}, {result_reg}")); + } + BinaryOperator::LogicalAnd => { + return Err(CompilerError::Unimplemented(format!( + "assembler/ISA does not yet support logical and!" + ))); + } + BinaryOperator::LogicalOr => { + return Err(CompilerError::Unimplemented(format!( + "assembler/ISA does not yet support logical or!" + ))); + } + BinaryOperator::LeftShift => code + .push(format!("\tshl {left_reg}, {right_reg}, 0, {result_reg}")), + BinaryOperator::RightShift => code + .push(format!("\tshr {left_reg}, {right_reg}, 0, {result_reg}")), // Comparison operators - return 1 (true) or 0 (false) BinaryOperator::Equal => { code.push(format!("\tcmp {}, {}", left_reg, right_reg)); @@ -557,8 +688,7 @@ impl CodeGenerator { code.push(format!("\tjge {}", end_label)); code.push(format!("\tlli 8, {}", result_reg)); code.push(format!("{}:", end_label)); - } - _ => unimplemented!(), + } // _ => unimplemented!(), } // Free operand registers (allocator will protect variables) @@ -568,6 +698,105 @@ impl CodeGenerator { Ok((result_reg, code)) } + Expression::UnaryPostfix { op, operand, .. } => { + let (operand_reg, operand_code) = + self.generate_expression(operand, true, func_body)?; + code.extend(operand_code); + + let (result_reg, result_alloc) = self.allocator.alloc_temp()?; + code.extend(result_alloc); + + match op { + UnaryOperator::Increment => { + // prefix increment + // code.push(format!("\taddi {}, 1, {}", operand_reg, + // operand_reg)); + code.push(format!("\tmov {}, {}", operand_reg, result_reg)); + } + UnaryOperator::Decrement => { + // prefix decrement + // code.push(format!("\tsubi {}, 1, {}", operand_reg, + // operand_reg)); + code.push(format!("\tmov {}, {}", operand_reg, result_reg)); + } + _ => { + return Err(CompilerError::Generic(format!( + "{op} is prefix only!" + ))); + } + } + + self.allocator.free_temp(operand_reg); + Ok((result_reg, code)) + } + + Expression::Unary { op, operand, .. } => { + let (operand_reg, operand_code) = + self.generate_expression(operand, true, func_body)?; + code.extend(operand_code); + + let (result_reg, result_alloc) = self.allocator.alloc_temp()?; + code.extend(result_alloc); + + match op { + UnaryOperator::Minus => { + // Negate: result = 0 - operand + code.push(format!("\tsub zero, {}, {}", operand_reg, result_reg)); + } + UnaryOperator::Plus => { + // Just move + code.push(format!("\tmov {}, {}", operand_reg, result_reg)); + } + UnaryOperator::Dereference => { + code.push(format!("\tldw {}, {}", operand_reg, result_reg)); + } + UnaryOperator::AddressOf => { + // ensure the referenced variable is on the stack and return its + // address. + let (offset, alloc_code) = + self.allocator.free_register(&operand_reg)?; + code.extend(alloc_code); + code.push(format!( + "\taddi spr, {}, {}", + offset - self.allocator.get_stack_offset(), + result_reg + )); + } + UnaryOperator::SizeOf => { + if let Ok(id) = operand.type_id() { + let size = id.size(); + code.push(format!("\tmov {}, {}", size, result_reg)); + } + } + UnaryOperator::Increment => { + // prefix increment + // code.push(format!("\tmov {}, {}", operand_reg, result_reg)); + code.push(format!("\taddi {}, 1, {}", operand_reg, result_reg)); + } + UnaryOperator::Decrement => { + // prefix decrement + // code.push(format!("\tmov {}, {}", operand_reg, result_reg)); + code.push(format!("\tsubi {}, 1, {}", operand_reg, result_reg)); + } + UnaryOperator::BitwiseNot => { + code.push(format!("\tnot {}, {}", operand_reg, result_reg)); + } + UnaryOperator::LogicalNot => { + return Err(CompilerError::Unimplemented(format!( + "Assembler/ISA does not yet support logical not" + ))); + } + _ => { + return Err(CompilerError::Generic(format!( + "{op} is postfix only!" + ))); + } + } + + self.allocator.free_temp(operand_reg); + Ok((result_reg, code)) + } + Expression::Call { func: Call { name, args }, .. @@ -642,69 +871,52 @@ impl CodeGenerator { Ok((result_reg, code)) } - Expression::Unary { op, operand, .. } => { - let (operand_reg, operand_code) = - self.generate_expression(operand, true, func_body)?; - code.extend(operand_code); + Expression::IndexAccess { + expr, + index, + type_id, + } => { + let (expr_reg, expr_alloc) = + self.generate_expression(expr, true, func_body)?; + code.extend(expr_alloc); + + let (index_reg, index_alloc) = + self.generate_expression(index, true, func_body)?; + code.extend(index_alloc); let (result_reg, result_alloc) = self.allocator.alloc_temp()?; code.extend(result_alloc); - match op { - UnaryOperator::Minus => { - // Negate: result = 0 - operand - code.push(format!("\tsub zero, {}, {}", operand_reg, result_reg)); - } - UnaryOperator::Plus => { - // Just move - code.push(format!("\tmov {}, {}", operand_reg, result_reg)); - } - UnaryOperator::Dereference => { - code.push(format!("\tldw {}, {}", operand_reg, result_reg)); - } - UnaryOperator::AddressOf => { - // ensure the referenced variable is on the stack and return its - // address. - let (offset, alloc_code) = - self.allocator.free_register(&operand_reg)?; - code.extend(alloc_code); - code.push(format!( - "\taddi spr, {}, {}", - offset - self.allocator.get_stack_offset(), - result_reg - )); - } - UnaryOperator::SizeOf => { - if let Ok(id) = operand.type_id() { - let size = id.size(); - code.push(format!("\tmov {}, {}", size, result_reg)); - } - } - UnaryOperator::CastAs => {} /* this should be removed once the */ - // semantic analyser can handle it! - UnaryOperator::Increment => { - // prefix increment - code.push(format!("\tmov {}, {}", operand_reg, result_reg)); - code.push(format!("\taddi {}, {}, 1", operand_reg, operand_reg)); - } - UnaryOperator::Decrement => { - // prefix decrement - code.push(format!("\tmov {}, {}", operand_reg, result_reg)); - code.push(format!("\tsubi {}, {}, 1", operand_reg, operand_reg)); - } - UnaryOperator::BitwiseNot => { - code.push(format!("\tnot {}, {}", operand_reg, result_reg)); - } - UnaryOperator::LogicalNot => unimplemented!(), - } + // add the expr pointer to the index to get the final address. + code.push(format!("\tadd {expr_reg} {index_reg} {result_reg}")); + // load the value at the address. + code.push(format!("\tldw {result_reg} {result_reg}")); + + self.allocator.free_temp(expr_reg); + self.allocator.free_temp(index_reg); - self.allocator.free_temp(operand_reg); Ok((result_reg, code)) } + Expression::MemberAccess { + expr, + field_name, + type_id, + } => Err(CompilerError::Unimplemented(format!( + "Structs are not yet implemented!" + ))), - Expression::Empty => Ok((Register::Null, code)), + Expression::TypeCast { + expr, + target_type, + type_id, + } => { + let (expr_reg, expr_code) = + self.generate_expression(expr, true, func_body)?; - _ => unimplemented!(), + // not sure if we actually need to do anything here. + // for now we just return the previous expression. + Ok((expr_reg, expr_code)) + } } } diff --git a/compiler/src/frontend/dsc/parser.rs b/compiler/src/frontend/dsc/parser.rs index a922a8e..78cf2a9 100644 --- a/compiler/src/frontend/dsc/parser.rs +++ b/compiler/src/frontend/dsc/parser.rs @@ -1,7 +1,8 @@ use super::lexer::Token; use crate::model::{ - BinaryOperator, Block, Call, CompilerError, ConstExpr, Declaration, Dependency, - Expression, Program, Statement, TypeId, UnaryOperator, Variable, + AssignmentOperator, BinaryOperator, Block, Call, CompilerError, ConstExpr, + Declaration, Dependency, Expression, Program, Statement, TypeId, UnaryOperator, + Variable, }; use crate::{expect_tt, expect_value}; use std::ops::{ControlFlow, FromResidual, Try}; @@ -328,7 +329,26 @@ impl Parser { } self.next()?; - let _ = expect_tt!(self.next()?, Assign)?; + let operator = match self.peek_next()? { + Token::Assign => AssignmentOperator::Assign, + Token::PlusEqual => AssignmentOperator::AddAssign, + Token::MinusEqual => AssignmentOperator::SubAssign, + Token::StarEqual => AssignmentOperator::MulAssign, + Token::SlashEqual => AssignmentOperator::DivAssign, + Token::PercentEqual => AssignmentOperator::ModAssign, + Token::AndEqual => AssignmentOperator::AndAssign, + Token::OrEqual => AssignmentOperator::OrAssign, + Token::XorEqual => AssignmentOperator::XorAssign, + Token::ShlEqual => AssignmentOperator::LeftShiftAssign, + Token::ShrEqual => AssignmentOperator::RightShiftAssign, + _ => { + return ParseResult::Reject(CompilerError::UnexpectedToken( + self.peek_next()?.tt().to_string(), + )); + } + }; + + self.next()?; let value = self.parse_expression()?; @@ -336,6 +356,7 @@ impl Parser { return ParseResult::Accept(Statement::Assign { varname: varname.name, + operator, value, }); } @@ -346,32 +367,132 @@ impl Parser { } fn parse_expression(&mut self) -> ParseResult { - self.parse_comparison() + self.parse_logical_or() + } + + fn parse_logical_or(&mut self) -> ParseResult { + let left = self.parse_logical_and()?; + + let op = match self.peek_next()? { + Token::Ampersand => BinaryOperator::LogicalOr, + _ => return ParseResult::Accept(left), + }; + + self.next()?; + ParseResult::Accept(Expression::Binary { + op, + left: Box::new(left), + right: Box::new(self.parse_logical_or()?), + type_id: Some(TypeId::U32), + }) + } + + fn parse_logical_and(&mut self) -> ParseResult { + let left = self.parse_bitwise_or()?; + + let op = match self.peek_next()? { + Token::Ampersand => BinaryOperator::LogicalAnd, + _ => return ParseResult::Accept(left), + }; + + self.next()?; + ParseResult::Accept(Expression::Binary { + op, + left: Box::new(left), + right: Box::new(self.parse_logical_and()?), + type_id: Some(TypeId::U32), + }) + } + + fn parse_bitwise_or(&mut self) -> ParseResult { + let left = self.parse_bitwise_xor()?; + + let op = match self.peek_next()? { + Token::Ampersand => BinaryOperator::BitwiseOr, + _ => return ParseResult::Accept(left), + }; + + self.next()?; + ParseResult::Accept(Expression::Binary { + op, + left: Box::new(left), + right: Box::new(self.parse_bitwise_or()?), + type_id: Some(TypeId::U32), + }) + } + + fn parse_bitwise_xor(&mut self) -> ParseResult { + let left = self.parse_bitwise_and()?; + + let op = match self.peek_next()? { + Token::Ampersand => BinaryOperator::BitwiseXor, + _ => return ParseResult::Accept(left), + }; + + self.next()?; + ParseResult::Accept(Expression::Binary { + op, + left: Box::new(left), + right: Box::new(self.parse_bitwise_xor()?), + type_id: Some(TypeId::U32), + }) + } + + fn parse_bitwise_and(&mut self) -> ParseResult { + let left = self.parse_comparison()?; + + let op = match self.peek_next()? { + Token::Ampersand => BinaryOperator::BitwiseAnd, + _ => return ParseResult::Accept(left), + }; + + self.next()?; + ParseResult::Accept(Expression::Binary { + op, + left: Box::new(left), + right: Box::new(self.parse_bitwise_and()?), + type_id: Some(TypeId::U32), + }) } fn parse_comparison(&mut self) -> ParseResult { - let mut expr = self.parse_additive()?; + let left = self.parse_shift()?; - while let Some(op) = match self.peek_next()? { - Token::EqualEqual => Some(BinaryOperator::Equal), - Token::BangEqual => Some(BinaryOperator::NotEqual), - Token::Less => Some(BinaryOperator::LessThan), - Token::Greater => Some(BinaryOperator::GreaterThan), - Token::LessEqual => Some(BinaryOperator::LessOrEqual), - Token::GreaterEqual => Some(BinaryOperator::GreaterOrEqual), - _ => None, - } { - self.next()?; - let right = Box::new(self.parse_additive()?); - expr = Expression::Binary { - op, - left: Box::new(expr), - right, - type_id: Some(TypeId::Bool), - }; - } + let op = match self.peek_next()? { + Token::EqualEqual => BinaryOperator::Equal, + Token::BangEqual => BinaryOperator::NotEqual, + Token::Less => BinaryOperator::LessThan, + Token::Greater => BinaryOperator::GreaterThan, + Token::LessEqual => BinaryOperator::LessOrEqual, + Token::GreaterEqual => BinaryOperator::GreaterOrEqual, + _ => return ParseResult::Accept(left), + }; - ParseResult::Accept(expr) + self.next()?; + ParseResult::Accept(Expression::Binary { + op, + left: Box::new(left), + right: Box::new(self.parse_comparison()?), + type_id: Some(TypeId::Bool), + }) + } + + fn parse_shift(&mut self) -> ParseResult { + let left = self.parse_additive()?; + + let op = match self.peek_next()? { + Token::LeftShift => BinaryOperator::LeftShift, + Token::RightShift => BinaryOperator::RightShift, + _ => return ParseResult::Accept(left), + }; + + self.next()?; + ParseResult::Accept(Expression::Binary { + op, + left: Box::new(left), + right: Box::new(self.parse_shift()?), + type_id: Some(TypeId::U32), + }) } fn parse_additive(&mut self) -> ParseResult { diff --git a/compiler/src/model.rs b/compiler/src/model.rs index a1abd87..1ad5cc4 100644 --- a/compiler/src/model.rs +++ b/compiler/src/model.rs @@ -11,6 +11,7 @@ pub enum CompilerError { Generic(String), UnknownType, TypeMismatch(TypeId, TypeId), + Unimplemented(String), } #[derive(Debug, PartialEq, Eq, Clone)] @@ -130,6 +131,7 @@ pub enum Statement { }, Assign { varname: String, + operator: AssignmentOperator, value: Expression, }, PtrWrite { @@ -308,6 +310,21 @@ impl Expression { } } +#[derive(Debug, Clone, PartialEq)] +pub enum AssignmentOperator { + Assign, + AddAssign, + SubAssign, + MulAssign, + DivAssign, + ModAssign, + AndAssign, + OrAssign, + XorAssign, + LeftShiftAssign, + RightShiftAssign, +} + #[allow(unused)] #[derive(Debug, Clone, PartialEq)] pub enum BinaryOperator { @@ -371,7 +388,6 @@ pub enum UnaryOperator { Minus, AddressOf, Dereference, - CastAs, BitwiseNot, LogicalNot, Increment, @@ -388,7 +404,6 @@ impl fmt::Display for UnaryOperator { Self::Minus => write!(f, "-"), Self::Dereference => write!(f, "*"), Self::AddressOf => write!(f, "&"), - Self::CastAs => write!(f, "as"), Self::BitwiseNot => write!(f, "~"), Self::LogicalNot => write!(f, "!"), Self::SizeOf => write!(f, "sizeof"), -- 2.47.3 From 509b3465f18fabc715fdbc2897bcfd88457db5a2 Mon Sep 17 00:00:00 2001 From: zxq5 Date: Mon, 9 Feb 2026 00:10:49 +0000 Subject: [PATCH 43/53] docs update --- docs/ISA REVISION notes.md | 4 ++++ docs/todo.md | 10 ++++++++++ 2 files changed, 14 insertions(+) create mode 100644 docs/ISA REVISION notes.md create mode 100644 docs/todo.md diff --git a/docs/ISA REVISION notes.md b/docs/ISA REVISION notes.md new file mode 100644 index 0000000..94bae35 --- /dev/null +++ b/docs/ISA REVISION notes.md @@ -0,0 +1,4 @@ + +- we definitely need to be able to use registers for shift operations. +- we need logical boolean operations in addition to the bitwise ones. +- better conditionals. diff --git a/docs/todo.md b/docs/todo.md new file mode 100644 index 0000000..4ecd16e --- /dev/null +++ b/docs/todo.md @@ -0,0 +1,10 @@ + +# Compiler optimisations! + +- [ ] [HARD] Immediate operations for values that support it (up to +/- u16::max for addi and subi respectively) + - this requires significant complexity in code generation as we need to traverse down the tree when we come across these operations to prevent additional register allocations. + +- [ ] [EASY] Add multiply and divide operations to code generation +- [ ] [MEDIUM] proper prefix/postfix inc/dec implementation. slightly more complex as we need to check for a variable and modify it in place +- [ ] [EASY] Investigate logical and operator not compiling - either a lexer or parser issue. +- [x] [MEDIUM] Get shift operations working correctly. -- 2.47.3 From 931af90789a0f2900ac443250da6aed92e21f548 Mon Sep 17 00:00:00 2001 From: zxq5 Date: Tue, 10 Feb 2026 10:03:48 +0000 Subject: [PATCH 44/53] - renamed assembler_runner to just assembler - implemented type parsing including custom types and generics (useless for now as we do no semantic analysis) - implemented struct literal parsing - implemented struct definition parsing (no generics yet) - implemented tuple parsing - registers are now allocated starting from zero - updated to-dos --- assembler/Cargo.toml | 2 +- compiler/src/backend/dsa/codegen.rs | 11 ++ compiler/src/backend/dsa/registers.rs | 41 +++--- compiler/src/frontend/dsc/lexer.rs | 3 + compiler/src/frontend/dsc/mod.rs | 2 +- compiler/src/frontend/dsc/parser.rs | 204 +++++++++++++++++++++----- compiler/src/model.rs | 98 +++++++++++-- docs/todo.md | 26 +++- 8 files changed, 316 insertions(+), 71 deletions(-) diff --git a/assembler/Cargo.toml b/assembler/Cargo.toml index 676d37d..0b577da 100644 --- a/assembler/Cargo.toml +++ b/assembler/Cargo.toml @@ -5,7 +5,7 @@ edition.workspace = true authors.workspace = true [[bin]] -name = "assembler_runner" +name = "assembler" path = "src/main.rs" [lib] diff --git a/compiler/src/backend/dsa/codegen.rs b/compiler/src/backend/dsa/codegen.rs index 7ba886c..4ca3c8e 100644 --- a/compiler/src/backend/dsa/codegen.rs +++ b/compiler/src/backend/dsa/codegen.rs @@ -65,6 +65,10 @@ impl CodeGenerator { Declaration::Dependency(Dependency { name, .. }) => { self.symbols.push(name) } + Declaration::Struct { .. } => {} /* we can't do any code generation for + * a struct yet. we may need to later + * once these become class-like + * objects with implementations */ } } @@ -164,6 +168,8 @@ impl CodeGenerator { Declaration::Dependency(Dependency { name, path }) => { self.imports.insert(name, path); } + Declaration::Struct { .. } => {} /* can't do any codegen for these yet, + * they're just types. */ }; Ok(()) @@ -536,6 +542,11 @@ impl CodeGenerator { } Expression::ArrayLiteral { elements, type_id } => todo!(), + Expression::StructLiteral { + name, + fields, + type_id, + } => todo!(), Expression::Variable { name, .. } => { if self.is_global(&name.name) { diff --git a/compiler/src/backend/dsa/registers.rs b/compiler/src/backend/dsa/registers.rs index 90da055..2436b8c 100644 --- a/compiler/src/backend/dsa/registers.rs +++ b/compiler/src/backend/dsa/registers.rs @@ -18,7 +18,7 @@ pub struct RegisterAllocator { stack_offset: i32, /// Track which registers are currently in use - in_use: HashMap, + in_use: Vec<(Register, bool)>, } #[derive(Debug, Clone)] @@ -85,7 +85,7 @@ impl RegisterAllocator { // println!("finding! {:#?}", self.in_use); if let Some(reg) = self.find_free_register() { - self.in_use.insert(reg, true); + self.in_use[reg as usize].1 = true; return Ok((reg, Vec::new())); } @@ -133,7 +133,7 @@ impl RegisterAllocator { // This is a true temporary - safe to free if !matches!(reg, Register::Zero | Register::Null) { - self.in_use.insert(reg, false); + self.in_use[reg as usize].1 = false; } } @@ -144,7 +144,7 @@ impl RegisterAllocator { && !matches!(reg, Register::Zero | Register::Null) { self.register_contents.remove(®); - self.in_use.insert(reg, false); + self.in_use[reg as usize].1 = false; } self.variable_locations.remove(var); @@ -252,7 +252,7 @@ impl RegisterAllocator { .insert(var_name.to_string(), Location::register(*source_reg)); self.register_contents .insert(*source_reg, var_name.to_string()); - self.in_use.insert(*source_reg, true); + self.in_use[*source_reg as usize].1 = true; return Vec::new(); } @@ -264,7 +264,7 @@ impl RegisterAllocator { .insert(var_name.to_string(), Location::register(free_reg)); self.register_contents .insert(free_reg.clone(), var_name.to_string()); - self.in_use.insert(free_reg, true); + self.in_use[free_reg as usize].1 = true; return vec![format!("\tmov {}, {}", source_reg, free_reg)]; } @@ -459,26 +459,31 @@ impl RegisterAllocator { pub fn get_caller_saved_registers(&self) -> Vec { self.register_contents .iter() - .filter(|(reg, _)| *self.in_use.get(*reg).unwrap_or(&false)) + .filter(|(reg, _)| { + self.in_use + .get(**reg as usize) + .unwrap_or(&(Register::Null, false)) + .1 + }) .map(|(reg, _)| reg.clone()) .collect() } /// Save caller-saved registers before a function call /// Returns assembly code to save them - pub fn _save_caller_saved(&mut self) -> Vec { - let mut code = Vec::new(); + // pub fn _save_caller_saved(&mut self) -> Vec { + // let mut code = Vec::new(); - // For simplicity, save all currently used registers - // In a more sophisticated compiler, you'd only save registers that are live - for (reg, _) in self.register_contents.clone() { - if *self.in_use.get(®).unwrap_or(&false) { - code.push(format!("\tpush {}", reg)); - } - } + // // For simplicity, save all currently used registers + // // In a more sophisticated compiler, you'd only save registers that are live + // for (reg, _) in self.register_contents.clone() { + // if *self.in_use.get(reg as usize).unwrap_or(&false) { + // code.push(format!("\tpush {}", reg)); + // } + // } - code - } + // code + // } /// Restore caller-saved registers after a function call /// Returns assembly code to restore them diff --git a/compiler/src/frontend/dsc/lexer.rs b/compiler/src/frontend/dsc/lexer.rs index 5805f6a..a5369ec 100644 --- a/compiler/src/frontend/dsc/lexer.rs +++ b/compiler/src/frontend/dsc/lexer.rs @@ -18,6 +18,7 @@ pub enum Token { Const, As, SizeOf, + Struct, // Identifiers and literals Identifier(Name), @@ -104,6 +105,7 @@ impl Token { Token::Const => "Const", Token::Static => "Static", Token::Include => "Include", + Token::Struct => "Struct", Token::Fn => "Fn", Token::If => "If", Token::Let => "Let", @@ -376,6 +378,7 @@ impl<'a> Lexer<'a> { "static" => Some(Token::Static), "as" => Some(Token::As), "sizeof" => Some(Token::SizeOf), + "struct" => Some(Token::Struct), _ => None, } } diff --git a/compiler/src/frontend/dsc/mod.rs b/compiler/src/frontend/dsc/mod.rs index 5718e6f..7778fa8 100644 --- a/compiler/src/frontend/dsc/mod.rs +++ b/compiler/src/frontend/dsc/mod.rs @@ -13,7 +13,7 @@ pub fn generate_ast(input: &str) -> Result { let lexer = lexer::Lexer::new(&input); let tokens = lexer.collect::>(); - // println!("{tokens:?}"); + println!("{tokens:#?}"); log(&format!("Parsing {} Tokens...", tokens.len())); diff --git a/compiler/src/frontend/dsc/parser.rs b/compiler/src/frontend/dsc/parser.rs index 78cf2a9..009b521 100644 --- a/compiler/src/frontend/dsc/parser.rs +++ b/compiler/src/frontend/dsc/parser.rs @@ -39,6 +39,10 @@ impl Parser { return self.parse_func(); } + if expect_tt!(self.peek_next()?, Struct).accepted() { + return self.parse_struct(); + } + if expect_tt!(self.peek_next()?, Include).accepted() { // expect include keyword let _ = self.next(); @@ -99,6 +103,28 @@ impl Parser { ParseResult::Reject(CompilerError::UnexpectedEndOfInput) } + fn parse_struct(&mut self) -> ParseResult { + let _ = expect_tt!(self.next()?, Struct)?; + let name = expect_value!(self.next()?, Identifier)?; + + let _ = expect_tt!(self.next()?, LeftBrace)?; + + let mut fields = Vec::new(); + while expect_tt!(self.peek_next()?, Identifier).accepted() { + let arg = self.parse_var_decl()?; + fields.push(arg); + + if expect_tt!(self.peek_next()?, Comma).accepted() { + self.next()?; + } else { + break; + } + } + + let _ = expect_tt!(self.next()?, RightBrace)?; + return ParseResult::Accept(Declaration::Struct { name, fields }); + } + fn parse_func(&mut self) -> ParseResult { // expect function keyword let _ = expect_tt!(self.next()?, Fn); @@ -318,18 +344,28 @@ impl Parser { }); } - // handle assignment without "let" - let name = expect_value!(self.peek_next()?, Identifier); - if name.accepted() { - let varname = name?; - if expect_tt!(self.peek(1)?, LeftParen).accepted() { - let expr = self.parse_expression()?; // a function call expr - let _ = expect_tt!(self.next()?, Semicolon)?; - return ParseResult::Accept(Statement::Expression { expr }); - } - + // handle an in-place function call + if let ParseResult::Accept(name) = expect_value!(self.peek_next()?, Identifier) + && let ParseResult::Accept(operator) = expect_tt!( + self.peek(1)?, + Assign, + PlusEqual, + MinusEqual, + StarEqual, + SlashEqual, + PercentEqual, + AndEqual, + OrEqual, + XorEqual, + ShlEqual, + ShrEqual + ) + { + // consume name token self.next()?; - let operator = match self.peek_next()? { + + // pattern match to find operator + let operator = match operator { Token::Assign => AssignmentOperator::Assign, Token::PlusEqual => AssignmentOperator::AddAssign, Token::MinusEqual => AssignmentOperator::SubAssign, @@ -348,6 +384,7 @@ impl Parser { } }; + // consume operator token self.next()?; let value = self.parse_expression()?; @@ -355,15 +392,17 @@ impl Parser { let _ = expect_tt!(self.next()?, Semicolon); return ParseResult::Accept(Statement::Assign { - varname: varname.name, + varname: name.name, operator, value, }); } - ParseResult::Reject(CompilerError::UnexpectedToken( - self.peek_next()?.tt().to_string(), - )) + // parse an expression and a semicolon + let expr = self.parse_expression()?; + let _ = expect_tt!(self.next()?, Semicolon)?; + + return ParseResult::Accept(Statement::Expression { expr }); } fn parse_expression(&mut self) -> ParseResult { @@ -374,7 +413,7 @@ impl Parser { let left = self.parse_logical_and()?; let op = match self.peek_next()? { - Token::Ampersand => BinaryOperator::LogicalOr, + Token::LogicalOr => BinaryOperator::LogicalOr, _ => return ParseResult::Accept(left), }; @@ -391,7 +430,7 @@ impl Parser { let left = self.parse_bitwise_or()?; let op = match self.peek_next()? { - Token::Ampersand => BinaryOperator::LogicalAnd, + Token::LogicalAnd => BinaryOperator::LogicalAnd, _ => return ParseResult::Accept(left), }; @@ -408,7 +447,7 @@ impl Parser { let left = self.parse_bitwise_xor()?; let op = match self.peek_next()? { - Token::Ampersand => BinaryOperator::BitwiseOr, + Token::Pipe => BinaryOperator::BitwiseOr, _ => return ParseResult::Accept(left), }; @@ -425,7 +464,7 @@ impl Parser { let left = self.parse_bitwise_and()?; let op = match self.peek_next()? { - Token::Ampersand => BinaryOperator::BitwiseXor, + Token::Caret => BinaryOperator::BitwiseXor, _ => return ParseResult::Accept(left), }; @@ -678,9 +717,39 @@ impl Parser { Token::Identifier(name) => { self.next()?; - ParseResult::Accept(Expression::Variable { + + // if the next token isn't the beginning of a struct literal this is just + // an identifier. + if !expect_tt!(self.peek_next()?, LeftBrace).accepted() { + return ParseResult::Accept(Expression::Variable { + name, + expr_type: None, + }); + } + + let _ = self.next()?; + + let mut fields = Vec::new(); + while !expect_tt!(self.peek_next()?, RightBrace).accepted() { + let name = expect_value!(self.next()?, Identifier)?; + let _ = expect_tt!(self.next()?, Colon)?; + let expr = self.parse_expression()?; + + fields.push((name, expr)); + + if expect_tt!(self.peek_next()?, Comma).accepted() { + self.next()?; + } else { + break; + } + } + + let _ = expect_tt!(self.next()?, RightBrace)?; + + ParseResult::Accept(Expression::StructLiteral { name, - expr_type: None, + fields, + type_id: None, }) } Token::LeftBracket => { @@ -729,21 +798,88 @@ impl Parser { } fn parse_type(&mut self) -> ParseResult { - // get the type name incl namespace - let typename = expect_value!(self.next()?, Identifier)?; + println!("yes {:?}", self.peek_next()?); - match typename.name.as_str() { - "u32" => ParseResult::Accept(TypeId::U32), - "u16" => ParseResult::Accept(TypeId::U16), - "u8" => ParseResult::Accept(TypeId::U8), - "i32" => ParseResult::Accept(TypeId::I32), - "i16" => ParseResult::Accept(TypeId::I16), - "i8" => ParseResult::Accept(TypeId::I8), - "void" => ParseResult::Accept(TypeId::Void), - "char" => ParseResult::Accept(TypeId::Char), - "str" => ParseResult::Accept(TypeId::Ptr(Box::new(TypeId::Char))), - _ => todo!("Implement parsing for other types!!"), + // parse primitive or named type + if expect_tt!(self.peek_next()?, Identifier).accepted() { + return self.parse_type_identifier(); } + + // parse array type + if expect_tt!(self.peek_next()?, LeftBracket).accepted() { + let _ = self.next()?; + + let internal_type = self.parse_type()?; + let _ = expect_tt!(self.next()?, Semicolon)?; + + let size = expect_value!(self.next()?, Integer)?; + + let _ = expect_tt!(self.next()?, RightBracket)?; + + return ParseResult::Accept(TypeId::Array { + r#type: Box::new(internal_type), + size: size as usize, + }); + } + + // parse tuple type + if expect_tt!(self.peek_next()?, LeftParen).accepted() { + let _ = self.next()?; + + let mut types = Vec::new(); + while !expect_tt!(self.peek_next()?, RightParen).accepted() { + types.push(self.parse_type()?); + if !expect_tt!(self.peek_next()?, Comma).accepted() { + break; + } + let _ = self.next()?; + } + let _ = expect_tt!(self.next()?, RightParen)?; + + return ParseResult::Accept(TypeId::Tuple(types)); + } + + ParseResult::Reject(CompilerError::Generic(format!( + "Parsing type but no valid type was detected: {:?}", + self.peek_next()? + ))) + } + + fn parse_type_identifier(&mut self) -> ParseResult { + // get the type name incl namespace + let name = expect_value!(self.next()?, Identifier)?; + + let type_id = match name.name.as_str() { + "u32" => TypeId::U32, + "u16" => TypeId::U16, + "u8" => TypeId::U8, + "i32" => TypeId::I32, + "i16" => TypeId::I16, + "i8" => TypeId::I8, + "void" => TypeId::Void, + "char" => TypeId::Char, + "str" => TypeId::Ptr(Box::new(TypeId::Char)), + _ => { + let mut generics = Vec::new(); + if expect_tt!(self.peek_next()?, Less).accepted() { + let _ = self.next()?; + + // loop until we find the closing '>' + while !expect_tt!(self.peek_next()?, Greater).accepted() { + generics.push(self.parse_type()?); + if !expect_tt!(self.peek_next()?, Comma).accepted() { + break; + } + let _ = self.next()?; + } + let _ = expect_tt!(self.next()?, Greater)?; + } + + TypeId::UnknownCustom { name, generics } + } + }; + + ParseResult::Accept(type_id) } fn next(&mut self) -> ParseResult { diff --git a/compiler/src/model.rs b/compiler/src/model.rs index 1ad5cc4..83969b7 100644 --- a/compiler/src/model.rs +++ b/compiler/src/model.rs @@ -40,6 +40,10 @@ pub enum Declaration { is_const: bool, }, Dependency(Dependency), + Struct { + name: Name, + fields: Vec, + }, } #[derive(Debug, Clone)] @@ -62,8 +66,20 @@ pub enum TypeId { Void, Ptr(Box), Ref(Box), - Array(Box, usize), - Struct { name: Name, fields: Vec }, + Tuple(Vec), + Array { + r#type: Box, + size: usize, + }, + UnknownCustom { + name: Name, + generics: Vec, + }, + Struct { + name: Name, + fields: Vec, + generics: Vec, + }, } impl TypeId { @@ -80,7 +96,10 @@ impl TypeId { Self::Void => 0, Self::Ptr(t) => t.size(), Self::Ref(t) => t.size(), - Self::Array(t, size) => t.size() * size, + Self::Tuple(types) => types.iter().map(|t| t.size()).sum(), + Self::Array { r#type, size } => r#type.size() * size, + Self::UnknownCustom { .. } => 1, /* TODO: calculate type size during */ + // semantic analysis Self::Struct { fields, .. } => fields.iter().map(|t| t.size()).sum(), } } @@ -100,14 +119,47 @@ impl fmt::Display for TypeId { Self::Void => write!(f, "void"), Self::Ptr(t) => write!(f, "*{}", t), Self::Ref(t) => write!(f, "&{}", t), - Self::Array(t, len) => write!(f, "[{}; {}]", t, len), - Self::Struct { name, fields } => { - write!(f, "struct {} {{", name)?; - for (i, field) in fields.iter().enumerate() { - write!(f, "{}: {}", i, field)?; - } - write!(f, "}}") + Self::Tuple(elems) => write!( + f, + "({})", + elems + .iter() + .map(|t| t.to_string()) + .collect::>() + .join(", ") + ), + Self::Array { r#type, size } => write!(f, "[{}; {}]", r#type, size), + Self::UnknownCustom { name, generics } => { + write!( + f, + "{}<{}>", + name, + generics + .iter() + .map(|t| t.to_string()) + .collect::>() + .join(", ") + ) } + Self::Struct { + name, + fields, + generics, + } => write!( + f, + "struct<{}> {} {{{}}}", + generics + .iter() + .map(|t| t.to_string()) + .collect::>() + .join(", "), + name, + fields + .iter() + .map(|t| t.to_string()) + .collect::>() + .join(", ") + ), } } } @@ -241,6 +293,11 @@ pub enum Expression { elements: Vec, type_id: Option, }, + StructLiteral { + name: Name, + fields: Vec<(Name, Expression)>, + type_id: Option, + }, } #[derive(Debug, Clone)] @@ -266,9 +323,12 @@ impl Expression { expr.is_pure() && index.is_pure() } Expression::MemberAccess { expr, .. } => expr.is_pure(), - Expression::ArrayLiteral { elements, type_id } => { + Expression::ArrayLiteral { elements, .. } => { elements.iter().all(|element| element.is_pure()) } + Expression::StructLiteral { fields, .. } => { + fields.iter().all(|(_, expr)| expr.is_pure()) + } } } @@ -304,7 +364,21 @@ impl Expression { let element_type = elements .first() .map_or(TypeId::Void, |e| e.type_id().unwrap_or(TypeId::Void)); - Ok(TypeId::Array(Box::new(element_type), elements.len())) + Ok(TypeId::Array { + r#type: Box::new(element_type), + size: elements.len(), + }) + } + Expression::StructLiteral { name, fields, .. } => { + let fields = fields + .iter() + .map(|(_, expr)| expr.type_id()) + .collect::, _>>()?; + Ok(TypeId::Struct { + name: name.clone(), + fields, + generics: Vec::new(), + }) } } } diff --git a/docs/todo.md b/docs/todo.md index 4ecd16e..4d565eb 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -1,10 +1,26 @@ +# General TODO's -# Compiler optimisations! +# Bugfixes +- [x] [EASY] Investigate logical and operator not compiling - either a lexer or parser issue. + - **note**: this was a parser issue. +# Missing features +- [x] [MEDIUM] Get shift operations working correctly. +- [ ] [MEDIUM] proper prefix/postfix inc/dec implementation. slightly more complex as we need to check for a variable and modify it in place +- [ ] [EASY] Add multiply and divide operations to code generation + - **note**: very easy to do but our division algorithm is hopelessly slow so not worth doing for now. + +# Performance Improvements +- [ ] [MEDIUM] implement a proper div/mod library that's not slow af. - [ ] [HARD] Immediate operations for values that support it (up to +/- u16::max for addi and subi respectively) - this requires significant complexity in code generation as we need to traverse down the tree when we come across these operations to prevent additional register allocations. -- [ ] [EASY] Add multiply and divide operations to code generation -- [ ] [MEDIUM] proper prefix/postfix inc/dec implementation. slightly more complex as we need to check for a variable and modify it in place -- [ ] [EASY] Investigate logical and operator not compiling - either a lexer or parser issue. -- [x] [MEDIUM] Get shift operations working correctly. +# Compiler optimisations + +# Codegen improvements +- [ ] [MEDIUM / time consuming] Add scoping to code generation +- [ ] [MEDIUM / time consuming] Rewrite entire codegen to imrpove code quality and make the code more readable. + - [ ] type-safe instruction builder + - [ ] Instruction & Register enums + - [ ] Instruction builder helper fns eg `fn add(left: &Register, right: &Register, dest: &Register) -> Instruction` + - [ ] Instruction Block types. -- 2.47.3 From 5e575e2cd8c1202c57f96b6e84f602297f8fd29a Mon Sep 17 00:00:00 2001 From: zxq5 Date: Tue, 10 Feb 2026 10:05:13 +0000 Subject: [PATCH 45/53] used claude to write a language spec (syntax and simple examples) for DSC that we can follow as a reference for implementation. --- docs/dsc-language-spec.md | 1223 +++++++++++++++++++++++++++++++++++++ 1 file changed, 1223 insertions(+) create mode 100644 docs/dsc-language-spec.md diff --git a/docs/dsc-language-spec.md b/docs/dsc-language-spec.md new file mode 100644 index 0000000..1b99dc5 --- /dev/null +++ b/docs/dsc-language-spec.md @@ -0,0 +1,1223 @@ +# DSC Language Specification v1.0 + +**Damn Simple C (DSC)** - A systems programming language for the DSA architecture + +--- + +## Table of Contents + +1. [Introduction](#introduction) +2. [Lexical Structure](#lexical-structure) +3. [Types](#types) +4. [Variables and Declarations](#variables-and-declarations) +5. [Classes](#classes) +6. [Functions](#functions) +7. [Expressions](#expressions) +8. [Statements](#statements) +9. [Control Flow](#control-flow) +10. [Memory Management](#memory-management) +11. [Generics](#generics) +12. [Modules and Imports](#modules-and-imports) +13. [Keywords](#keywords) +14. [Operators](#operators) +15. [Complete Example Programs](#complete-example-programs) +16. [Grammar Summary](#grammar-summary) +17. [Appendix A: Differences from Rust](#appendix-a-differences-from-rust) +18. [Appendix B: Standard Library Conventions](#appendix-b-standard-library-conventions) + +--- + +## 1. Introduction + +DSC is a statically-typed systems programming language designed for the DSA (Damn Simple Architecture). It combines modern syntax with explicit memory management and direct assembly interoperability. + +### Design Goals + +- **Explicit**: No hidden control flow or implicit conversions +- **Simple**: Easy to understand what the compiler generates +- **Safe**: Scope-based resource management via `defer` +- **Interoperable**: Direct assembly library integration + +--- + +## 2. Lexical Structure + +### 2.1 Comments + +```rust +// Single-line comment + +/* Multi-line + comment */ +``` + +### 2.2 Identifiers + +Identifiers must start with a letter or underscore, followed by any number of letters, digits, or underscores. + +```rust +valid_identifier +_private +MyClass +get_value_42 +``` + +### 2.3 Reserved Keywords + +``` +as break class const continue +defer else false field fn +if impl include let loop +mut priv pub return self +static struct true void while +``` + +### 2.4 Literals + +#### Integer Literals +```rust +42 // Decimal +0x2A // Hexadecimal +0b101010 // Binary +0o52 // Octal +``` + +#### String Literals +```rust +"Hello, world!" +"Escape sequences: \n \t \\ \"" +``` + +#### Boolean Literals +```rust +true +false +``` + +--- + +## 3. Types + +### 3.1 Primitive Types + +#### Integer Types +```rust +i8 // Signed 8-bit integer +i16 // Signed 16-bit integer +i32 // Signed 32-bit integer +i64 // Signed 64-bit integer + +u8 // Unsigned 8-bit integer +u16 // Unsigned 16-bit integer +u32 // Unsigned 32-bit integer +u64 // Unsigned 64-bit integer +``` + +#### Boolean Type +```rust +bool // true or false +``` + +#### String Type +```rust +str // String literal type (pointer to null-terminated byte array) +``` + +#### Void Type +```rust +void // Absence of a value (only valid as return type) +``` + +### 3.2 Pointer Types + +Pointers are declared using the `&` prefix: + +```rust +&u32 // Pointer to u32 +&bool // Pointer to bool +&Point // Pointer to Point class +``` + +**Note**: Pointers in DSC are raw memory addresses (like C pointers), not Rust-style references with borrow checking. + +### 3.3 Array Types + +```rust +[u32; 10] // Fixed-size array of 10 u32 values +[bool; 256] // Fixed-size array of 256 bool values +``` + +### 3.4 Generic Types + +```rust +Vec // Generic type with one type parameter +Map // Generic type with multiple type parameters +``` + +--- + +## 4. Variables and Declarations + +### 4.1 Variable Declaration + +Variables are declared with `let` and are immutable by default: + +```rust +let x: u32 = 42; +let name: str = "Alice"; +let ptr: &u32 = &x; +``` + +### 4.2 Mutable Variables + +Use `mut` to declare mutable variables: + +```rust +let mut count: u32 = 0; +count = count + 1; +``` + +### 4.3 Type Inference + +Type annotations can be omitted when the type can be inferred: + +```rust +let x = 42; // Inferred as u32 (default integer type) +let name = "Bob"; // Inferred as str +``` + +--- + +## 5. Classes + +Classes are DSC's primary means of encapsulation, combining data and behavior. + +### 5.1 Class Definition + +```rust +class ClassName { + // Class body +} +``` + +### 5.2 Fields + +Fields represent the data members of a class. They must be explicitly marked as `pub` or `priv`: + +```rust +class Point { + pub field x: u32; + pub field y: u32; + priv field cached: bool; +} +``` + +**Syntax**: `[pub|priv] field : ;` + +### 5.3 Constants + +Constants are compile-time values associated with a class: + +```rust +class Math { + pub const PI: u32 = 3; + pub const E: u32 = 2; + priv const INTERNAL_CONSTANT: u32 = 42; +} +``` + +**Syntax**: `[pub|priv] const : = ;` + +**Usage**: `Math::PI` + +### 5.4 Static Fields + +Static fields are global variables associated with a class: + +```rust +class Logger { + pub static mut INSTANCE_COUNT: u32 = 0; + priv static mut INTERNAL_STATE: u32 = 0; +} +``` + +**Syntax**: `[pub|priv] static mut : = ;` + +**Usage**: +```rust +Logger::INSTANCE_COUNT = Logger::INSTANCE_COUNT + 1; +``` + +**Note**: All static fields must be declared `mut` as they represent mutable global state. + +### 5.5 Methods + +Methods are functions associated with a class. Instance methods take a `self` parameter: + +```rust +class Point { + pub field x: u32; + pub field y: u32; + + // Constructor (static method) + pub fn new(x: u32, y: u32) -> Point { + return Point { x: x, y: y }; + } + + // Instance method + pub fn get_x(self: &Point) -> u32 { + return self.x; + } + + // Mutable instance method + pub fn set_x(self: &Point, value: u32) { + self.x = value; + } + + // Static method + pub fn zero() -> Point { + return Point { x: 0, y: 0 }; + } +} +``` + +**Method Syntax**: +- Instance method: `[pub|priv] fn (self: &, ...) -> ` +- Static method: `[pub|priv] fn (...) -> ` + +### 5.6 Class Instantiation + +```rust +// Using constructor +let p1: Point = Point::new(10, 20); + +// Using struct literal syntax +let p2: Point = Point { x: 5, y: 15 }; +``` + +### 5.7 Method Calls + +```rust +let p: Point = Point::new(10, 20); + +// Method call syntax (preferred) +let x: u32 = p.get_x(); + +// Explicit syntax (equivalent) +let x: u32 = Point::get_x(&p); + +// Static method call +let origin: Point = Point::zero(); +``` + +### 5.8 Field Access + +```rust +let p: Point = Point { x: 10, y: 20 }; + +// Read field +let x: u32 = p.x; + +// Write field (if mutable) +let mut p2: Point = Point { x: 0, y: 0 }; +p2.x = 42; +``` + +### 5.9 Complete Class Example + +```rust +class Vec { + priv field data: &u32; + priv field len: u32; + priv field capacity: u32; + + pub const DEFAULT_CAPACITY: u32 = 16; + pub static mut TOTAL_ALLOCATIONS: u32 = 0; + + pub fn new(capacity: u32) -> Vec { + Vec::TOTAL_ALLOCATIONS = Vec::TOTAL_ALLOCATIONS + 1; + return Vec { + data: alloc::malloc(capacity * 4) as &u32, + len: 0, + capacity: capacity + }; + } + + pub fn with_default_capacity() -> Vec { + return Vec::new(Vec::DEFAULT_CAPACITY); + } + + pub fn push(self: &Vec, item: u32) { + if self.len == self.capacity { + self.grow(); + } + *(self.data + self.len) = item; + self.len = self.len + 1; + } + + pub fn get(self: &Vec, index: u32) -> u32 { + return *(self.data + index); + } + + priv fn grow(self: &Vec) { + let new_capacity: u32 = self.capacity * 2; + let new_data: &u32 = alloc::malloc(new_capacity * 4) as &u32; + + // Copy old data + let mut i: u32 = 0; + while i < self.len { + *(new_data + i) = *(self.data + i); + i = i + 1; + } + + alloc::free(self.data as u32); + self.data = new_data; + self.capacity = new_capacity; + } + + pub fn drop(self: &Vec) { + alloc::free(self.data as u32); + } +} +``` + +--- + +## 6. Functions + +### 6.1 Function Declaration + +```rust +fn function_name(param1: Type1, param2: Type2) -> ReturnType { + // Function body +} +``` + +### 6.2 Return Type + +Functions without a return value use `void` or omit the return type: + +```rust +fn print_message(msg: str) -> void { + // No return value +} + +// Equivalent +fn print_message(msg: str) { + // No return value +} +``` + +### 6.3 Return Statement + +```rust +fn add(a: u32, b: u32) -> u32 { + return a + b; +} +``` + +### 6.4 Function Parameters + +```rust +fn process(value: u32, ptr: &u32, flag: bool) -> u32 { + // ... +} +``` + +--- + +## 7. Expressions + +### 7.1 Literals + +```rust +42 // Integer literal +0xFF // Hex literal +true // Boolean literal +"hello" // String literal +``` + +### 7.2 Binary Operations + +```rust +a + b // Addition +a - b // Subtraction +a * b // Multiplication +a / b // Division +a % b // Modulo +``` + +### 7.3 Comparison Operations + +```rust +a == b // Equal +a != b // Not equal +a < b // Less than +a > b // Greater than +a <= b // Less than or equal +a >= b // Greater than or equal +``` + +### 7.4 Logical Operations + +```rust +a && b // Logical AND +a || b // Logical OR +!a // Logical NOT +``` + +### 7.5 Bitwise Operations + +```rust +a & b // Bitwise AND +a | b // Bitwise OR +a ^ b // Bitwise XOR +~a // Bitwise NOT +a << b // Left shift +a >> b // Right shift +``` + +### 7.6 Address-of and Dereference + +```rust +&x // Address-of (get pointer to x) +*ptr // Dereference (read/write through pointer) +``` + +### 7.7 Type Casting + +```rust +value as u32 // Cast value to u32 +ptr as &u8 // Cast pointer type +``` + +### 7.8 Field Access + +```rust +obj.field // Access field of object +ptr.field // Access field through pointer (implicit dereference) +``` + +### 7.9 Method Call + +```rust +obj.method(args) // Instance method call +Class::method(args) // Static method call +``` + +### 7.10 Array Indexing + +```rust +arr[index] // Access array element +``` + +--- + +## 8. Statements + +### 8.1 Expression Statement + +```rust +function_call(); +x = y + 1; +``` + +### 8.2 Variable Declaration + +```rust +let x: u32 = 42; +let mut y: u32 = 0; +``` + +### 8.3 Assignment + +```rust +x = 10; +*ptr = 20; +obj.field = 30; +arr[0] = 40; +``` + +### 8.4 Compound Assignment + +```rust +x += 5; // x = x + 5 +x -= 3; // x = x - 3 +x *= 2; // x = x * 2 +x /= 4; // x = x / 4 +``` + +--- + +## 9. Control Flow + +### 9.1 If Statement + +```rust +if condition { + // then block +} + +if condition { + // then block +} else { + // else block +} + +if condition1 { + // block 1 +} else if condition2 { + // block 2 +} else { + // block 3 +} +``` + +### 9.2 While Loop + +```rust +while condition { + // loop body +} +``` + +### 9.3 Loop (Infinite Loop) + +```rust +loop { + // infinite loop + if should_exit { + break; + } +} +``` + +### 9.4 Break and Continue + +```rust +while condition { + if skip_condition { + continue; // Skip to next iteration + } + + if exit_condition { + break; // Exit loop + } +} +``` + +--- + +## 10. Memory Management + +### 10.1 Defer Statement + +The `defer` keyword schedules a statement to execute when the current scope exits. + +```rust +fn example() { + let ptr: u32 = alloc::malloc(256); + defer alloc::free(ptr); + + // Use ptr... + + if error { + return; // defer runs here + } + + // More work... + +} // defer runs here +``` + +### 10.2 Defer Execution Rules + +1. **Scope-based**: Defers execute when their enclosing scope exits +2. **LIFO order**: Multiple defers execute in reverse order of declaration +3. **All exit paths**: Defers run on return, break, continue, or natural scope end + +```rust +fn nested_defers() { + let a: u32 = alloc::malloc(100); + defer alloc::free(a); // Runs second + + if condition { + let b: u32 = alloc::malloc(200); + defer alloc::free(b); // Runs first (if in this scope) + + // ... work ... + + } // b's defer runs here + + // ... more work ... + +} // a's defer runs here +``` + +### 10.3 Defer with Methods + +```rust +class Resource { + priv field handle: u32; + + pub fn acquire() -> Resource { + return Resource { handle: alloc::malloc(1024) }; + } + + pub fn release(self: &Resource) { + alloc::free(self.handle); + } +} + +fn use_resource() { + let res: Resource = Resource::acquire(); + defer res.release(); + + // Use resource... + +} // res.release() automatically called +``` + +--- + +## 11. Generics + +### 11.1 Generic Classes + +```rust +class Box { + priv field value: T; + + pub fn new(value: T) -> Box { + return Box { value: value }; + } + + pub fn get(self: &Box) -> T { + return self.value; + } + + pub fn set(self: &Box, value: T) { + self.value = value; + } +} +``` + +### 11.2 Generic Usage + +```rust +let int_box: Box = Box::new(42); +let str_box: Box = Box::new("hello"); + +let value: u32 = int_box.get(); +``` + +### 11.3 Multiple Type Parameters + +```rust +class Pair { + pub field first: T; + pub field second: U; + + pub fn new(first: T, second: U) -> Pair { + return Pair { first: first, second: second }; + } +} + +let p: Pair = Pair::new(42, "answer"); +``` + +### 11.4 Generic Functions + +```rust +fn swap(a: &T, b: &T) { + let temp: T = *a; + *a = *b; + *b = temp; +} + +let mut x: u32 = 1; +let mut y: u32 = 2; +swap(&x, &y); +``` + +### 11.5 Generic Constraints + +**Note**: DSC does not support trait bounds or where clauses. Generic types are instantiated via monomorphization without constraints. + +--- + +## 12. Modules and Imports + +### 12.1 Assembly Imports + +DSC can import assembly modules for low-level operations: + +```rust +include print: "./lib/io/print.dsa"; +include alloc: "./lib/memory/alloc.dsa"; +``` + +**Syntax**: `include : "";` + +### 12.2 Using Imported Functions + +```rust +print::println("Hello, world!"); +let ptr: u32 = alloc::malloc(256); +``` + +--- + +## 13. Keywords + +### 13.1 Complete Keyword List + +| Keyword | Purpose | +|---------|---------| +| `as` | Type casting | +| `break` | Exit loop | +| `class` | Class definition | +| `const` | Constant declaration | +| `continue` | Next loop iteration | +| `defer` | Defer statement execution | +| `else` | Alternative branch | +| `false` | Boolean literal | +| `field` | Class field declaration | +| `fn` | Function declaration | +| `if` | Conditional statement | +| `impl` | *Reserved for future use* | +| `include` | Import assembly module | +| `let` | Variable declaration | +| `loop` | Infinite loop | +| `mut` | Mutable binding | +| `priv` | Private visibility | +| `pub` | Public visibility | +| `return` | Return from function | +| `self` | Instance reference | +| `static` | Static field declaration | +| `struct` | *Reserved for future use* | +| `true` | Boolean literal | +| `void` | No return type | +| `while` | While loop | + +--- + +## 14. Operators + +### 14.1 Operator Precedence (Highest to Lowest) + +| Precedence | Operators | Associativity | +|------------|-----------|---------------| +| 1 | `()` `[]` `.` `::` | Left to right | +| 2 | `!` `~` `&` `*` (unary) `-` (unary) | Right to left | +| 3 | `as` | Left to right | +| 4 | `*` `/` `%` | Left to right | +| 5 | `+` `-` | Left to right | +| 6 | `<<` `>>` | Left to right | +| 7 | `&` | Left to right | +| 8 | `^` | Left to right | +| 9 | `|` | Left to right | +| 10 | `==` `!=` `<` `>` `<=` `>=` | Left to right | +| 11 | `&&` | Left to right | +| 12 | `||` | Left to right | +| 13 | `=` `+=` `-=` `*=` `/=` | Right to left | + +### 14.2 Operator Summary + +#### Arithmetic Operators +```rust ++ Addition +- Subtraction +* Multiplication +/ Division +% Modulo +``` + +#### Comparison Operators +```rust +== Equal +!= Not equal +< Less than +> Greater than +<= Less than or equal +>= Greater than or equal +``` + +#### Logical Operators +```rust +&& Logical AND +|| Logical OR +! Logical NOT +``` + +#### Bitwise Operators +```rust +& Bitwise AND +| Bitwise OR +^ Bitwise XOR +~ Bitwise NOT +<< Left shift +>> Right shift +``` + +#### Memory Operators +```rust +& Address-of +* Dereference +``` + +#### Assignment Operators +```rust += Assignment ++= Add and assign +-= Subtract and assign +*= Multiply and assign +/= Divide and assign +``` + +--- + +## 15. Complete Example Programs + +### 15.1 Hello World + +```rust +include print: "./lib/io/print.dsa"; + +fn main() -> void { + print::println("Hello, world!"); +} +``` + +### 15.2 Vector Implementation + +```rust +include alloc: "./lib/memory/alloc.dsa"; + +class Vec { + priv field data: &T; + priv field len: u32; + priv field capacity: u32; + + pub const DEFAULT_CAPACITY: u32 = 16; + + pub fn new(capacity: u32) -> Vec { + let size: u32 = capacity * 4; // Assume sizeof(T) = 4 + return Vec { + data: alloc::malloc(size) as &T, + len: 0, + capacity: capacity + }; + } + + pub fn push(self: &Vec, item: T) { + if self.len >= self.capacity { + self.resize(); + } + *(self.data + self.len) = item; + self.len = self.len + 1; + } + + pub fn get(self: &Vec, index: u32) -> T { + return *(self.data + index); + } + + pub fn len(self: &Vec) -> u32 { + return self.len; + } + + priv fn resize(self: &Vec) { + let new_capacity: u32 = self.capacity * 2; + let new_size: u32 = new_capacity * 4; + let new_data: &T = alloc::malloc(new_size) as &T; + + let mut i: u32 = 0; + while i < self.len { + *(new_data + i) = *(self.data + i); + i = i + 1; + } + + alloc::free(self.data as u32); + self.data = new_data; + self.capacity = new_capacity; + } + + pub fn drop(self: &Vec) { + alloc::free(self.data as u32); + } +} + +fn main() -> void { + let vec: Vec = Vec::new(10); + defer vec.drop(); + + vec.push(1); + vec.push(2); + vec.push(3); + + let mut i: u32 = 0; + while i < vec.len() { + let value: u32 = vec.get(i); + // Use value... + i = i + 1; + } +} +``` + +### 15.3 Resource Management Example + +```rust +include alloc: "./lib/memory/alloc.dsa"; +include print: "./lib/io/print.dsa"; + +class File { + priv field handle: u32; + priv field is_open: bool; + + pub fn open(path: str) -> File { + let handle: u32 = 0; // Platform-specific open + return File { handle: handle, is_open: true }; + } + + pub fn write(self: &File, data: str) { + if !self.is_open { + return; + } + // Write implementation... + } + + pub fn close(self: &File) { + if self.is_open { + // Platform-specific close + self.is_open = false; + } + } +} + +fn process_file(path: str) -> bool { + let file: File = File::open(path); + defer file.close(); // Ensures file is closed on all exit paths + + let buffer: u32 = alloc::malloc(1024); + defer alloc::free(buffer); + + if !file.is_open { + print::println("Failed to open file"); + return false; // Defers run: free(buffer), file.close() + } + + file.write("Hello, file!"); + + return true; // Defers run: free(buffer), file.close() +} + +fn main() -> void { + let success: bool = process_file("output.txt"); + + if success { + print::println("File processed successfully"); + } else { + print::println("File processing failed"); + } +} +``` + +--- + +## 16. Grammar Summary + +### 16.1 EBNF Grammar + +```ebnf +program = { import_statement | class_definition | function_definition } + +import_statement = "include" identifier ":" string_literal ";" + +class_definition = "class" identifier [ generic_params ] "{" { class_member } "}" + +class_member = field_declaration + | const_declaration + | static_declaration + | method_declaration + +field_declaration = visibility "field" identifier ":" type ";" + +const_declaration = visibility "const" IDENTIFIER ":" type "=" expression ";" + +static_declaration = visibility "static" "mut" identifier ":" type "=" expression ";" + +method_declaration = visibility "fn" identifier [ generic_params ] "(" parameters ")" [ "->" type ] block + +function_definition = "fn" identifier [ generic_params ] "(" parameters ")" [ "->" type ] block + +generic_params = "<" identifier { "," identifier } ">" + +parameters = [ parameter { "," parameter } ] + +parameter = identifier ":" type + +visibility = "pub" | "priv" + +type = primitive_type + | pointer_type + | array_type + | generic_type + | identifier + +primitive_type = "u8" | "u16" | "u32" | "u64" + | "i8" | "i16" | "i32" | "i64" + | "bool" | "str" | "void" + +pointer_type = "&" type + +array_type = "[" type ";" integer_literal "]" + +generic_type = identifier "<" type { "," type } ">" + +block = "{" { statement } "}" + +statement = let_statement + | expression_statement + | if_statement + | while_statement + | loop_statement + | return_statement + | defer_statement + | break_statement + | continue_statement + +let_statement = "let" [ "mut" ] identifier ":" type "=" expression ";" + +defer_statement = "defer" expression ";" + +if_statement = "if" expression block [ "else" ( if_statement | block ) ] + +while_statement = "while" expression block + +loop_statement = "loop" block + +return_statement = "return" [ expression ] ";" + +break_statement = "break" ";" + +continue_statement = "continue" ";" + +expression_statement = expression ";" + +expression = assignment_expression + +assignment_expression = logical_or_expression [ assignment_operator assignment_expression ] + +assignment_operator = "=" | "+=" | "-=" | "*=" | "/=" + +logical_or_expression = logical_and_expression { "||" logical_and_expression } + +logical_and_expression = equality_expression { "&&" equality_expression } + +equality_expression = relational_expression { equality_operator relational_expression } + +equality_operator = "==" | "!=" + +relational_expression = bitwise_or_expression { relational_operator bitwise_or_expression } + +relational_operator = "<" | ">" | "<=" | ">=" + +bitwise_or_expression = bitwise_xor_expression { "|" bitwise_xor_expression } + +bitwise_xor_expression = bitwise_and_expression { "^" bitwise_and_expression } + +bitwise_and_expression = shift_expression { "&" shift_expression } + +shift_expression = additive_expression { shift_operator additive_expression } + +shift_operator = "<<" | ">>" + +additive_expression = multiplicative_expression { additive_operator multiplicative_expression } + +additive_operator = "+" | "-" + +multiplicative_expression = cast_expression { multiplicative_operator cast_expression } + +multiplicative_operator = "*" | "/" | "%" + +cast_expression = unary_expression [ "as" type ] + +unary_expression = postfix_expression + | unary_operator unary_expression + +unary_operator = "!" | "~" | "&" | "*" | "-" + +postfix_expression = primary_expression { postfix_operator } + +postfix_operator = "." identifier [ call_suffix ] + | "::" identifier [ call_suffix ] + | "[" expression "]" + | call_suffix + +call_suffix = "(" [ arguments ] ")" + +arguments = expression { "," expression } + +primary_expression = identifier + | literal + | "(" expression ")" + | struct_literal + +struct_literal = identifier "{" [ field_init { "," field_init } ] "}" + +field_init = identifier ":" expression + +literal = integer_literal + | string_literal + | boolean_literal + +boolean_literal = "true" | "false" +``` + +--- + +## Appendix A: Differences from Rust + +While DSC uses Rust-like syntax, there are key differences: + +| Feature | Rust | DSC | +|---------|------|-----| +| **Encapsulation** | `struct` + `impl` | `class` | +| **Field syntax** | `field: Type` | `field field: Type;` | +| **References** | Borrow-checked `&T` | Raw pointers `&T` | +| **Ownership** | Compile-time borrow checker | Manual with `defer` | +| **Traits** | Supported | Not supported | +| **Lifetimes** | Supported | Not supported | +| **Pattern matching** | Supported | Not supported | +| **Enums** | Sum types with data | Not yet supported | +| **Modules** | Native | Assembly imports only | + +--- + +## Appendix B: Standard Library Conventions + +By convention, standard library modules should provide: + +### Allocation Module (`alloc`) +```rust +include alloc: "./lib/memory/alloc.dsa"; + +// Functions: +// alloc::malloc(size: u32) -> u32 +// alloc::free(ptr: u32) -> void +``` + +### Print Module (`print`) +```rust +include print: "./lib/io/print.dsa"; + +// Functions: +// print::print(msg: str) -> void +// print::println(msg: str) -> void +// print::print_num(n: u32) -> void +// print::print_hex(n: u32) -> void +``` + +--- + +*End of DSC Language Specification v1.0* -- 2.47.3 From 8361833b1c1fa29c683a13e6e0db431894e5015f Mon Sep 17 00:00:00 2001 From: zxq5 Date: Tue, 10 Feb 2026 16:33:32 +0000 Subject: [PATCH 46/53] broken commit, started working on scopes --- compiler/src/backend/dsa/codegen.rs | 48 +++++++++++++++++- compiler/src/backend/dsa/mod.rs | 3 ++ compiler/src/backend/dsa/registers.rs | 73 ++++++++++++++++++++++++++- compiler/src/model.rs | 8 +++ 4 files changed, 129 insertions(+), 3 deletions(-) diff --git a/compiler/src/backend/dsa/codegen.rs b/compiler/src/backend/dsa/codegen.rs index 4ca3c8e..ae85ba1 100644 --- a/compiler/src/backend/dsa/codegen.rs +++ b/compiler/src/backend/dsa/codegen.rs @@ -5,12 +5,14 @@ use std::time::SystemTime; use chrono::{DateTime, Local}; use super::registers::RegisterAllocator; +use crate::backend::dsa::instruction::CodeGen; use crate::backend::dsa::registers::Register; +use crate::backend::dsa::variable::{ScopeKind, ScopeManager}; use crate::{block, comment, dsa}; use crate::model::{ AssignmentOperator, BinaryOperator, Call, CompilerError, ConstExpr, Declaration, - Dependency, Expression, Program, Statement, TypeId, UnaryOperator, Variable, + Dependency, Expression, Name, Program, Statement, TypeId, UnaryOperator, Variable, }; pub struct CodeGenerator { @@ -52,6 +54,50 @@ impl CodeGenerator { } pub fn generate(&mut self) -> Result { + // let mut codegen = CodeGen::new(); + // let mut scope_mgr = ScopeManager::new(); + // scope_mgr.enter_scope(ScopeKind::Global); + // scope_mgr.enter_scope(ScopeKind::Function); + + // let point_type = TypeId::Struct { + // name: Name::new("Point", None), + // fields: vec![TypeId::U32, TypeId::U32], + // generics: vec![], + // }; + + // let p_var = scope_mgr.declare_var("p".into(), point_type)?; + // scope_mgr.allocate_var(p_var, false)?; // Force stack (struct too big) + + // // Access p.x (offset 0, size 4) + // let (x_reg, load_block) = scope_mgr.access_member(p_var, 0, 4)?; + // codegen.emit_block(load_block); + + // // Store to p.y (offset 4, size 4) + // let value_reg = Register::Rg0; // assume value is here + // let store_block = scope_mgr.store_member(p_var, 4, 4, value_reg)?; + // codegen.emit_block(store_block); + + // scope_mgr.enter_scope(ScopeKind::Loop); + // let pointer = scope_mgr + // .declare_var("pointer".into(), TypeId::Ptr(Box::new(TypeId::U32)))?; + // scope_mgr.allocate_var(pointer, true)?; + + // scope_mgr.enter_scope(ScopeKind::Function); + // let var2 = scope_mgr.declare_var("var2".into(), TypeId::U32)?; + // scope_mgr.allocate_var(var2, true)?; + // let array = scope_mgr.declare_var( + // "pointer".into(), + // TypeId::Array { + // r#type: Box::new(TypeId::U32), + // size: 10, + // }, + // )?; + // scope_mgr.allocate_var(array, false)?; + + // println!("{}", scope_mgr); + + // return Ok(String::new()); + // always include the print library for debugging! self.include("print", "./lib/io/print.dsa"); diff --git a/compiler/src/backend/dsa/mod.rs b/compiler/src/backend/dsa/mod.rs index 8ee13d7..9b7ed48 100644 --- a/compiler/src/backend/dsa/mod.rs +++ b/compiler/src/backend/dsa/mod.rs @@ -1,7 +1,10 @@ use crate::model::{CompilerError, Program}; mod codegen; +mod instruction; mod registers; +mod scope; +mod variable; pub fn generate_code(ast: &Program) -> Result { let mut codegen = codegen::CodeGenerator::new(ast.clone()); diff --git a/compiler/src/backend/dsa/registers.rs b/compiler/src/backend/dsa/registers.rs index 2436b8c..7539182 100644 --- a/compiler/src/backend/dsa/registers.rs +++ b/compiler/src/backend/dsa/registers.rs @@ -501,6 +501,7 @@ impl RegisterAllocator { #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum Register { + // general purpose Rg0 = 0, Rg1 = 1, Rg2 = 2, @@ -517,8 +518,68 @@ pub enum Register { Rgd = 13, Rge = 14, Rgf = 15, - Zero = 16, - Null = 17, + + // special + Bpr, + Spr, + Ret, + Acc, + + // read only + Pcx, + Zero, + + // null + Null, +} + +impl Register { + pub fn get_gp() -> [Register; 16] { + [ + Register::Rg0, + Register::Rg1, + Register::Rg2, + Register::Rg3, + Register::Rg4, + Register::Rg5, + Register::Rg6, + Register::Rg7, + Register::Rg8, + Register::Rg9, + Register::Rga, + Register::Rgb, + Register::Rgc, + Register::Rgd, + Register::Rge, + Register::Rgf, + ] + } + + pub fn is_gp(&self) -> bool { + (*self as u8) < 16 + } + + pub fn from_index(idx: usize) -> Register { + match idx { + 0 => Register::Rg0, + 1 => Register::Rg1, + 2 => Register::Rg2, + 3 => Register::Rg3, + 4 => Register::Rg4, + 5 => Register::Rg5, + 6 => Register::Rg6, + 7 => Register::Rg7, + 8 => Register::Rg8, + 9 => Register::Rg9, + 10 => Register::Rga, + 11 => Register::Rgb, + 12 => Register::Rgc, + 13 => Register::Rgd, + 14 => Register::Rge, + 15 => Register::Rgf, + _ => unreachable!("this function shouldn't ever be called with idx>15"), + } + } } impl fmt::Display for Register { @@ -540,7 +601,15 @@ impl fmt::Display for Register { Self::Rgd => write!(f, "rgd"), Self::Rge => write!(f, "rge"), Self::Rgf => write!(f, "rgf"), + + Self::Acc => write!(f, "acc"), + Self::Ret => write!(f, "ret"), + Self::Bpr => write!(f, "bpr"), + Self::Spr => write!(f, "spr"), + Self::Zero => write!(f, "zero"), + Self::Pcx => write!(f, "pcx"), + Self::Null => write!(f, "null"), } } diff --git a/compiler/src/model.rs b/compiler/src/model.rs index 83969b7..6b00fa4 100644 --- a/compiler/src/model.rs +++ b/compiler/src/model.rs @@ -19,6 +19,14 @@ pub struct Name { pub name: String, pub namespace: Option, } +impl Name { + pub fn new(name: impl Into, namespace: Option) -> Self { + Self { + name: name.into(), + namespace, + } + } +} #[derive(Debug, Clone)] pub struct Program { -- 2.47.3 From 75ad04cf9535bf4852de7b8c8c8d9326255fbb54 Mon Sep 17 00:00:00 2001 From: zxq5 Date: Tue, 10 Feb 2026 16:37:33 +0000 Subject: [PATCH 47/53] forgot to commit these --- compiler/src/backend/dsa/instruction.rs | 610 ++++++++++++++++++++++++ compiler/src/backend/dsa/scope.rs | 7 + compiler/src/backend/dsa/variable.rs | 186 ++++++++ 3 files changed, 803 insertions(+) create mode 100644 compiler/src/backend/dsa/instruction.rs create mode 100644 compiler/src/backend/dsa/scope.rs create mode 100644 compiler/src/backend/dsa/variable.rs diff --git a/compiler/src/backend/dsa/instruction.rs b/compiler/src/backend/dsa/instruction.rs new file mode 100644 index 0000000..e419b62 --- /dev/null +++ b/compiler/src/backend/dsa/instruction.rs @@ -0,0 +1,610 @@ +use std::fmt; + +use crate::{ + backend::dsa::registers::Register, + model::{CompilerError, Expression}, +}; + +pub struct CodeGen { + // For building the final program + program: InsBlock, + + // For generating temporary blocks + label_counter: usize, + stack_offset: i32, +} + +impl CodeGen { + pub fn new() -> Self { + Self { + program: InsBlock::new(), + label_counter: 0, + stack_offset: 0, + } + } + + /// Emit directly to program (for top-level constructs) + pub fn emit(&mut self, instr: Instruction) { + self.program.push(instr); + } + + /// Emit a block to program + pub fn emit_block(&mut self, block: InsBlock) { + self.program.append(block); + } + + /// Build expression (returns block for composition) + pub fn build_expr(&mut self, expr: &Expression) -> Result { + // ... returns InstrBlock + todo!() + } + + /// Get final output + pub fn finish(mut self) -> String { + // Optimize before final output + // self.program.remove_dead_code(); + // self.program.optimize_peephole(); + + self.program + .instructions + .iter() + .map(|i| i.to_string()) + .collect::>() + .join("\n") + } +} + +pub struct InsBlock { + instructions: Vec, +} + +impl InsBlock { + pub fn new() -> Self { + Self { + instructions: vec![], + } + } + + pub fn push(&mut self, instr: Instruction) { + self.instructions.push(instr); + } + + pub fn append(&mut self, mut other: Self) { + self.instructions.append(&mut other.instructions); + } + + pub fn extend(&mut self, instrs: impl IntoIterator) { + self.instructions.extend(instrs); + } + + pub fn is_empty(&self) -> bool { + self.instructions.is_empty() + } + + pub fn len(&self) -> usize { + self.instructions.len() + } + + pub fn iter(&self) -> impl Iterator { + self.instructions.iter() + } +} + +pub enum Instruction { + // Labels and comments + Label(Label), + Comment(String), + + // Data movement + Mov { + src: Register, + dest: Register, + }, + Movs { + src: Register, + dest: Register, + }, + + // Memory operations + Ldb { + addr: MemOperand, + dest: Register, + }, + Ldh { + addr: MemOperand, + dest: Register, + }, + Ldw { + addr: MemOperand, + dest: Register, + }, + Stb { + src: Register, + addr: MemOperand, + }, + Sth { + src: Register, + addr: MemOperand, + }, + Stw { + src: Register, + addr: MemOperand, + }, + + // Immediate loads + Lli { + imm: Imm, + dest: Register, + }, + Lui { + imm: Imm, + dest: Register, + }, + + // Arithmetic + Add { + src1: Register, + src2: Register, + dest: Register, + }, + Sub { + src1: Register, + src2: Register, + dest: Register, + }, + IAdd { + src: Register, + imm: Imm, + dest: Option, + }, + ISub { + src: Register, + imm: Imm, + dest: Option, + }, + Inc { + reg: Register, + }, + Dec { + reg: Register, + }, + + // Bitwise + And { + src1: Register, + src2: Register, + dest: Register, + }, + Or { + src1: Register, + src2: Register, + dest: Register, + }, + Xor { + src1: Register, + src2: Register, + dest: Register, + }, + Not { + src: Register, + dest: Register, + }, + Nand { + src1: Register, + src2: Register, + dest: Register, + }, + Nor { + src1: Register, + src2: Register, + dest: Register, + }, + Xnor { + src1: Register, + src2: Register, + dest: Register, + }, + + // Shifts + Shl { + src1: Register, + r_shamt: Register, + i_shamt: u16, + dest: Register, + }, + Shr { + src1: Register, + rsh: Register, + ish: u16, + dest: Register, + }, + + // Comparison + Cmp { + reg1: Register, + reg2: Register, + }, + + // Jumps + Jmp { + target: Label, + }, + Jeq { + target: Label, + }, + Jne { + target: Label, + }, + Jgt { + target: Label, + }, + Jge { + target: Label, + }, + Jlt { + target: Label, + }, + Jle { + target: Label, + }, + + // Stack + Push { + reg: Register, + }, + Pop { + reg: Register, + }, + + // Function calls + Call { + target: String, + }, // namespace::function + Return, + + // System + Hlt, + Nop, + Int { + code: u8, + }, +} + +impl fmt::Display for Instruction { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::Label(l) => write!(f, "{}:", l), + Self::Comment(c) => write!(f, "; {}", c), + + Self::Mov { src, dest } => write!(f, " mov {}, {}", src, dest), + Self::Movs { src, dest } => write!(f, " movs {}, {}", src, dest), + + Self::Ldb { addr, dest } => { + write!(f, " ldb {}, {}", format_mem_operand(addr), dest) + } + Self::Ldh { addr, dest } => { + write!(f, " ldh {}, {}", format_mem_operand(addr), dest) + } + Self::Ldw { addr, dest } => { + write!(f, " ldw {}, {}", format_mem_operand(addr), dest) + } + // Self::Ldbs { addr, dest } => { + // write!(f, " ldbs {}, {}", format_mem_operand(addr), dest) + // } + // Self::Ldhs { addr, dest } => { + // write!(f, " ldhs {}, {}", format_mem_operand(addr), dest) + // } + // Self::Ldws { addr, dest } => { + // write!(f, " ldws {}, {}", format_mem_operand(addr), dest) + // } + Self::Stb { src, addr } => { + write!(f, " stb {}, {}", src, format_mem_operand(addr)) + } + Self::Sth { src, addr } => { + write!(f, " sth {}, {}", src, format_mem_operand(addr)) + } + Self::Stw { src, addr } => { + write!(f, " stw {}, {}", src, format_mem_operand(addr)) + } + + Self::Lli { imm, dest } => write!(f, " lli {}, {}", imm, dest), + Self::Lui { imm, dest } => write!(f, " lui {}, {}", imm, dest), + + // arithmetic + Self::Add { src1, src2, dest } => { + write!(f, " add {}, {}, {}", src1, src2, dest) + } + Self::Sub { src1, src2, dest } => { + write!(f, " sub {}, {}, {}", src1, src2, dest) + } + Self::And { src1, src2, dest } => { + write!(f, " and {}, {}, {}", src1, src2, dest) + } + Self::Or { src1, src2, dest } => { + write!(f, " or {}, {}, {}", src1, src2, dest) + } + Self::Nand { src1, src2, dest } => { + write!(f, " nand {}, {}, {}", src1, src2, dest) + } + Self::Xor { src1, src2, dest } => { + write!(f, " xor {}, {}, {}", src1, src2, dest) + } + Self::Nor { src1, src2, dest } => { + write!(f, " nor {}, {}, {}", src1, src2, dest) + } + Self::Not { src, dest } => { + write!(f, " not {} {}", src, dest) + } + Self::Xnor { src1, src2, dest } => { + write!(f, " xnor {}, {}, {}", src1, src2, dest) + } + Self::IAdd { src, imm, dest } => { + if let Some(d) = dest { + write!(f, " iadd {}, {}, {}", src, imm, d) + } else { + write!(f, " iadd {}, {}", src, imm) + } + } + Self::ISub { src, imm, dest } => { + if let Some(d) = dest { + write!(f, " isub {}, {}, {}", src, imm, d) + } else { + write!(f, " isub {}, {}", src, imm) + } + } + + // shift instructions + Self::Shl { + src1, + r_shamt, + i_shamt, + dest, + } => { + write!(f, " shl {}, {}, {}, {}", src1, r_shamt, i_shamt, dest) + } + Self::Shr { + src1, + rsh: r_shamt, + ish: i_shamt, + dest, + } => { + write!(f, " shl {}, {}, {}, {}", src1, r_shamt, i_shamt, dest) + } + + // increment instructions + Self::Inc { reg } => write!(f, " inc {}", reg), + Self::Dec { reg } => write!(f, " dec {}", reg), + + Self::Cmp { reg1, reg2 } => write!(f, " cmp {}, {}", reg1, reg2), + + // jump instructions + Self::Jmp { target } => write!(f, " jmp {}", target), + Self::Jeq { target } => write!(f, " jeq {}", target), + Self::Jne { target } => write!(f, " jne {}", target), + Self::Jgt { target } => write!(f, " jgt {}", target), + Self::Jge { target } => write!(f, " jge {}", target), + Self::Jlt { target } => write!(f, " jlt {}", target), + Self::Jle { target } => write!(f, " jle {}", target), + + // stack pseudoinstructions + Self::Push { reg } => write!(f, " push {}", reg), + Self::Pop { reg } => write!(f, " pop {}", reg), + + // call & return pseudoinstructions + Self::Call { target } => write!(f, " call {}", target), + Self::Return => write!(f, " return"), + + // misc instructions + Self::Int { code } => write!(f, " int {}", code), + Self::Hlt => write!(f, " hlt"), + Self::Nop => write!(f, " nop"), + } + } +} + +impl Instruction { + // Movement + pub fn mov(src: Register, dest: Register) -> Self { + Self::Mov { src, dest } + } + + // Memory loads + pub fn ldw_reg(base: Register, dest: Register) -> Self { + Self::Ldw { + addr: MemOperand::RegIndirect(base), + dest, + } + } + + pub fn ldw_reg_offset(base: Register, offset: i32, dest: Register) -> Self { + Self::Ldw { + addr: MemOperand::RegOffset(base, offset), + dest, + } + } + + pub fn ldw_label(label: impl Into