From 5a78e254697ad1a45a41b6637f2b138ee176c7f3 Mon Sep 17 00:00:00 2001 From: Devin Bidwell Date: Mon, 17 Nov 2025 21:20:32 -0700 Subject: [PATCH] Fixed exponent expression not being in right-first order of operations --- Cargo.lock | 12 ++ libs/compiler/Cargo.toml | 5 + libs/compiler/src/lib.rs | 2 + libs/compiler/src/test/mod.rs | 67 ++++++++++ libs/compiler/src/v2.rs | 170 ++++++++++++++++++++++++++ libs/compiler/src/variable_manager.rs | 33 +++++ libs/parser/src/lib.rs | 16 +-- 7 files changed, 297 insertions(+), 8 deletions(-) create mode 100644 libs/compiler/src/v2.rs create mode 100644 libs/compiler/src/variable_manager.rs diff --git a/Cargo.lock b/Cargo.lock index 916ef8f..fbabf72 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -245,8 +245,11 @@ checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" name = "compiler" version = "0.1.0" dependencies = [ + "anyhow", + "indoc", "parser", "quick-error", + "tokenizer", ] [[package]] @@ -344,6 +347,15 @@ dependencies = [ "hashbrown 0.16.0", ] +[[package]] +name = "indoc" +version = "2.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" +dependencies = [ + "rustversion", +] + [[package]] name = "inventory" version = "0.3.21" diff --git a/libs/compiler/Cargo.toml b/libs/compiler/Cargo.toml index edf4fd3..122c21c 100644 --- a/libs/compiler/Cargo.toml +++ b/libs/compiler/Cargo.toml @@ -6,3 +6,8 @@ edition = "2024" [dependencies] quick-error = { workspace = true } parser = { path = "../parser" } +tokenizer = { path = "../tokenizer" } + +[dev-dependencies] +anyhow = { version = "1.0" } +indoc = { version = "2.0" } diff --git a/libs/compiler/src/lib.rs b/libs/compiler/src/lib.rs index 2a490af..98910a9 100644 --- a/libs/compiler/src/lib.rs +++ b/libs/compiler/src/lib.rs @@ -1,5 +1,7 @@ #[cfg(test)] mod test; +mod v2; +mod variable_manager; use parser::Parser as ASTParser; use parser::sys_call::SysCall; diff --git a/libs/compiler/src/test/mod.rs b/libs/compiler/src/test/mod.rs index e69de29..ef883df 100644 --- a/libs/compiler/src/test/mod.rs +++ b/libs/compiler/src/test/mod.rs @@ -0,0 +1,67 @@ +use super::v2::Compiler; +use crate::v2::CompilerConfig; +use indoc::indoc; +use parser::Parser; +use std::io::BufWriter; +use tokenizer::Tokenizer; + +macro_rules! output { + ($input:expr) => { + String::from_utf8($input.into_inner()?)? + }; +} + +macro_rules! compile { + ($source:expr) => {{ + let mut writer = BufWriter::new(Vec::new()); + let compiler = Compiler::new( + Parser::new(Tokenizer::from(String::from($source))), + &mut writer, + None, + ); + compiler.compile()?; + output!(writer) + }}; + + (debug $source:expr) => {{ + let mut writer = BufWriter::new(Vec::new()); + let compiler = Compiler::new( + Parser::new(Tokenizer::from(String::from($source))), + &mut writer, + Some(CompilerConfig { + debug: true, + ..Default::default() + }), + ); + compiler.compile()?; + output!(writer) + }}; +} + +#[test] +fn test_function_declaration_with_register_params() -> anyhow::Result<()> { + let compiled = compile!(debug r#" + // This is a test function declaration with no body + fn doSomething(arg1, arg2) { + }; + "#); + + assert_eq!( + compiled, + indoc! {" + j main + doSomething: + push ra + push r4 + move r4 r0 #arg1 + push r5 + move r5 r1 #arg2 + pop r5 + pop r4 + pop ra + j ra + "} + ); + + Ok(()) +} diff --git a/libs/compiler/src/v2.rs b/libs/compiler/src/v2.rs new file mode 100644 index 0000000..7a8d4ce --- /dev/null +++ b/libs/compiler/src/v2.rs @@ -0,0 +1,170 @@ +use parser::{ + Parser as ASTParser, + tree_node::{BlockExpression, Expression, FunctionExpression}, +}; +use quick_error::quick_error; +use std::{ + collections::HashMap, + io::{BufWriter, Write}, +}; + +use crate::variable_manager::VariableScope; + +quick_error! { + #[derive(Debug)] + pub enum CompilerError { + ParseError(error: parser::ParseError) { + from() + } + IoError(error: std::io::Error) { + from() + } + DuplicateFunction(func_name: String) { + display("{func_name} has already been defined") + } + } +} + +#[derive(Default)] +pub struct CompilerConfig { + pub debug: bool, +} + +pub struct Compiler<'a, W: std::io::Write> { + parser: ASTParser, + /// Max stack size for the program is by default 512. + variable_scope: Vec>, + function_locations: HashMap, + devices: HashMap, + output: &'a mut BufWriter, + current_line: usize, + declared_main: bool, + config: CompilerConfig, +} + +impl<'a, W: std::io::Write> Compiler<'a, W> { + pub fn new( + parser: ASTParser, + writer: &'a mut BufWriter, + config: Option, + ) -> Self { + Self { + parser, + variable_scope: Vec::new(), + function_locations: HashMap::new(), + devices: HashMap::new(), + output: writer, + current_line: 1, + declared_main: false, + config: config.unwrap_or_default(), + } + } + + pub fn compile(mut self) -> Result<(), CompilerError> { + let expr = self.parser.parse_all()?; + + let Some(expr) = expr else { return Ok(()) }; + + self.write_output("j main")?; + self.expression(expr, &mut VariableScope::default()) + } + + fn write_output(&mut self, output: impl Into) -> Result<(), CompilerError> { + self.output.write_all(output.into().as_bytes())?; + self.output.write_all(b"\n")?; + self.current_line += 1; + Ok(()) + } + + fn expression<'v>( + &mut self, + expr: Expression, + scope: &mut VariableScope<'v>, + ) -> Result<(), CompilerError> { + match expr { + Expression::Function(expr_func) => self.expression_function(expr_func, scope)?, + Expression::Block(expr_block) => { + self.expression_block(expr_block, &mut VariableScope::scoped(&scope))? + } + _ => todo!(), + }; + + Ok(()) + } + + fn expression_block<'v>( + &mut self, + mut expr: BlockExpression, + scope: &mut VariableScope<'v>, + ) -> Result<(), CompilerError> { + // First, sort the expressions to ensure functions are hoisted + expr.0.sort_by(|a, b| { + if matches!(b, Expression::Function(_)) && matches!(a, Expression::Function(_)) { + std::cmp::Ordering::Equal + } else if matches!(a, Expression::Function(_)) { + std::cmp::Ordering::Less + } else { + std::cmp::Ordering::Greater + } + }); + + for expr in expr.0 { + if !self.declared_main && !matches!(expr, Expression::Function(_)) { + self.write_output("main:")?; + self.declared_main = true; + } + + self.expression(expr, scope)?; + } + + Ok(()) + } + + fn expression_function<'v>( + &mut self, + expr: FunctionExpression, + scope: &mut VariableScope<'v>, + ) -> Result<(), CompilerError> { + let FunctionExpression { + name, + arguments, + body, + } = expr; + + if self.function_locations.contains_key(&name) { + return Err(CompilerError::DuplicateFunction(name)); + } + + self.function_locations + .insert(name.clone(), self.current_line); + + self.write_output(format!("{}:", name))?; + self.write_output("push ra")?; + + let mut block_scope = VariableScope::scoped(&scope); + + for (index, var_name) in arguments.iter().enumerate() { + self.write_output(format!("push r{}", index + 4))?; + self.write_output(format!( + "move r{} r{} {}", + index + 4, + index, + if self.config.debug { + format!("#{}", var_name) + } else { + "".into() + } + ))?; + } + + self.expression_block(body, &mut block_scope)?; + + for (indx, _) in arguments.iter().enumerate().rev() { + self.write_output(format!("pop r{}", indx + 4))?; + } + + self.write_output("pop ra")?; + self.write_output("j ra")?; + Ok(()) + } +} diff --git a/libs/compiler/src/variable_manager.rs b/libs/compiler/src/variable_manager.rs new file mode 100644 index 0000000..56189c6 --- /dev/null +++ b/libs/compiler/src/variable_manager.rs @@ -0,0 +1,33 @@ +// r0 - r3 : function arguments +// r4 - r9 : temporary variables +// r10 - r15 : persistant variables + +use std::collections::HashMap; + +enum VarType { + /// Represents a parameter register (r0 - r3) + Func(u8), + /// Represents a temporary register (r4 - r8) + Temp(u8), + /// Represents a variable register (r9 - r15) + Persist(u8), + /// Represents a stack pointer offset for a given variable + Stack(u16), +} + +#[derive(Default)] +pub struct VariableScope<'a> { + stack: Vec>, + temp: HashMap<&'a str, u8>, + persist: HashMap<&'a str, u8>, + parent: Option<&'a VariableScope<'a>>, +} + +impl<'a> VariableScope<'a> { + pub fn scoped(parent: &'a VariableScope<'a>) -> Self { + Self { + parent: Option::Some(parent), + ..Default::default() + } + } +} diff --git a/libs/parser/src/lib.rs b/libs/parser/src/lib.rs index ece1be2..d5c476f 100644 --- a/libs/parser/src/lib.rs +++ b/libs/parser/src/lib.rs @@ -361,25 +361,22 @@ impl Parser { // Every time we find a valid operator, we pop 2 off the expressions and add one back. // This means that we need to keep track of the current iteration to ensure we are // removing the correct expressions from the vector - let mut current_iteration = 0; // Loop through operators, and build the binary expressions for exponential operators only - for (i, operator) in operators.iter().enumerate() { + for (i, operator) in operators.iter().enumerate().rev() { if operator == &Symbol::Exp { - let index = i - current_iteration; - let left = expressions.remove(index); - let right = expressions.remove(index); + let right = expressions.remove(i + 1); + let left = expressions.remove(i); expressions.insert( - index, + i, Expression::Binary(BinaryExpression::Exponent(boxed!(left), boxed!(right))), ); - current_iteration += 1; } } // remove all the exponential operators from the operators vector operators.retain(|symbol| symbol != &Symbol::Exp); - current_iteration = 0; + let mut current_iteration = 0; // Loop through operators, and build the binary expressions for multiplication and division operators for (i, operator) in operators.iter().enumerate() { @@ -982,6 +979,9 @@ mod tests { let expr = parser!("4 ** 2 + 5 ** 2").parse()?.unwrap(); assert_eq!("((4 ** 2) + (5 ** 2))", expr.to_string()); + let expr = parser!("2 ** 3 ** 4").parse()?.unwrap(); + assert_eq!("(2 ** (3 ** 4))", expr.to_string()); + let expr = parser!("45 * 2 - 15 / 5 + 5 ** 2").parse()?.unwrap(); assert_eq!("(((45 * 2) - (15 / 5)) + (5 ** 2))", expr.to_string());