Compare commits

9 Commits

Author SHA1 Message Date
zxq5 a1099249e9 updated roadmap 2026-02-04 01:59:50 +00:00
zxq5 cb65a928c8 fixed bug where stack inspector shows incorrect addresses 2026-02-04 01:59:43 +00:00
zxq5 fa8aa1cd29 integrated compiler in DSA editor 2026-02-04 01:58:55 +00:00
zxq5 7780f5804f deleted old files and modified some dsa source files 2026-02-04 01:58:37 +00:00
zxq5 889ee8ef71 wrote dsa/dsc code examples including an allocator 2026-02-04 01:58:03 +00:00
zxq5 dd20401ad6 added basic logging to common
TODO: improve logging
2026-02-04 01:57:40 +00:00
zxq5 f4933b55fb forgot to commit this 2026-02-04 01:57:18 +00:00
zxq5 14a04a524c added support for DSA libraries to compiler and made some optimisations.
provided an API for the editor to use.
2026-02-04 01:56:58 +00:00
zxq5 f25db6c8fd updated assembler logging 2026-02-04 01:56:15 +00:00
29 changed files with 836 additions and 536 deletions
+1 -2
View File
@@ -14,8 +14,7 @@ pub fn codegen(nodes: Vec<Node>) -> Result<Vec<Instruction>, AssembleError> {
instructions.push(build_instruction(&node)?);
}
println!("------------------------");
log("Compilation Success ✅");
log("Assembly Successful ✅");
Ok(instructions)
}
+1 -1
View File
@@ -65,7 +65,7 @@ pub fn lexer(mut program: String, module: u64) -> Result<Vec<Token>, AssembleErr
}
}
println!("{:#?}", tokens);
// println!("{:#?}", tokens);
Ok(tokens)
}
+8 -16
View File
@@ -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<Vec<Instruction>, 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);
+1
View File
@@ -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.
+4
View File
@@ -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}");
}
+1
View File
@@ -6,3 +6,4 @@ authors.workspace = true
[dependencies]
chrono = "0.4.43"
common = { path = "../common" }
+2 -2
View File
@@ -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
+34 -16
View File
@@ -25,14 +25,14 @@ pub struct CodeGenerator {
static GLOBAL_METHODS: LazyLock<HashMap<&str, &str>> = 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<String>), 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 {
+87 -226
View File
@@ -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<String>,
}
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<u64, String> {
@@ -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);
}
}
+74
View File
@@ -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<dyn std::error::Error>> {
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::<Vec<_>>();
// 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(())
}
+3 -47
View File
@@ -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 <src.c> [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::<Vec<_>>();
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();
}
+31 -39
View File
@@ -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<TypeId, CompilerError> {
// 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<Name, CompilerError> {
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<Token, CompilerError> {
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<String>,
}
#[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,
+49 -26
View File
@@ -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);
// 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.variable_locations.insert(
// var_name.to_string(),
// Location::Register(source_reg.to_string()),
// );
// 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
// .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!"
);
}
}
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;
+1
View File
@@ -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"
+23
View File
@@ -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;
+3 -4
View File
@@ -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();
+121
View File
@@ -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
+32
View File
@@ -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;
}
+2 -2
View File
@@ -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:
+5 -12
View File
@@ -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
-37
View File
@@ -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
+100
View File
@@ -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
@@ -1,36 +1,3 @@
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
+43 -37
View File
@@ -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
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
+9
View File
@@ -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);
}
+172 -25
View File
@@ -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
+24 -6
View File
@@ -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 autocompletion 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 editors 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 autocompletion
- [ ] Provide dedicated DSC build command that uses the new compiler integration
---