From 5a78e254697ad1a45a41b6637f2b138ee176c7f3 Mon Sep 17 00:00:00 2001 From: Devin Bidwell Date: Mon, 17 Nov 2025 21:20:32 -0700 Subject: [PATCH 01/22] 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()); From 4e6096fd3ffb845d8b77dcd824af1ad4e67627e3 Mon Sep 17 00:00:00 2001 From: Devin Bidwell Date: Tue, 18 Nov 2025 18:30:56 -0700 Subject: [PATCH 02/22] Rename error variants --- libs/compiler/src/lib.rs | 10 +- libs/compiler/src/test/mod.rs | 38 ++++++- libs/compiler/src/v2.rs | 85 +++++++++----- libs/compiler/src/variable_manager.rs | 155 ++++++++++++++++++++++++-- libs/parser/src/lib.rs | 116 +++++++++---------- libs/tokenizer/src/lib.rs | 55 ++++----- src/lib.rs | 6 +- src/main.rs | 10 +- 8 files changed, 330 insertions(+), 145 deletions(-) diff --git a/libs/compiler/src/lib.rs b/libs/compiler/src/lib.rs index 98910a9..4d1d570 100644 --- a/libs/compiler/src/lib.rs +++ b/libs/compiler/src/lib.rs @@ -11,10 +11,12 @@ use std::cmp::Ordering; use std::collections::HashMap; use std::io::{BufWriter, Write}; +pub use v2::{Compiler, Error}; + quick_error! { #[derive(Debug)] pub enum CompileError { - ParseError(err: parser::ParseError) { + ParseError(err: parser::Error) { from() display("Parse error: {}", err) } @@ -43,7 +45,7 @@ quick_error! { } } -pub struct Compiler<'a, W: std::io::Write> { +pub struct CompilerV1<'a, W: std::io::Write> { parser: ASTParser, /// Max stack size for the program is by default 512. variable_scope: Vec>, @@ -54,7 +56,7 @@ pub struct Compiler<'a, W: std::io::Write> { declared_main: bool, } -impl<'a, W: std::io::Write> Compiler<'a, W> { +impl<'a, W: std::io::Write> CompilerV1<'a, W> { pub fn new(parser: ASTParser, writer: &'a mut BufWriter) -> Self { Self { parser, @@ -212,7 +214,7 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { self.variable_scope.push(HashMap::new()); fn perform_operation( - compiler: &mut Compiler, + compiler: &mut CompilerV1, op: &str, left: Expression, right: Expression, diff --git a/libs/compiler/src/test/mod.rs b/libs/compiler/src/test/mod.rs index ef883df..7200928 100644 --- a/libs/compiler/src/test/mod.rs +++ b/libs/compiler/src/test/mod.rs @@ -38,6 +38,36 @@ macro_rules! compile { }}; } +#[test] +fn test_function_declaration_with_spillover_params() -> anyhow::Result<()> { + let compiled = compile!(debug r#" + // we need more than 4 params to 'spill' into a stack var + fn doSomething(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) {}; + "#); + + assert_eq!( + compiled, + indoc! {" + j main + doSomething: + pop r8 #arg9 + pop r9 #arg8 + pop r10 #arg7 + pop r11 #arg6 + pop r12 #arg5 + pop r13 #arg4 + pop r14 #arg3 + pop r15 #arg2 + push ra + pop ra + sub sp 1 + j ra + "} + ); + + Ok(()) +} + #[test] fn test_function_declaration_with_register_params() -> anyhow::Result<()> { let compiled = compile!(debug r#" @@ -51,13 +81,9 @@ fn test_function_declaration_with_register_params() -> anyhow::Result<()> { indoc! {" j main doSomething: + pop r8 #arg2 + pop r9 #arg1 push ra - push r4 - move r4 r0 #arg1 - push r5 - move r5 r1 #arg2 - pop r5 - pop r4 pop ra j ra "} diff --git a/libs/compiler/src/v2.rs b/libs/compiler/src/v2.rs index 7a8d4ce..2eec79f 100644 --- a/libs/compiler/src/v2.rs +++ b/libs/compiler/src/v2.rs @@ -1,3 +1,4 @@ +use crate::variable_manager::{self, LocationRequest, VariableLocation, VariableScope}; use parser::{ Parser as ASTParser, tree_node::{BlockExpression, Expression, FunctionExpression}, @@ -8,17 +9,18 @@ use std::{ io::{BufWriter, Write}, }; -use crate::variable_manager::VariableScope; - quick_error! { #[derive(Debug)] - pub enum CompilerError { - ParseError(error: parser::ParseError) { + pub enum Error { + ParseError(error: parser::Error) { from() } IoError(error: std::io::Error) { from() } + ScopeError(error: variable_manager::Error) { + from() + } DuplicateFunction(func_name: String) { display("{func_name} has already been defined") } @@ -60,7 +62,7 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { } } - pub fn compile(mut self) -> Result<(), CompilerError> { + pub fn compile(mut self) -> Result<(), Error> { let expr = self.parser.parse_all()?; let Some(expr) = expr else { return Ok(()) }; @@ -69,7 +71,7 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { self.expression(expr, &mut VariableScope::default()) } - fn write_output(&mut self, output: impl Into) -> Result<(), CompilerError> { + fn write_output(&mut self, output: impl Into) -> Result<(), Error> { self.output.write_all(output.into().as_bytes())?; self.output.write_all(b"\n")?; self.current_line += 1; @@ -80,7 +82,7 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { &mut self, expr: Expression, scope: &mut VariableScope<'v>, - ) -> Result<(), CompilerError> { + ) -> Result<(), Error> { match expr { Expression::Function(expr_func) => self.expression_function(expr_func, scope)?, Expression::Block(expr_block) => { @@ -96,7 +98,7 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { &mut self, mut expr: BlockExpression, scope: &mut VariableScope<'v>, - ) -> Result<(), CompilerError> { + ) -> Result<(), Error> { // First, sort the expressions to ensure functions are hoisted expr.0.sort_by(|a, b| { if matches!(b, Expression::Function(_)) && matches!(a, Expression::Function(_)) { @@ -120,11 +122,13 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { Ok(()) } + /// Compile a function declaration. + /// Calees are responsible for backing up any registers they wish to use. fn expression_function<'v>( &mut self, expr: FunctionExpression, scope: &mut VariableScope<'v>, - ) -> Result<(), CompilerError> { + ) -> Result<(), Error> { let FunctionExpression { name, arguments, @@ -132,38 +136,69 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { } = expr; if self.function_locations.contains_key(&name) { - return Err(CompilerError::DuplicateFunction(name)); + return Err(Error::DuplicateFunction(name)); } self.function_locations .insert(name.clone(), self.current_line); + // Declare the function as a line identifier self.write_output(format!("{}:", name))?; - self.write_output("push ra")?; + // Create a new block scope for the function body 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() + let mut saved_variables = 0; + + // do a reverse pass to pop variables from the stack and put them into registers + for var_name in arguments + .iter() + .rev() + .take(VariableScope::PERSIST_REGISTER_COUNT as usize) + { + let loc = block_scope.add_variable(var_name, LocationRequest::Persist)?; + // we don't need to imcrement the stack offset as it's already on the stack from the + // previous scope + + match loc { + VariableLocation::Persistant(loc) => { + self.write_output(format!( + "pop r{loc} {}", + if self.config.debug { + format!("#{}", var_name) + } else { + "".into() + } + ))?; } - ))?; + VariableLocation::Stack(_) => { + unimplemented!("Attempted to save to stack without tracking in scope") + } + + _ => { + unimplemented!( + "Attempted to return a Temporary scoped variable from a Persistant request" + ) + } + } + saved_variables += 1; } + // now do a forward pass in case we have spilled into the stack. We don't need to push + // anything as they already exist on the stack, but we DO need to let our block_scope be + // aware that the variables exist on the stack (left to right) + for var_name in arguments.iter().take(arguments.len() - saved_variables) { + block_scope.add_variable(var_name, LocationRequest::Stack)?; + } + + self.write_output("push ra")?; self.expression_block(body, &mut block_scope)?; + self.write_output("pop ra")?; - for (indx, _) in arguments.iter().enumerate().rev() { - self.write_output(format!("pop r{}", indx + 4))?; + if block_scope.stack_offset() > 0 { + self.write_output(format!("sub sp {}", block_scope.stack_offset()))?; } - 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 index 56189c6..fb34802 100644 --- a/libs/compiler/src/variable_manager.rs +++ b/libs/compiler/src/variable_manager.rs @@ -1,12 +1,28 @@ -// r0 - r3 : function arguments -// r4 - r9 : temporary variables -// r10 - r15 : persistant variables +// r0 - r7 : temporary variables +// r8 - r15 : persistant variables -use std::collections::HashMap; +use quick_error::quick_error; +use std::collections::{HashMap, VecDeque}; -enum VarType { - /// Represents a parameter register (r0 - r3) - Func(u8), +const TEMP: [u8; 8] = [0, 1, 2, 3, 4, 5, 6, 7]; +const PERSIST: [u8; 8] = [8, 9, 10, 11, 12, 13, 14, 15]; + +quick_error! { + #[derive(Debug)] + pub enum Error { + DuplicateVariable(var: String) { + display("{var} already exists.") + } + UnknownVariable(var: String) { + display("{var} does not exist.") + } + Unknown(reason: String) { + display("{reason}") + } + } +} + +pub enum VarType { /// Represents a temporary register (r4 - r8) Temp(u8), /// Represents a variable register (r9 - r15) @@ -15,19 +31,136 @@ enum VarType { Stack(u16), } -#[derive(Default)] +/// A request to store a variable at a specific register type +pub enum LocationRequest { + /// Request to store a variable in a temprary register. + Temp, + /// Request to store a variable in a persistant register. + Persist, + /// Request to store a variable in the stack. + Stack, +} + +#[derive(Clone)] +pub enum VariableLocation { + Temporary(u8), + Persistant(u8), + Stack(u16), +} + pub struct VariableScope<'a> { - stack: Vec>, - temp: HashMap<&'a str, u8>, - persist: HashMap<&'a str, u8>, + temporary_vars: VecDeque, + persistant_vars: VecDeque, + var_lookup_table: HashMap, + var_stack: HashMap, + stack_offset: u16, parent: Option<&'a VariableScope<'a>>, } +impl<'a> Default for VariableScope<'a> { + fn default() -> Self { + Self { + parent: None, + stack_offset: 0, + persistant_vars: PERSIST.to_vec().into(), + temporary_vars: TEMP.to_vec().into(), + var_stack: HashMap::new(), + var_lookup_table: HashMap::new(), + } + } +} + impl<'a> VariableScope<'a> { + pub const TEMP_REGISTER_COUNT: u8 = 8; + pub const PERSIST_REGISTER_COUNT: u8 = 8; + pub fn scoped(parent: &'a VariableScope<'a>) -> Self { Self { parent: Option::Some(parent), ..Default::default() } } + + pub fn stack_offset(&self) -> u16 { + self.stack_offset + } + + pub fn stack_incr(&mut self) { + self.stack_offset += 1; + } + + /// Adds and tracks a new scoped variable. If the location you request is full, will fall back + /// to the stack. + pub fn add_variable( + &mut self, + var_name: impl Into, + location: LocationRequest, + ) -> Result { + let var_name = var_name.into(); + if self.var_lookup_table.contains_key(var_name.as_str()) { + return Err(Error::DuplicateVariable(var_name)); + } + let var_location = match location { + LocationRequest::Temp => { + if let Some(next_var) = self.temporary_vars.pop_front() { + let loc = VariableLocation::Temporary(next_var); + loc + } else { + let loc = VariableLocation::Stack(self.stack_offset); + self.stack_offset += 1; + loc + } + } + LocationRequest::Persist => { + if let Some(next_var) = self.persistant_vars.pop_front() { + let loc = VariableLocation::Persistant(next_var); + loc + } else { + let loc = VariableLocation::Stack(self.stack_offset); + self.stack_offset += 1; + loc + } + } + LocationRequest::Stack => { + let loc = VariableLocation::Stack(self.stack_offset); + self.stack_offset += 1; + loc + } + }; + self.var_lookup_table.insert(var_name, var_location.clone()); + + Ok(var_location) + } + + pub fn get_location_of( + &mut self, + var_name: impl Into, + ) -> Result { + let var_name = var_name.into(); + self.var_lookup_table + .get(var_name.as_str()) + .map(|v| v.clone()) + .ok_or(Error::UnknownVariable(var_name)) + } + + pub fn free_temp(&mut self, var_name: impl Into) -> Result<(), Error> { + let var_name = var_name.into(); + let Some(location) = self.var_lookup_table.remove(var_name.as_str()) else { + return Err(Error::UnknownVariable(var_name)); + }; + + match location { + VariableLocation::Temporary(t) => { + self.temporary_vars.push_back(t); + } + VariableLocation::Persistant(_) => { + return Err(Error::UnknownVariable(String::from( + "Attempted to free a `let` variable.", + ))); + } + VariableLocation::Stack(_) => {} + }; + + Ok(()) + } } diff --git a/libs/parser/src/lib.rs b/libs/parser/src/lib.rs index d5c476f..5e597a6 100644 --- a/libs/parser/src/lib.rs +++ b/libs/parser/src/lib.rs @@ -5,7 +5,7 @@ use quick_error::quick_error; use std::io::SeekFrom; use sys_call::SysCall; use tokenizer::{ - Tokenizer, TokenizerBuffer, TokenizerError, + self, Tokenizer, TokenizerBuffer, token::{Keyword, Symbol, Token, TokenType}, }; use tree_node::*; @@ -20,8 +20,8 @@ macro_rules! boxed { quick_error! { #[derive(Debug)] - pub enum ParseError { - TokenizerError(err: TokenizerError) { + pub enum Error { + TokenizerError(err: tokenizer::Error) { from() display("Tokenizer Error: {}", err) source(err) @@ -57,7 +57,7 @@ macro_rules! token_from_option { ($token:expr) => { match $token { Some(ref token) => token.clone(), - None => return Err(ParseError::UnexpectedEOF), + None => return Err(Error::UnexpectedEOF), } }; } @@ -66,14 +66,14 @@ macro_rules! extract_token_data { ($token:ident, $pattern:pat, $extraction:expr) => { match $token.token_type { $pattern => $extraction, - _ => return Err(ParseError::UnexpectedToken($token.clone())), + _ => return Err(Error::UnexpectedToken($token.clone())), } }; ($token:expr, $pattern:pat, $extraction:expr) => { match $token.token_type { $pattern => $extraction, _ => { - return Err(ParseError::UnexpectedToken($token.clone())); + return Err(Error::UnexpectedToken($token.clone())); } } }; @@ -118,7 +118,7 @@ impl Parser { /// Parses all the input from the tokenizer buffer and returns the resulting expression /// Expressions are returned in a root block expression node - pub fn parse_all(&mut self) -> Result, ParseError> { + pub fn parse_all(&mut self) -> Result, Error> { let mut expressions = Vec::::new(); while let Some(expression) = self.parse()? { @@ -129,7 +129,7 @@ impl Parser { } /// Parses the input from the tokenizer buffer and returns the resulting expression - pub fn parse(&mut self) -> Result, ParseError> { + pub fn parse(&mut self) -> Result, Error> { self.assign_next()?; let expr = self.expression()?; @@ -141,18 +141,18 @@ impl Parser { } /// Assigns the next token in the tokenizer buffer to the current token - fn assign_next(&mut self) -> Result<(), ParseError> { + fn assign_next(&mut self) -> Result<(), Error> { self.current_token = self.tokenizer.next_token()?; Ok(()) } /// Calls `assign_next` and returns the next token in the tokenizer buffer - fn get_next(&mut self) -> Result, ParseError> { + fn get_next(&mut self) -> Result, Error> { self.assign_next()?; Ok(self.current_token.as_ref()) } - fn expression(&mut self) -> Result, ParseError> { + fn expression(&mut self) -> Result, Error> { macro_rules! matches_keyword { ($keyword:expr, $($pattern:pat),+) => { matches!($keyword, $($pattern)|+) @@ -172,7 +172,7 @@ impl Parser { TokenType::Keyword(e) if matches_keyword!(e, Keyword::Enum, Keyword::If, Keyword::Else) => { - return Err(ParseError::UnsupportedKeyword(current_token.clone())); + return Err(Error::UnsupportedKeyword(current_token.clone())); } // match declarations with a `let` keyword @@ -215,7 +215,7 @@ impl Parser { TokenType::Symbol(Symbol::LParen) => Expression::Priority(self.priority()?), _ => { - return Err(ParseError::UnexpectedToken(current_token.clone())); + return Err(Error::UnexpectedToken(current_token.clone())); } }); @@ -237,7 +237,7 @@ impl Parser { Ok(Some(expr)) } - fn get_binary_child_node(&mut self) -> Result { + fn get_binary_child_node(&mut self) -> Result { let current_token = token_from_option!(self.current_token); match current_token.token_type { @@ -257,16 +257,16 @@ impl Parser { { self.invocation().map(Expression::Invocation) } - _ => Err(ParseError::UnexpectedToken(current_token.clone())), + _ => Err(Error::UnexpectedToken(current_token.clone())), } } - fn device(&mut self) -> Result { + fn device(&mut self) -> Result { // sanity check, make sure current token is a `device` keyword let current_token = token_from_option!(self.current_token); if !self_matches_current!(self, TokenType::Keyword(Keyword::Device)) { - return Err(ParseError::UnexpectedToken(current_token.clone())); + return Err(Error::UnexpectedToken(current_token.clone())); } let identifier = extract_token_data!( @@ -277,7 +277,7 @@ impl Parser { let current_token = token_from_option!(self.get_next()?).clone(); if !token_matches!(current_token, TokenType::Symbol(Symbol::Assign)) { - return Err(ParseError::UnexpectedToken(current_token)); + return Err(Error::UnexpectedToken(current_token)); } let device = extract_token_data!( @@ -292,7 +292,7 @@ impl Parser { }) } - fn assignment(&mut self) -> Result { + fn assignment(&mut self) -> Result { let identifier = extract_token_data!( token_from_option!(self.current_token), TokenType::Identifier(ref id), @@ -301,11 +301,11 @@ impl Parser { let current_token = token_from_option!(self.get_next()?).clone(); if !token_matches!(current_token, TokenType::Symbol(Symbol::Assign)) { - return Err(ParseError::UnexpectedToken(current_token)); + return Err(Error::UnexpectedToken(current_token)); } self.assign_next()?; - let expression = self.expression()?.ok_or(ParseError::UnexpectedEOF)?; + let expression = self.expression()?.ok_or(Error::UnexpectedEOF)?; Ok(AssignmentExpression { identifier, @@ -314,7 +314,7 @@ impl Parser { } /// Handles mathmatical expressions in the explicit order of PEMDAS - fn binary(&mut self, previous: Expression) -> Result { + fn binary(&mut self, previous: Expression) -> Result { // We cannot use recursion here, as we need to handle the precedence of the operators // We need to use a loop to parse the binary expressions. @@ -330,7 +330,7 @@ impl Parser { | Expression::Negation(_) // -1 + 2 => {} _ => { - return Err(ParseError::InvalidSyntax(current_token.clone(), String::from("Invalid expression for binary operation"))) + return Err(Error::InvalidSyntax(current_token.clone(), String::from("Invalid expression for binary operation"))) } } @@ -352,7 +352,7 @@ impl Parser { // validate the vectors and make sure operators.len() == expressions.len() - 1 if operators.len() != expressions.len() - 1 { - return Err(ParseError::InvalidSyntax( + return Err(Error::InvalidSyntax( current_token.clone(), String::from("Invalid number of operators"), )); @@ -433,7 +433,7 @@ impl Parser { // Ensure there is only one expression left in the expressions vector, and no operators left if expressions.len() != 1 || !operators.is_empty() { - return Err(ParseError::InvalidSyntax( + return Err(Error::InvalidSyntax( current_token.clone(), String::from("Invalid number of operators"), )); @@ -454,24 +454,24 @@ impl Parser { } } - fn priority(&mut self) -> Result, ParseError> { + fn priority(&mut self) -> Result, Error> { let current_token = token_from_option!(self.current_token); if !token_matches!(current_token, TokenType::Symbol(Symbol::LParen)) { - return Err(ParseError::UnexpectedToken(current_token.clone())); + return Err(Error::UnexpectedToken(current_token.clone())); } self.assign_next()?; - let expression = self.expression()?.ok_or(ParseError::UnexpectedEOF)?; + let expression = self.expression()?.ok_or(Error::UnexpectedEOF)?; let current_token = token_from_option!(self.get_next()?); if !token_matches!(current_token, TokenType::Symbol(Symbol::RParen)) { - return Err(ParseError::UnexpectedToken(current_token.clone())); + return Err(Error::UnexpectedToken(current_token.clone())); } Ok(boxed!(expression)) } - fn invocation(&mut self) -> Result { + fn invocation(&mut self) -> Result { let identifier = extract_token_data!( token_from_option!(self.current_token), TokenType::Identifier(ref id), @@ -481,7 +481,7 @@ impl Parser { // Ensure the next token is a left parenthesis let current_token = token_from_option!(self.get_next()?); if !token_matches!(current_token, TokenType::Symbol(Symbol::LParen)) { - return Err(ParseError::UnexpectedToken(current_token.clone())); + return Err(Error::UnexpectedToken(current_token.clone())); } let mut arguments = Vec::::new(); @@ -492,10 +492,10 @@ impl Parser { TokenType::Symbol(Symbol::RParen) ) { let current_token = token_from_option!(self.current_token); - let expression = self.expression()?.ok_or(ParseError::UnexpectedEOF)?; + let expression = self.expression()?.ok_or(Error::UnexpectedEOF)?; if let Expression::Block(_) = expression { - return Err(ParseError::InvalidSyntax( + return Err(Error::InvalidSyntax( current_token, String::from("Block expressions are not allowed in function invocations"), )); @@ -507,7 +507,7 @@ impl Parser { if !self_matches_peek!(self, TokenType::Symbol(Symbol::Comma)) && !self_matches_peek!(self, TokenType::Symbol(Symbol::RParen)) { - return Err(ParseError::UnexpectedToken( + return Err(Error::UnexpectedToken( token_from_option!(self.get_next()?).clone(), )); } @@ -527,20 +527,20 @@ impl Parser { }) } - fn block(&mut self) -> Result { + fn block(&mut self) -> Result { let mut expressions = Vec::::new(); let current_token = token_from_option!(self.current_token); // sanity check: make sure the current token is a left brace if !token_matches!(current_token, TokenType::Symbol(Symbol::LBrace)) { - return Err(ParseError::UnexpectedToken(current_token.clone())); + return Err(Error::UnexpectedToken(current_token.clone())); } while !self_matches_peek!( self, TokenType::Symbol(Symbol::RBrace) | TokenType::Keyword(Keyword::Return) ) { - let expression = self.parse()?.ok_or(ParseError::UnexpectedEOF)?; + let expression = self.parse()?.ok_or(Error::UnexpectedEOF)?; expressions.push(expression); } @@ -549,7 +549,7 @@ impl Parser { if token_matches!(current_token, TokenType::Keyword(Keyword::Return)) { self.assign_next()?; - let expression = self.expression()?.ok_or(ParseError::UnexpectedEOF)?; + let expression = self.expression()?.ok_or(Error::UnexpectedEOF)?; let return_expr = Expression::Return(boxed!(expression)); expressions.push(return_expr); self.assign_next()?; @@ -560,10 +560,10 @@ impl Parser { Ok(BlockExpression(expressions)) } - fn declaration(&mut self) -> Result { + fn declaration(&mut self) -> Result { let current_token = token_from_option!(self.current_token); if !self_matches_current!(self, TokenType::Keyword(Keyword::Let)) { - return Err(ParseError::UnexpectedToken(current_token.clone())); + return Err(Error::UnexpectedToken(current_token.clone())); } let identifier = extract_token_data!( token_from_option!(self.get_next()?), @@ -574,16 +574,16 @@ impl Parser { let current_token = token_from_option!(self.get_next()?).clone(); if !token_matches!(current_token, TokenType::Symbol(Symbol::Assign)) { - return Err(ParseError::UnexpectedToken(current_token.clone())); + return Err(Error::UnexpectedToken(current_token.clone())); } self.assign_next()?; - let assignment_expression = self.expression()?.ok_or(ParseError::UnexpectedEOF)?; + let assignment_expression = self.expression()?.ok_or(Error::UnexpectedEOF)?; // make sure the next token is a semi-colon let current_token = token_from_option!(self.get_next()?); if !token_matches!(current_token, TokenType::Symbol(Symbol::Semicolon)) { - return Err(ParseError::UnexpectedToken(current_token.clone())); + return Err(Error::UnexpectedToken(current_token.clone())); } Ok(Expression::Declaration( @@ -592,22 +592,22 @@ impl Parser { )) } - fn literal(&mut self) -> Result { + fn literal(&mut self) -> Result { let current_token = token_from_option!(self.current_token); let literal = match current_token.token_type { TokenType::Number(num) => Literal::Number(num), TokenType::String(string) => Literal::String(string), - _ => return Err(ParseError::UnexpectedToken(current_token.clone())), + _ => return Err(Error::UnexpectedToken(current_token.clone())), }; Ok(literal) } - fn function(&mut self) -> Result { + fn function(&mut self) -> Result { let current_token = token_from_option!(self.current_token); // Sanify check that the current token is a `fn` keyword if !self_matches_current!(self, TokenType::Keyword(Keyword::Fn)) { - return Err(ParseError::UnexpectedToken(current_token.clone())); + return Err(Error::UnexpectedToken(current_token.clone())); } let fn_ident = extract_token_data!( @@ -619,7 +619,7 @@ impl Parser { // make sure next token is a left parenthesis let current_token = token_from_option!(self.get_next()?); if !token_matches!(current_token, TokenType::Symbol(Symbol::LParen)) { - return Err(ParseError::UnexpectedToken(current_token.clone())); + return Err(Error::UnexpectedToken(current_token.clone())); } let mut arguments = Vec::::new(); @@ -635,7 +635,7 @@ impl Parser { extract_token_data!(current_token, TokenType::Identifier(ref id), id.clone()); if arguments.contains(&argument) { - return Err(ParseError::DuplicateIdentifier(current_token.clone())); + return Err(Error::DuplicateIdentifier(current_token.clone())); } arguments.push(argument); @@ -644,7 +644,7 @@ impl Parser { if !self_matches_peek!(self, TokenType::Symbol(Symbol::Comma)) && !self_matches_peek!(self, TokenType::Symbol(Symbol::RParen)) { - return Err(ParseError::UnexpectedToken( + return Err(Error::UnexpectedToken( token_from_option!(self.get_next()?).clone(), )); } @@ -661,7 +661,7 @@ impl Parser { // make sure the next token is a left brace let current_token = token_from_option!(self.get_next()?); if !token_matches!(current_token, TokenType::Symbol(Symbol::LBrace)) { - return Err(ParseError::UnexpectedToken(current_token.clone())); + return Err(Error::UnexpectedToken(current_token.clone())); }; Ok(FunctionExpression { @@ -671,15 +671,15 @@ impl Parser { }) } - fn syscall(&mut self) -> Result { + fn syscall(&mut self) -> Result { /// Checks the length of the arguments and returns an error if the length is not equal to the expected length fn check_length( parser: &Parser, arguments: &[Expression], length: usize, - ) -> Result<(), ParseError> { + ) -> Result<(), Error> { if arguments.len() != length { - return Err(ParseError::InvalidSyntax( + return Err(Error::InvalidSyntax( token_from_option!(parser.current_token).clone(), format!("Expected {} arguments", length), )); @@ -695,7 +695,7 @@ impl Parser { } Some(Expression::Variable(ident)) => LiteralOrVariable::Variable(ident.clone()), _ => { - return Err(ParseError::UnexpectedToken( + return Err(Error::UnexpectedToken( token_from_option!(self.current_token).clone(), )) } @@ -709,7 +709,7 @@ impl Parser { match $arg { LiteralOrVariable::$matcher(i) => i, _ => { - return Err(ParseError::InvalidSyntax( + return Err(Error::InvalidSyntax( token_from_option!(self.current_token).clone(), String::from("Expected a variable"), )) @@ -737,7 +737,7 @@ impl Parser { let device = literal_or_variable!(args.next()); let Some(Expression::Literal(Literal::String(variable))) = args.next() else { - return Err(ParseError::UnexpectedToken( + return Err(Error::UnexpectedToken( token_from_option!(self.current_token).clone(), )); }; @@ -756,7 +756,7 @@ impl Parser { let Literal::String(logic_type) = get_arg!(Literal, literal_or_variable!(args.next())) else { - return Err(ParseError::UnexpectedToken( + return Err(Error::UnexpectedToken( token_from_option!(self.current_token).clone(), )); }; diff --git a/libs/tokenizer/src/lib.rs b/libs/tokenizer/src/lib.rs index 47b2c17..215d053 100644 --- a/libs/tokenizer/src/lib.rs +++ b/libs/tokenizer/src/lib.rs @@ -12,7 +12,7 @@ use token::{Keyword, Number, Symbol, Temperature, Token, TokenType}; quick_error! { #[derive(Debug)] - pub enum TokenizerError { + pub enum Error { IOError(err: std::io::Error) { from() display("IO Error: {}", err) @@ -48,7 +48,7 @@ pub struct Tokenizer { } impl Tokenizer { - pub fn from_path(input_file: impl Into) -> Result { + pub fn from_path(input_file: impl Into) -> Result { let file = std::fs::File::open(input_file.into())?; let reader = BufReader::new(Box::new(file) as Box); @@ -83,7 +83,7 @@ impl Tokenizer { /// /// # Important /// This function will increment the line and column counters - fn next_char(&mut self) -> Result, TokenizerError> { + fn next_char(&mut self) -> Result, Error> { let bytes_read = self.reader.read(&mut self.char_buffer)?; if bytes_read == 0 { @@ -106,7 +106,7 @@ impl Tokenizer { /// /// # Important /// This does not increment the line or column counters - fn peek_next_char(&mut self) -> Result, TokenizerError> { + fn peek_next_char(&mut self) -> Result, Error> { let current_pos = self.reader.stream_position()?; let to_return = if self.reader.read(&mut self.char_buffer)? == 0 { @@ -126,7 +126,7 @@ impl Tokenizer { /// /// # Important /// This function will increment the line and column counters - fn skip_line(&mut self) -> Result<(), TokenizerError> { + fn skip_line(&mut self) -> Result<(), Error> { while let Some(next_char) = self.next_char()? { if next_char == '\n' { break; @@ -137,7 +137,7 @@ impl Tokenizer { /// Consumes the tokenizer and returns the next token in the stream /// If there are no more tokens in the stream, this function returns None - pub fn next_token(&mut self) -> Result, TokenizerError> { + pub fn next_token(&mut self) -> Result, Error> { while let Some(next_char) = self.next_char()? { // skip whitespace if next_char.is_whitespace() { @@ -165,11 +165,7 @@ impl Tokenizer { return self.tokenize_keyword_or_identifier(next_char).map(Some); } _ => { - return Err(TokenizerError::UnknownSymbolError( - next_char, - self.line, - self.column, - )); + return Err(Error::UnknownSymbolError(next_char, self.line, self.column)); } } } @@ -183,7 +179,7 @@ impl Tokenizer { /// Peeks the next token in the stream without consuming it /// If there are no more tokens in the stream, this function returns None - pub fn peek_next(&mut self) -> Result, TokenizerError> { + pub fn peek_next(&mut self) -> Result, Error> { let current_pos = self.reader.stream_position()?; let column = self.column; let line = self.line; @@ -196,7 +192,7 @@ impl Tokenizer { } /// Tokenizes a symbol - fn tokenize_symbol(&mut self, first_symbol: char) -> Result { + fn tokenize_symbol(&mut self, first_symbol: char) -> Result { /// Helper macro to create a symbol token macro_rules! symbol { ($symbol:ident) => { @@ -266,7 +262,7 @@ impl Tokenizer { symbol!(LogicalOr) } - _ => Err(TokenizerError::UnknownSymbolError( + _ => Err(Error::UnknownSymbolError( first_symbol, self.line, self.column, @@ -275,7 +271,7 @@ impl Tokenizer { } /// Tokenizes a number literal. Also handles temperatures with a suffix of `c`, `f`, or `k`. - fn tokenize_number(&mut self, first_char: char) -> Result { + fn tokenize_number(&mut self, first_char: char) -> Result { let mut primary = String::with_capacity(16); let mut decimal: Option = None; let mut reading_decimal = false; @@ -319,16 +315,16 @@ impl Tokenizer { let decimal_scale = decimal.len() as u32; let number = format!("{}{}", primary, decimal) .parse::() - .map_err(|e| TokenizerError::NumberParseError(e, self.line, self.column))?; + .map_err(|e| Error::NumberParseError(e, self.line, self.column))?; Number::Decimal( Decimal::try_from_i128_with_scale(number, decimal_scale) - .map_err(|e| TokenizerError::DecimalParseError(e, line, column))?, + .map_err(|e| Error::DecimalParseError(e, line, column))?, ) } else { Number::Integer( primary .parse() - .map_err(|e| TokenizerError::NumberParseError(e, line, column))?, + .map_err(|e| Error::NumberParseError(e, line, column))?, ) }; @@ -350,7 +346,7 @@ impl Tokenizer { } /// Tokenizes a string literal - fn tokenize_string(&mut self, beginning_quote: char) -> Result { + fn tokenize_string(&mut self, beginning_quote: char) -> Result { let mut buffer = String::with_capacity(16); let column = self.column; @@ -368,10 +364,7 @@ impl Tokenizer { } /// Tokenizes a keyword or an identifier. Also handles boolean literals - fn tokenize_keyword_or_identifier( - &mut self, - first_char: char, - ) -> Result { + fn tokenize_keyword_or_identifier(&mut self, first_char: char) -> Result { macro_rules! keyword { ($keyword:ident) => {{ return Ok(Token::new( @@ -441,9 +434,7 @@ impl Tokenizer { looped_char = self.next_char()?; } - Err(TokenizerError::UnknownKeywordOrIdentifierError( - buffer, line, column, - )) + Err(Error::UnknownKeywordOrIdentifierError(buffer, line, column)) } } @@ -464,7 +455,7 @@ impl TokenizerBuffer { /// Reads the next token from the tokenizer, pushing the value to the back of the history /// and returning the token - pub fn next_token(&mut self) -> Result, TokenizerError> { + pub fn next_token(&mut self) -> Result, Error> { if let Some(token) = self.buffer.pop_front() { self.history.push_back(token.clone()); return Ok(Some(token)); @@ -478,7 +469,7 @@ impl TokenizerBuffer { } /// Peeks the next token in the stream without adding to the history stack - pub fn peek(&mut self) -> Result, TokenizerError> { + pub fn peek(&mut self) -> Result, Error> { if let Some(token) = self.buffer.front() { return Ok(Some(token.clone())); } @@ -487,7 +478,7 @@ impl TokenizerBuffer { Ok(token) } - fn seek_from_current(&mut self, seek_to: i64) -> Result<(), TokenizerError> { + fn seek_from_current(&mut self, seek_to: i64) -> Result<(), Error> { use Ordering::*; // if seek_to > 0 then we need to check if the buffer has enough tokens to pop, otherwise we need to read from the tokenizer // if seek_to < 0 then we need to pop from the history and push to the front of the buffer. If not enough, then we throw (we reached the front of the history) @@ -500,7 +491,7 @@ impl TokenizerBuffer { if let Some(token) = self.tokenizer.next_token()? { tokens.push(token); } else { - return Err(TokenizerError::IOError(std::io::Error::new( + return Err(Error::IOError(std::io::Error::new( std::io::ErrorKind::UnexpectedEof, "Unexpected EOF", ))); @@ -515,7 +506,7 @@ impl TokenizerBuffer { if let Some(token) = self.history.pop_back() { tokens.push(token); } else { - return Err(TokenizerError::IOError(std::io::Error::new( + return Err(Error::IOError(std::io::Error::new( std::io::ErrorKind::UnexpectedEof, "Unexpected EOF", ))); @@ -530,7 +521,7 @@ impl TokenizerBuffer { } /// Adds to or removes from the History stack, allowing the user to move back and forth in the stream - pub fn seek(&mut self, from: SeekFrom) -> Result<(), TokenizerError> { + pub fn seek(&mut self, from: SeekFrom) -> Result<(), Error> { match from { SeekFrom::Current(seek_to) => self.seek_from_current(seek_to)?, SeekFrom::End(_) => unimplemented!("SeekFrom::End will not be implemented"), diff --git a/src/lib.rs b/src/lib.rs index c023443..68a5650 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,14 +9,12 @@ fn compile_from_string( input: &safer_ffi::string::String, output: &mut safer_ffi::string::String, ) -> bool { - let buffer = Vec::::new(); - - let mut writer = BufWriter::new(buffer); + let mut writer = BufWriter::new(Vec::new()); let tokenizer = Tokenizer::from(input.to_string()); let parser = Parser::new(tokenizer); - let compiler = Compiler::new(parser, &mut writer); + let compiler = Compiler::new(parser, &mut writer, None); let Ok(()) = compiler.compile() else { return false; diff --git a/src/main.rs b/src/main.rs index a549747..f93734a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,20 +9,20 @@ use std::{ io::{BufWriter, Read, Write}, path::PathBuf, }; -use tokenizer::{Tokenizer, TokenizerError}; +use tokenizer::{self, Tokenizer}; quick_error! { #[derive(Debug)] enum StationlangError { - TokenizerError(err: TokenizerError) { + TokenizerError(err: tokenizer::Error) { from() display("Tokenizer error: {}", err) } - ParserError(err: parser::ParseError) { + ParserError(err: parser::Error) { from() display("Parser error: {}", err) } - CompileError(err: compiler::CompileError) { + CompileError(err: compiler::Error) { from() display("Compile error: {}", err) } @@ -71,7 +71,7 @@ fn run_logic() -> Result<(), StationlangError> { None => BufWriter::new(Box::new(std::io::stdout())), }; - let compiler = Compiler::new(parser, &mut writer); + let compiler = Compiler::new(parser, &mut writer, None); compiler.compile()?; writer.flush()?; From e068539ea146065423d6eed158b493e28dd7072a Mon Sep 17 00:00:00 2001 From: Devin Bidwell Date: Tue, 18 Nov 2025 19:36:08 -0700 Subject: [PATCH 03/22] Extraction of function arguments from stack --- libs/compiler/src/test/mod.rs | 11 ++++++++--- libs/compiler/src/v2.rs | 23 +++++++++++++++++------ libs/compiler/src/variable_manager.rs | 24 +++++++++++++++++++----- 3 files changed, 44 insertions(+), 14 deletions(-) diff --git a/libs/compiler/src/test/mod.rs b/libs/compiler/src/test/mod.rs index 7200928..aff4abd 100644 --- a/libs/compiler/src/test/mod.rs +++ b/libs/compiler/src/test/mod.rs @@ -59,9 +59,11 @@ fn test_function_declaration_with_spillover_params() -> anyhow::Result<()> { pop r14 #arg3 pop r15 #arg2 push ra - pop ra - sub sp 1 + sub r0 sp 1 + get ra db r0 + sub sp sp 2 j ra + main: "} ); @@ -84,8 +86,11 @@ fn test_function_declaration_with_register_params() -> anyhow::Result<()> { pop r8 #arg2 pop r9 #arg1 push ra - pop ra + sub r0 sp 1 + get ra db r0 + sub sp sp 1 j ra + main: "} ); diff --git a/libs/compiler/src/v2.rs b/libs/compiler/src/v2.rs index 2eec79f..d9bbf51 100644 --- a/libs/compiler/src/v2.rs +++ b/libs/compiler/src/v2.rs @@ -85,9 +85,7 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { ) -> Result<(), Error> { 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))? - } + Expression::Block(expr_block) => self.expression_block(expr_block, scope)?, _ => todo!(), }; @@ -111,7 +109,10 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { }); for expr in expr.0 { - if !self.declared_main && !matches!(expr, Expression::Function(_)) { + if !self.declared_main + && !matches!(expr, Expression::Function(_)) + && !scope.has_parent() + { self.write_output("main:")?; self.declared_main = true; } @@ -192,11 +193,21 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { } self.write_output("push ra")?; + block_scope.add_variable(format!("{name}_ra"), LocationRequest::Stack)?; + self.expression_block(body, &mut block_scope)?; - self.write_output("pop ra")?; + // Get the saved return address and save it back into `ra` + let VariableLocation::Stack(ra_stack_offset) = + block_scope.get_location_of(format!("{name}_ra"))? + else { + panic!("This shouldn't happen"); + }; + + self.write_output(format!("sub r0 sp {ra_stack_offset}"))?; + self.write_output("get ra db r0")?; if block_scope.stack_offset() > 0 { - self.write_output(format!("sub sp {}", block_scope.stack_offset()))?; + self.write_output(format!("sub sp sp {}", block_scope.stack_offset()))?; } self.write_output("j ra")?; diff --git a/libs/compiler/src/variable_manager.rs b/libs/compiler/src/variable_manager.rs index fb34802..d1be261 100644 --- a/libs/compiler/src/variable_manager.rs +++ b/libs/compiler/src/variable_manager.rs @@ -1,10 +1,11 @@ -// r0 - r7 : temporary variables -// r8 - r15 : persistant variables +// r0 : Return Value / Temp Stack Pointer +// r1 - r7 : Temporary Variables +// r8 - r15 : Persistant Variables use quick_error::quick_error; use std::collections::{HashMap, VecDeque}; -const TEMP: [u8; 8] = [0, 1, 2, 3, 4, 5, 6, 7]; +const TEMP: [u8; 7] = [1, 2, 3, 4, 5, 6, 7]; const PERSIST: [u8; 8] = [8, 9, 10, 11, 12, 13, 14, 15]; quick_error! { @@ -137,10 +138,23 @@ impl<'a> VariableScope<'a> { var_name: impl Into, ) -> Result { let var_name = var_name.into(); - self.var_lookup_table + let var = self + .var_lookup_table .get(var_name.as_str()) .map(|v| v.clone()) - .ok_or(Error::UnknownVariable(var_name)) + .ok_or(Error::UnknownVariable(var_name))?; + + if let VariableLocation::Stack(inserted_at_offset) = var { + Ok(VariableLocation::Stack( + self.stack_offset - inserted_at_offset, + )) + } else { + Ok(var) + } + } + + pub fn has_parent(&self) -> bool { + self.parent.is_some() } pub fn free_temp(&mut self, var_name: impl Into) -> Result<(), Error> { From df127af3d864407446b3719e45a9b94a57756e0a Mon Sep 17 00:00:00 2001 From: Devin Bidwell Date: Tue, 18 Nov 2025 22:35:00 -0700 Subject: [PATCH 04/22] wip --- libs/compiler/src/test/mod.rs | 2 -- libs/compiler/src/v2.rs | 47 +++++++++++++++++++++++++++++------ 2 files changed, 39 insertions(+), 10 deletions(-) diff --git a/libs/compiler/src/test/mod.rs b/libs/compiler/src/test/mod.rs index aff4abd..4321378 100644 --- a/libs/compiler/src/test/mod.rs +++ b/libs/compiler/src/test/mod.rs @@ -63,7 +63,6 @@ fn test_function_declaration_with_spillover_params() -> anyhow::Result<()> { get ra db r0 sub sp sp 2 j ra - main: "} ); @@ -90,7 +89,6 @@ fn test_function_declaration_with_register_params() -> anyhow::Result<()> { get ra db r0 sub sp sp 1 j ra - main: "} ); diff --git a/libs/compiler/src/v2.rs b/libs/compiler/src/v2.rs index d9bbf51..b184c8b 100644 --- a/libs/compiler/src/v2.rs +++ b/libs/compiler/src/v2.rs @@ -1,7 +1,7 @@ use crate::variable_manager::{self, LocationRequest, VariableLocation, VariableScope}; use parser::{ Parser as ASTParser, - tree_node::{BlockExpression, Expression, FunctionExpression}, + tree_node::{BlockExpression, DeviceDeclarationExpression, Expression, FunctionExpression}, }; use quick_error::quick_error; use std::{ @@ -24,6 +24,12 @@ quick_error! { DuplicateFunction(func_name: String) { display("{func_name} has already been defined") } + InvalidDevice(device: String) { + display("{device} is not valid") + } + Unknown(reason: String) { + display("{reason}") + } } } @@ -34,8 +40,6 @@ pub struct CompilerConfig { 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, @@ -52,7 +56,6 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { ) -> Self { Self { parser, - variable_scope: Vec::new(), function_locations: HashMap::new(), devices: HashMap::new(), output: writer, @@ -86,12 +89,35 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { match expr { Expression::Function(expr_func) => self.expression_function(expr_func, scope)?, Expression::Block(expr_block) => self.expression_block(expr_block, scope)?, + Expression::DeviceDeclaration(expr_dev) => self.expression_device(expr_dev), + Expression::Declaration(var_name, expr) => { + self.expression_declaration(var_name, *expr, scope)? + } _ => todo!(), }; Ok(()) } + fn expression_declaration<'v>( + &mut self, + var_name: String, + expr: Expression, + scope: &mut VariableScope<'v>, + ) -> Result<(), Error> { + scope.add_variable(var_name.clone(), LocationRequest::Persist)?; + + match expr { + _ => unimplemented!(), + } + + Ok(()) + } + + fn expression_device<'v>(&mut self, expr: DeviceDeclarationExpression) { + self.devices.insert(expr.name, expr.device); + } + fn expression_block<'v>( &mut self, mut expr: BlockExpression, @@ -173,13 +199,16 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { ))?; } VariableLocation::Stack(_) => { - unimplemented!("Attempted to save to stack without tracking in scope") + return Err(Error::Unknown( + "Attempted to save to stack without tracking in scope".into(), + )); } _ => { - unimplemented!( + return Err(Error::Unknown( "Attempted to return a Temporary scoped variable from a Persistant request" - ) + .into(), + )); } } saved_variables += 1; @@ -200,7 +229,9 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { let VariableLocation::Stack(ra_stack_offset) = block_scope.get_location_of(format!("{name}_ra"))? else { - panic!("This shouldn't happen"); + return Err(Error::Unknown( + "Stored return address not in stack as expected".into(), + )); }; self.write_output(format!("sub r0 sp {ra_stack_offset}"))?; From 2ad1eef3e843ce71e86a093444104b120b110359 Mon Sep 17 00:00:00 2001 From: Devin Bidwell Date: Sat, 22 Nov 2025 13:44:50 -0700 Subject: [PATCH 05/22] wip refactor --- Cargo.lock | 23 +++ libs/compiler/Cargo.toml | 1 + libs/compiler/src/lib.rs | 2 +- .../test/declaration_function_invocation.rs | 85 ++++++++ libs/compiler/src/test/declaration_literal.rs | 62 ++++++ .../compiler/src/test/function_declaration.rs | 58 ++++++ libs/compiler/src/test/mod.rs | 85 ++------ libs/compiler/src/v2.rs | 185 ++++++++++++++++-- libs/compiler/src/variable_manager.rs | 24 ++- 9 files changed, 427 insertions(+), 98 deletions(-) create mode 100644 libs/compiler/src/test/declaration_function_invocation.rs create mode 100644 libs/compiler/src/test/declaration_literal.rs create mode 100644 libs/compiler/src/test/function_declaration.rs diff --git a/Cargo.lock b/Cargo.lock index fbabf72..897a276 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -248,10 +248,17 @@ dependencies = [ "anyhow", "indoc", "parser", + "pretty_assertions", "quick-error", "tokenizer", ] +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + [[package]] name = "equivalent" version = "1.0.2" @@ -478,6 +485,16 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "pretty_assertions" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" +dependencies = [ + "diff", + "yansi", +] + [[package]] name = "prettyplease" version = "0.1.25" @@ -1046,6 +1063,12 @@ dependencies = [ "tap", ] +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + [[package]] name = "zerocopy" version = "0.8.27" diff --git a/libs/compiler/Cargo.toml b/libs/compiler/Cargo.toml index 122c21c..85434ac 100644 --- a/libs/compiler/Cargo.toml +++ b/libs/compiler/Cargo.toml @@ -11,3 +11,4 @@ tokenizer = { path = "../tokenizer" } [dev-dependencies] anyhow = { version = "1.0" } indoc = { version = "2.0" } +pretty_assertions = "1" diff --git a/libs/compiler/src/lib.rs b/libs/compiler/src/lib.rs index 4d1d570..388d2b2 100644 --- a/libs/compiler/src/lib.rs +++ b/libs/compiler/src/lib.rs @@ -11,7 +11,7 @@ use std::cmp::Ordering; use std::collections::HashMap; use std::io::{BufWriter, Write}; -pub use v2::{Compiler, Error}; +pub use v2::{Compiler, CompilerConfig, Error}; quick_error! { #[derive(Debug)] diff --git a/libs/compiler/src/test/declaration_function_invocation.rs b/libs/compiler/src/test/declaration_function_invocation.rs new file mode 100644 index 0000000..1b1a05c --- /dev/null +++ b/libs/compiler/src/test/declaration_function_invocation.rs @@ -0,0 +1,85 @@ +use crate::compile; +use indoc::indoc; +use pretty_assertions::assert_eq; + +#[test] +fn no_arguments() -> anyhow::Result<()> { + let compiled = compile! { + debug + " + fn doSomething() {}; + let i = doSomething(); + " + }; + + let to_test = indoc! { + " + j main + doSomething: + push ra + sub r0 sp 1 + get ra db r0 + sub sp sp 1 + j ra + main: + jal doSomething + move r8 r15 #i + " + }; + + assert_eq!(compiled, to_test); + + Ok(()) +} + +#[test] +fn let_var_args() -> anyhow::Result<()> { + let compiled = compile! { + debug + " + fn doSomething(arg1) {}; + let arg1 = 123; + let i = doSomething(arg1); + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + doSomething: + pop r8 #arg1 + push ra + sub r0 sp 1 + get ra db r0 + sub sp sp 1 + j ra + main: + move r8 123 #arg1 + push r8 + push r8 + jal doSomething + sub r0 sp 1 + get r8 db r0 + sub sp sp 1 + move r9 r15 #i + " + } + ); + + Ok(()) +} + +#[test] +fn incorrect_args_count() -> anyhow::Result<()> { + let compiled = compile! { + debug + " + fn doSomething(arg1, arg2){}; + let i = doSomething(); + " + }; + + Ok(()) +} diff --git a/libs/compiler/src/test/declaration_literal.rs b/libs/compiler/src/test/declaration_literal.rs new file mode 100644 index 0000000..5a881a4 --- /dev/null +++ b/libs/compiler/src/test/declaration_literal.rs @@ -0,0 +1,62 @@ +use indoc::indoc; +use pretty_assertions::assert_eq; + +#[test] +fn variable_declaration_numeric_literal() -> anyhow::Result<()> { + let compiled = crate::compile! { + debug r#" + let i = 20c; + "# + }; + + assert_eq!( + compiled, + indoc! { + " + j main + main: + move r8 293.15 #i + " + } + ); + + Ok(()) +} + +#[test] +fn variable_declaration_numeric_literal_stack_spillover() -> anyhow::Result<()> { + let compiled = compile! {debug r#" + let a = 0; + let b = 1; + let c = 2; + let d = 3; + let e = 4; + let f = 5; + let g = 6; + let h = 7; + let i = 8; + let j = 9; + "#}; + + assert_eq!( + compiled, + indoc! { + " + j main + main: + move r8 0 #a + move r9 1 #b + move r10 2 #c + move r11 3 #d + move r12 4 #e + move r13 5 #f + move r14 6 #g + push 7 #h + push 8 #i + push 9 #j + " + } + ); + + Ok(()) +} diff --git a/libs/compiler/src/test/function_declaration.rs b/libs/compiler/src/test/function_declaration.rs new file mode 100644 index 0000000..28200f7 --- /dev/null +++ b/libs/compiler/src/test/function_declaration.rs @@ -0,0 +1,58 @@ +use indoc::indoc; +use pretty_assertions::assert_eq; + +#[test] +fn test_function_declaration_with_spillover_params() -> anyhow::Result<()> { + let compiled = compile!(debug r#" + // we need more than 4 params to 'spill' into a stack var + fn doSomething(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) {}; + "#); + + assert_eq!( + compiled, + indoc! {" + j main + doSomething: + pop r8 #arg9 + pop r9 #arg8 + pop r10 #arg7 + pop r11 #arg6 + pop r12 #arg5 + pop r13 #arg4 + pop r14 #arg3 + push ra + sub r0 sp 1 + get ra db r0 + sub sp sp 3 + j ra + "} + ); + + Ok(()) +} + +#[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: + pop r8 #arg2 + pop r9 #arg1 + push ra + sub r0 sp 1 + get ra db r0 + sub sp sp 1 + j ra + "} + ); + + Ok(()) +} diff --git a/libs/compiler/src/test/mod.rs b/libs/compiler/src/test/mod.rs index 4321378..e439f4d 100644 --- a/libs/compiler/src/test/mod.rs +++ b/libs/compiler/src/test/mod.rs @@ -1,21 +1,15 @@ -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_export] macro_rules! compile { ($source:expr) => {{ - let mut writer = BufWriter::new(Vec::new()); - let compiler = Compiler::new( - Parser::new(Tokenizer::from(String::from($source))), + let mut writer = std::io::BufWriter::new(Vec::new()); + let compiler = crate::Compiler::new( + parser::Parser::new(tokenizer::Tokenizer::from(String::from($source))), &mut writer, None, ); @@ -24,73 +18,16 @@ macro_rules! compile { }}; (debug $source:expr) => {{ - let mut writer = BufWriter::new(Vec::new()); - let compiler = Compiler::new( - Parser::new(Tokenizer::from(String::from($source))), + let mut writer = std::io::BufWriter::new(Vec::new()); + let compiler = crate::Compiler::new( + parser::Parser::new(tokenizer::Tokenizer::from(String::from($source))), &mut writer, - Some(CompilerConfig { - debug: true, - ..Default::default() - }), + Some(crate::CompilerConfig { debug: true }), ); compiler.compile()?; output!(writer) }}; } - -#[test] -fn test_function_declaration_with_spillover_params() -> anyhow::Result<()> { - let compiled = compile!(debug r#" - // we need more than 4 params to 'spill' into a stack var - fn doSomething(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) {}; - "#); - - assert_eq!( - compiled, - indoc! {" - j main - doSomething: - pop r8 #arg9 - pop r9 #arg8 - pop r10 #arg7 - pop r11 #arg6 - pop r12 #arg5 - pop r13 #arg4 - pop r14 #arg3 - pop r15 #arg2 - push ra - sub r0 sp 1 - get ra db r0 - sub sp sp 2 - j ra - "} - ); - - Ok(()) -} - -#[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: - pop r8 #arg2 - pop r9 #arg1 - push ra - sub r0 sp 1 - get ra db r0 - sub sp sp 1 - j ra - "} - ); - - Ok(()) -} +mod declaration_function_invocation; +mod declaration_literal; +mod function_declaration; diff --git a/libs/compiler/src/v2.rs b/libs/compiler/src/v2.rs index b184c8b..c411d8c 100644 --- a/libs/compiler/src/v2.rs +++ b/libs/compiler/src/v2.rs @@ -1,7 +1,10 @@ use crate::variable_manager::{self, LocationRequest, VariableLocation, VariableScope}; use parser::{ Parser as ASTParser, - tree_node::{BlockExpression, DeviceDeclarationExpression, Expression, FunctionExpression}, + tree_node::{ + BlockExpression, DeviceDeclarationExpression, Expression, FunctionExpression, + InvocationExpression, Literal, + }, }; use quick_error::quick_error; use std::{ @@ -9,6 +12,16 @@ use std::{ io::{BufWriter, Write}, }; +macro_rules! debug { + ($self: expr, $debug_value: expr) => { + if $self.config.debug { + format!($debug_value) + } else { + "".into() + } + }; +} + quick_error! { #[derive(Debug)] pub enum Error { @@ -22,10 +35,16 @@ quick_error! { from() } DuplicateFunction(func_name: String) { - display("{func_name} has already been defined") + display("`{func_name}` has already been defined") + } + UnknownIdentifier(ident: String) { + display("`{ident}` is not found in the current scope.") } InvalidDevice(device: String) { - display("{device} is not valid") + display("`{device}` is not valid") + } + AgrumentMismatch(func_name: String) { + display("Incorrect number of arguments passed into `{func_name}`") } Unknown(reason: String) { display("{reason}") @@ -41,6 +60,7 @@ pub struct CompilerConfig { pub struct Compiler<'a, W: std::io::Write> { parser: ASTParser, function_locations: HashMap, + function_metadata: HashMap>, devices: HashMap, output: &'a mut BufWriter, current_line: usize, @@ -57,6 +77,7 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { Self { parser, function_locations: HashMap::new(), + function_metadata: HashMap::new(), devices: HashMap::new(), output: writer, current_line: 1, @@ -93,6 +114,9 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { Expression::Declaration(var_name, expr) => { self.expression_declaration(var_name, *expr, scope)? } + Expression::Invocation(expr_invoke) => { + self.expression_function_invocation(expr_invoke, scope)? + } _ => todo!(), }; @@ -105,16 +129,135 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { expr: Expression, scope: &mut VariableScope<'v>, ) -> Result<(), Error> { - scope.add_variable(var_name.clone(), LocationRequest::Persist)?; - match expr { - _ => unimplemented!(), + Expression::Literal(Literal::Number(num)) => { + let var_location = + scope.add_variable(var_name.clone(), LocationRequest::Persist)?; + let num_str = num.to_string(); + + if let VariableLocation::Temporary(reg) | VariableLocation::Persistant(reg) = + var_location + { + self.write_output(format!( + "move r{reg} {num_str} {}", + debug!(self, "#{var_name}") + ))?; + } else { + self.write_output(format!("push {num_str} {}", debug!(self, "#{var_name}")))?; + } + } + Expression::Invocation(invoke_expr) => { + self.expression_function_invocation(invoke_expr, scope)?; + + // Return value _should_ be in VariableScope::RETURN_REGISTER + match scope.add_variable(var_name.clone(), LocationRequest::Persist)? { + VariableLocation::Temporary(reg) | VariableLocation::Persistant(reg) => self + .write_output(format!( + "move r{reg} r{} {}", + VariableScope::RETURN_REGISTER, + debug!(self, "#{var_name}") + ))?, + VariableLocation::Stack(_) => self.write_output(format!( + "push r{} {}", + VariableScope::RETURN_REGISTER, + debug!(self, "#{var_name}") + ))?, + } + } + _ => { + return Err(Error::Unknown( + "`{var_name}` declaration of this type is not supported.".into(), + )); + } } Ok(()) } - fn expression_device<'v>(&mut self, expr: DeviceDeclarationExpression) { + fn expression_function_invocation( + &mut self, + invoke_expr: InvocationExpression, + stack: &mut VariableScope, + ) -> Result<(), Error> { + if !self.function_locations.contains_key(&invoke_expr.name) { + return Err(Error::UnknownIdentifier(invoke_expr.name)); + } + + let Some(args) = self.function_metadata.get(&invoke_expr.name) else { + return Err(Error::UnknownIdentifier(invoke_expr.name)); + }; + + if args.len() != invoke_expr.arguments.len() { + return Err(Error::AgrumentMismatch(invoke_expr.name)); + } + + // backup all used registers to the stack + let active_registers = stack.registers().cloned().collect::>(); + for register in &active_registers { + stack.add_variable(format!("temp_{register}"), LocationRequest::Stack)?; + self.write_output(format!("push r{register}"))?; + } + for arg in invoke_expr.arguments { + match arg { + Expression::Literal(Literal::Number(num)) => { + let num_str = num.to_string(); + self.write_output(format!("push {num_str}"))?; + } + Expression::Variable(var_name) => match stack.get_location_of(var_name)? { + VariableLocation::Persistant(reg) | VariableLocation::Temporary(reg) => { + self.write_output(format!("push r{reg}"))?; + } + VariableLocation::Stack(stack_offset) => { + self.write_output(format!( + "sub r{0} sp {stack_offset}", + VariableScope::TEMP_STACK_REGISTER + ))?; + self.write_output(format!( + "get r{0} db r{0}", + VariableScope::TEMP_STACK_REGISTER + ))?; + self.write_output(format!( + "push r{0}", + VariableScope::TEMP_STACK_REGISTER + ))?; + } + }, + _ => { + return Err(Error::Unknown(format!( + "Attempted to call `{}` with an unsupported argument", + invoke_expr.name + ))); + } + } + } + + // jump to the function and store current line in ra + self.write_output(format!("jal {}", invoke_expr.name))?; + + for register in active_registers { + let VariableLocation::Stack(stack_offset) = + stack.get_location_of(format!("temp_{register}"))? + else { + return Err(Error::UnknownIdentifier(format!("temp_{register}"))); + }; + self.write_output(format!( + "sub r{0} sp {stack_offset}", + VariableScope::TEMP_STACK_REGISTER + ))?; + self.write_output(format!( + "get r{register} db r{0}", + VariableScope::TEMP_STACK_REGISTER + ))?; + } + + if stack.stack_offset() > 0 { + self.write_output(format!("sub sp sp {}", stack.stack_offset()))?; + } + + Ok(()) + } + + fn expression_device(&mut self, expr: DeviceDeclarationExpression) { self.devices.insert(expr.name, expr.device); } @@ -166,14 +309,17 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { return Err(Error::DuplicateFunction(name)); } - self.function_locations - .insert(name.clone(), self.current_line); + self.function_metadata + .insert(name.clone(), arguments.clone()); // Declare the function as a line identifier self.write_output(format!("{}:", name))?; + self.function_locations + .insert(name.clone(), self.current_line); + // Create a new block scope for the function body - let mut block_scope = VariableScope::scoped(&scope); + let mut block_scope = VariableScope::scoped(scope); let mut saved_variables = 0; @@ -189,14 +335,7 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { match loc { VariableLocation::Persistant(loc) => { - self.write_output(format!( - "pop r{loc} {}", - if self.config.debug { - format!("#{}", var_name) - } else { - "".into() - } - ))?; + self.write_output(format!("pop r{loc} {}", debug!(self, "#{var_name}")))?; } VariableLocation::Stack(_) => { return Err(Error::Unknown( @@ -234,8 +373,14 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { )); }; - self.write_output(format!("sub r0 sp {ra_stack_offset}"))?; - self.write_output("get ra db r0")?; + self.write_output(format!( + "sub r{0} sp {ra_stack_offset}", + VariableScope::TEMP_STACK_REGISTER + ))?; + self.write_output(format!( + "get ra db r{0}", + VariableScope::TEMP_STACK_REGISTER + ))?; if block_scope.stack_offset() > 0 { self.write_output(format!("sub sp sp {}", block_scope.stack_offset()))?; diff --git a/libs/compiler/src/variable_manager.rs b/libs/compiler/src/variable_manager.rs index d1be261..edf42e4 100644 --- a/libs/compiler/src/variable_manager.rs +++ b/libs/compiler/src/variable_manager.rs @@ -6,7 +6,7 @@ use quick_error::quick_error; use std::collections::{HashMap, VecDeque}; const TEMP: [u8; 7] = [1, 2, 3, 4, 5, 6, 7]; -const PERSIST: [u8; 8] = [8, 9, 10, 11, 12, 13, 14, 15]; +const PERSIST: [u8; 7] = [8, 9, 10, 11, 12, 13, 14]; quick_error! { #[derive(Debug)] @@ -72,8 +72,26 @@ impl<'a> Default for VariableScope<'a> { } impl<'a> VariableScope<'a> { - pub const TEMP_REGISTER_COUNT: u8 = 8; - pub const PERSIST_REGISTER_COUNT: u8 = 8; + pub const TEMP_REGISTER_COUNT: u8 = 7; + pub const PERSIST_REGISTER_COUNT: u8 = 7; + + pub const RETURN_REGISTER: u8 = 15; + pub const TEMP_STACK_REGISTER: u8 = 0; + + pub fn registers(&self) -> impl Iterator { + self.var_lookup_table + .values() + .filter(|val| { + matches!( + val, + VariableLocation::Temporary(_) | VariableLocation::Persistant(_) + ) + }) + .map(|loc| match loc { + VariableLocation::Persistant(reg) | VariableLocation::Temporary(reg) => reg, + _ => unreachable!(), + }) + } pub fn scoped(parent: &'a VariableScope<'a>) -> Self { Self { From 0c4ebedacf1335287506fa704eb9454116087909 Mon Sep 17 00:00:00 2001 From: Devin Bidwell Date: Sat, 22 Nov 2025 13:49:39 -0700 Subject: [PATCH 06/22] moar tests --- .../src/test/declaration_function_invocation.rs | 7 ++++++- libs/compiler/src/test/mod.rs | 10 ++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/libs/compiler/src/test/declaration_function_invocation.rs b/libs/compiler/src/test/declaration_function_invocation.rs index 1b1a05c..3a028ca 100644 --- a/libs/compiler/src/test/declaration_function_invocation.rs +++ b/libs/compiler/src/test/declaration_function_invocation.rs @@ -74,12 +74,17 @@ fn let_var_args() -> anyhow::Result<()> { #[test] fn incorrect_args_count() -> anyhow::Result<()> { let compiled = compile! { - debug + result " fn doSomething(arg1, arg2){}; let i = doSomething(); " }; + assert!(matches!( + compiled, + Err(super::super::Error::AgrumentMismatch(_)) + )); + Ok(()) } diff --git a/libs/compiler/src/test/mod.rs b/libs/compiler/src/test/mod.rs index e439f4d..3efbc34 100644 --- a/libs/compiler/src/test/mod.rs +++ b/libs/compiler/src/test/mod.rs @@ -17,6 +17,16 @@ macro_rules! compile { output!(writer) }}; + (result $source:expr) => {{ + let mut writer = std::io::BufWriter::new(Vec::new()); + let compiler = crate::Compiler::new( + parser::Parser::new(tokenizer::Tokenizer::from(String::from($source))), + &mut writer, + Some(crate::CompilerConfig { debug: true }), + ); + compiler.compile() + }}; + (debug $source:expr) => {{ let mut writer = std::io::BufWriter::new(Vec::new()); let compiler = crate::Compiler::new( From 4703f9d24af3931acca058cf4c167eb810169877 Mon Sep 17 00:00:00 2001 From: Devin Bidwell Date: Sat, 22 Nov 2025 16:20:35 -0700 Subject: [PATCH 07/22] Removed lots of dead code --- libs/compiler/src/lib.rs | 397 -------------------------- libs/compiler/src/test/mod.rs | 6 +- libs/compiler/src/variable_manager.rs | 16 +- 3 files changed, 7 insertions(+), 412 deletions(-) diff --git a/libs/compiler/src/lib.rs b/libs/compiler/src/lib.rs index 388d2b2..f7cb92c 100644 --- a/libs/compiler/src/lib.rs +++ b/libs/compiler/src/lib.rs @@ -3,401 +3,4 @@ mod test; mod v2; mod variable_manager; -use parser::Parser as ASTParser; -use parser::sys_call::SysCall; -use parser::tree_node::*; -use quick_error::quick_error; -use std::cmp::Ordering; -use std::collections::HashMap; -use std::io::{BufWriter, Write}; - pub use v2::{Compiler, CompilerConfig, Error}; - -quick_error! { - #[derive(Debug)] - pub enum CompileError { - ParseError(err: parser::Error) { - from() - display("Parse error: {}", err) - } - ScopeError { - display("A fatal error has occurred with the compiler. Scope could not be found.") - } - WriteError(err: std::io::Error) { - from() - display("Write error: {}", err) - } - DuplicateVariable(variable: String) { - display("A variable with the same name already exists in the current scope: {}", variable) - } - VariableNotFound(variable: String) { - display("Variable {} was not found in the current scope.", variable) - } - MissingFunction(name: String) { - display("Function {} was not found in the function table.", name) - } - MissingDevice(name: String) { - display("Device {} was not found in the device table.", name) - } - InvalidSyscall(syscall: SysCall) { - display("Syscall {} is not valid.", syscall) - } - } -} - -pub struct CompilerV1<'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, -} - -impl<'a, W: std::io::Write> CompilerV1<'a, W> { - pub fn new(parser: ASTParser, writer: &'a mut BufWriter) -> Self { - Self { - parser, - variable_scope: Vec::new(), - function_locations: HashMap::new(), - devices: HashMap::new(), - output: writer, - current_line: 0, - declared_main: false, - } - } - - fn get_variable_index(&self, var_name: &str) -> Result { - let mut offset = 0; - - for scope in &self.variable_scope { - let scope_size = scope.len() as i32; - if let Some(index) = scope.get(var_name) { - let index = (scope_size - *index) + offset; - - return Ok(index); - } - - offset += scope_size; - } - - Err(CompileError::VariableNotFound(var_name.to_owned())) - } - - fn push_stack(&mut self, var_name: &str) -> Result<(), CompileError> { - // check to make sure the variable doesn't already exist in the current scope - if self - .variable_scope - .last() - .ok_or(CompileError::ScopeError)? - .contains_key(var_name) - { - return Err(CompileError::DuplicateVariable(var_name.to_string())); - } - - let scope_size = self - .variable_scope - .last() - .ok_or(CompileError::ScopeError)? - .len(); - - self.variable_scope - .last_mut() - .ok_or(CompileError::ScopeError)? - .insert(var_name.to_string(), scope_size as i32); - - Ok(()) - } - - /// Pop the given variable from the current stack. Errors if the variable is not found in the - /// current scope. - fn pop_current(&mut self, var_name: &str) -> Result { - let last_scope = self - .variable_scope - .last_mut() - .ok_or(CompileError::ScopeError)?; - - last_scope - .remove(var_name) - .ok_or(CompileError::VariableNotFound(var_name.to_string())) - } - - fn write_output(&mut self, output: impl Into) -> Result<(), CompileError> { - self.output.write_all(output.into().as_bytes())?; - self.output.write_all(b"\n")?; - self.current_line += 1; - - Ok(()) - } - - pub fn compile(mut self) -> Result<(), CompileError> { - let ast = self.parser.parse_all()?; - - let Some(ast) = ast else { - return Ok(()); - }; - - // Jump directly to the main block. This will avoid executing functions before the main block. - self.write_output("j main")?; - - self.expression(ast)?; - Ok(()) - } - - fn expression(&mut self, expression: Expression) -> Result<(), CompileError> { - match expression { - Expression::Function(expr) => self.function_expression(expr)?, - Expression::Block(expr) => self.block_expression(expr)?, - Expression::Invocation(expr) => self.invocation_expression(expr)?, - Expression::Binary(expr) => self.binary_expression(expr)?, - Expression::Declaration(var_name, expr) => { - self.declaration_expression(&var_name, *expr)? - } - Expression::DeviceDeclaration(DeviceDeclarationExpression { name, device }) => { - self.devices.insert(name, device); - } - _ => todo!("{:?}", expression), - }; - - Ok(()) - } - - fn declaration_expression( - &mut self, - var_name: &str, - expr: Expression, - ) -> Result<(), CompileError> { - match expr { - Expression::Literal(Literal::Number(num)) => { - self.push_stack(var_name)?; - self.write_output(format!("push {num}"))?; - } - Expression::Binary(expr) => { - self.binary_expression(expr)?; - self.push_stack(var_name)?; - } - Expression::Syscall(expr) => { - self.syscall_declaration_expression(expr)?; - self.push_stack(var_name)?; - } - _ => todo!(), - } - - Ok(()) - } - - fn syscall_declaration_expression(&mut self, expr: SysCall) -> Result<(), CompileError> { - use parser::sys_call::System; - #[allow(clippy::collapsible_match)] - match expr { - SysCall::System(ref sys) => match sys { - System::LoadFromDevice(LiteralOrVariable::Variable(device), value) => { - let device = self - .devices - .get(device) - .ok_or(CompileError::MissingDevice(device.clone()))?; - - self.write_output(format!("l r15 {device} {value}"))?; - self.write_output("push r15")?; - } - _ => return Err(CompileError::InvalidSyscall(expr)), - }, - _ => return Err(CompileError::InvalidSyscall(expr)), - } - - Ok(()) - } - - fn binary_expression(&mut self, expr: BinaryExpression) -> Result<(), CompileError> { - self.variable_scope.push(HashMap::new()); - - fn perform_operation( - compiler: &mut CompilerV1, - op: &str, - left: Expression, - right: Expression, - ) -> Result<(), CompileError> { - match left { - Expression::Literal(Literal::Number(num)) => { - compiler.write_output(format!("push {num}"))?; - compiler.push_stack(&format!("{op}ExpressionLeft"))?; - } - Expression::Variable(var_name) => { - let var_offset = compiler.get_variable_index(&var_name)? + 1; - compiler.write_output(format!("sub r15 sp {var_offset}"))?; - compiler.write_output("get r15 db r15")?; - compiler.write_output("push r15")?; - compiler.push_stack(&format!("{op}ExpressionLeft"))?; - } - Expression::Binary(expr) => { - compiler.binary_expression(expr)?; - compiler.push_stack(&format!("{op}ExpressionLeft"))?; - } - Expression::Priority(expr) => match *expr { - Expression::Binary(expr) => { - compiler.binary_expression(expr)?; - compiler.push_stack(&format!("{op}ExpressionLeft"))?; - } - _ => todo!(), - }, - _ => todo!(), - }; - - match right { - Expression::Literal(Literal::Number(num)) => { - compiler.write_output(format!("push {num}"))?; - compiler.push_stack(&format!("{op}ExpressionRight"))?; - } - Expression::Variable(var_name) => { - let var_offset = compiler.get_variable_index(&var_name)? + 1; - compiler.write_output(format!("sub r15 sp {}", var_offset))?; - compiler.write_output("get r15 db r15")?; - compiler.write_output("push r15")?; - compiler.push_stack(&format!("{op}ExpressionRight"))?; - } - Expression::Binary(expr) => { - compiler.binary_expression(expr)?; - compiler.push_stack(&format!("{op}ExpressionRight"))?; - } - Expression::Priority(expr) => match *expr { - Expression::Binary(expr) => { - compiler.binary_expression(expr)?; - compiler.push_stack(&format!("{op}ExpressionRight"))?; - } - _ => todo!(), - }, - _ => todo!(), - }; - - compiler.write_output("pop r1")?; - compiler.write_output("pop r0")?; - compiler.write_output(format!("{op} r0 r0 r1"))?; - compiler.write_output("push r0")?; - - Ok(()) - } - - match expr { - BinaryExpression::Add(left, right) => { - perform_operation(self, "add", *left, *right)?; - } - BinaryExpression::Subtract(left, right) => { - perform_operation(self, "sub", *left, *right)?; - } - BinaryExpression::Multiply(left, right) => { - perform_operation(self, "mul", *left, *right)?; - } - BinaryExpression::Divide(left, right) => { - perform_operation(self, "div", *left, *right)?; - } - _ => todo!("Operation not currently supported"), - } - self.variable_scope.pop(); - - Ok(()) - } - - fn invocation_expression(&mut self, expr: InvocationExpression) -> Result<(), CompileError> { - let function_name = expr.name; - let args_count = expr.arguments.len(); - - let function_line = *self - .function_locations - .get(&function_name) - .ok_or(CompileError::MissingFunction(function_name.clone()))?; - - let mut to_write = String::new(); - - self.push_stack(&format!("{function_name}ReturnAddress"))?; - - for (iter_index, arg) in expr.arguments.into_iter().enumerate() { - match arg { - Expression::Literal(Literal::Number(num)) => { - to_write.push_str(&format!("push {}\n", num)); - } - Expression::Variable(var_name) => { - let index = self.get_variable_index(&var_name)?; - - to_write.push_str(&format!("sub r15 sp {index}\n")); - to_write.push_str("get r15 db r15\n"); - to_write.push_str("push r15\n"); - } - Expression::Binary(expr) => { - self.binary_expression(expr)?; - to_write.push_str("push r0\n"); - } - _ => todo!("something is up with the arguments: {arg:?}"), - } - self.push_stack(&format!("{function_name}Invocation{iter_index}"))?; - } - - // push the return address onto the stack. Current + to write + pushing the return address - let return_addr = self.current_line + to_write.lines().count() + 2; - self.write_output(format!("push {return_addr}"))?; - self.output.write_all(to_write.as_bytes())?; - self.current_line = return_addr - 1; - - self.write_output(format!("j {function_line}"))?; - - self.pop_current(&format!("{function_name}ReturnAddress"))?; - for i in 0..args_count { - self.pop_current(&format!("{function_name}Invocation{i}"))?; - } - - Ok(()) - } - - fn function_expression(&mut self, expression: FunctionExpression) -> Result<(), CompileError> { - let func_name = expression.name; - - self.variable_scope.push(HashMap::new()); - - self.function_locations.insert(func_name, self.current_line); - - for arg in expression.arguments.iter().rev() { - self.push_stack(arg)?; - } - - for expr in expression.body.0 { - self.expression(expr)?; - } - - let scope = self.variable_scope.pop().ok_or(CompileError::ScopeError)?; - - self.write_output(format!("sub sp sp {0}", scope.len()))?; - self.write_output("pop ra")?; - self.write_output("j ra")?; - - Ok(()) - } - - fn block_expression(&mut self, mut expression: BlockExpression) -> Result<(), CompileError> { - self.variable_scope.push(HashMap::new()); - - // hoist functions to the top of the block - expression.0.sort_by(|a, b| { - if matches!(a, Expression::Function(_)) && matches!(b, Expression::Function(_)) { - Ordering::Equal - } else if matches!(a, Expression::Function(_)) { - Ordering::Less - } else { - Ordering::Greater - } - }); - - for expr in expression.0 { - // if we haven't declared main yet and we have already declared all the function expressions, declare main - if !self.declared_main && !matches!(expr, Expression::Function(_)) { - self.write_output("main:")?; - self.declared_main = true; - } - self.expression(expr)?; - } - - self.variable_scope.pop(); - - Ok(()) - } -} diff --git a/libs/compiler/src/test/mod.rs b/libs/compiler/src/test/mod.rs index 3efbc34..20a9f64 100644 --- a/libs/compiler/src/test/mod.rs +++ b/libs/compiler/src/test/mod.rs @@ -1,14 +1,16 @@ +#![allow(clippy::crate_in_macro_def)] + macro_rules! output { ($input:expr) => { String::from_utf8($input.into_inner()?)? }; } -#[macro_export] +#[cfg_attr(test, macro_export)] macro_rules! compile { ($source:expr) => {{ let mut writer = std::io::BufWriter::new(Vec::new()); - let compiler = crate::Compiler::new( + let compiler = ::Compiler::new( parser::Parser::new(tokenizer::Tokenizer::from(String::from($source))), &mut writer, None, diff --git a/libs/compiler/src/variable_manager.rs b/libs/compiler/src/variable_manager.rs index edf42e4..6892f03 100644 --- a/libs/compiler/src/variable_manager.rs +++ b/libs/compiler/src/variable_manager.rs @@ -1,6 +1,7 @@ -// r0 : Return Value / Temp Stack Pointer +// r15 : Return Value +// r0 : Unmanaged temp variable // r1 - r7 : Temporary Variables -// r8 - r15 : Persistant Variables +// r8 - r14 : Persistant Variables use quick_error::quick_error; use std::collections::{HashMap, VecDeque}; @@ -23,15 +24,6 @@ quick_error! { } } -pub enum VarType { - /// 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), -} - /// A request to store a variable at a specific register type pub enum LocationRequest { /// Request to store a variable in a temprary register. @@ -53,7 +45,6 @@ pub struct VariableScope<'a> { temporary_vars: VecDeque, persistant_vars: VecDeque, var_lookup_table: HashMap, - var_stack: HashMap, stack_offset: u16, parent: Option<&'a VariableScope<'a>>, } @@ -65,7 +56,6 @@ impl<'a> Default for VariableScope<'a> { stack_offset: 0, persistant_vars: PERSIST.to_vec().into(), temporary_vars: TEMP.to_vec().into(), - var_stack: HashMap::new(), var_lookup_table: HashMap::new(), } } From fd9964ea5a5661d03f2011073a8d0fdf84ed1d8b Mon Sep 17 00:00:00 2001 From: Devin Bidwell Date: Sat, 22 Nov 2025 17:54:06 -0700 Subject: [PATCH 08/22] Add README.md --- Cargo.lock | 26 +++--- README.md | 30 +++++++ .../test/declaration_function_invocation.rs | 82 +++++++++++++++++++ libs/compiler/src/test/declaration_literal.rs | 4 +- libs/compiler/src/v2.rs | 1 + 5 files changed, 129 insertions(+), 14 deletions(-) create mode 100644 README.md diff --git a/Cargo.lock b/Cargo.lock index 897a276..b534c4b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -197,9 +197,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "clap" -version = "4.5.52" +version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa8120877db0e5c011242f96806ce3c94e0737ab8108532a76a3300a01db2ab8" +checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" dependencies = [ "clap_builder", "clap_derive", @@ -207,9 +207,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.52" +version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02576b399397b659c26064fbc92a75fede9d18ffd5f80ca1cd74ddab167016e1" +checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" dependencies = [ "anstream", "anstyle", @@ -334,9 +334,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" [[package]] name = "heck" @@ -346,12 +346,12 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "indexmap" -version = "2.12.0" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" +checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" dependencies = [ "equivalent", - "hashbrown 0.16.0", + "hashbrown 0.16.1", ] [[package]] @@ -1071,18 +1071,18 @@ checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" [[package]] name = "zerocopy" -version = "0.8.27" +version = "0.8.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +checksum = "43fa6694ed34d6e57407afbccdeecfa268c470a7d2a5b0cf49ce9fcc345afb90" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.27" +version = "0.8.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +checksum = "c640b22cd9817fae95be82f0d2f90b11f7605f6c319d16705c459b27ac2cbc26" dependencies = [ "proc-macro2", "quote", diff --git a/README.md b/README.md new file mode 100644 index 0000000..b169a15 --- /dev/null +++ b/README.md @@ -0,0 +1,30 @@ +# Stationeers Language (slang) + +This is an ambitious attempt at creating: + +- A new programming language (slang) +- A compiler to translate slang -> IC10 +- A mod to allow direct input of slang in the in-game script editor to + automatically compile to IC10 before running + +This project currently outputs 3 files: + +- A Linux CLI +- A Windows CLI +- A Windows FFI dll + - Contains a single function: `compile_from_string` + +The aim of this project is to lower the amount of time it takes to code simple +scripts in Stationeers so you can get back to engineering atmospherics or +whatever you are working on. This project is NOT meant to fully replace IC10. +Obviously hand-coded assembly written by an experienced programmer is more +optimized and smaller than something that a C compiler will spit out. This is +the same way. It WILL produce valid IC10, but for large complicated projects it +might produce over the allowed limit of lines the in-game editor supports. + +Current Unknowns + +- Should I support a configurable script line length in-game to allow larger + scripts to be saved? +- Should compilation be "behind the scenes" (in game editor will ALWAYS be what + you put in. IC10 will be IC10, slang will be slang) diff --git a/libs/compiler/src/test/declaration_function_invocation.rs b/libs/compiler/src/test/declaration_function_invocation.rs index 3a028ca..02302a1 100644 --- a/libs/compiler/src/test/declaration_function_invocation.rs +++ b/libs/compiler/src/test/declaration_function_invocation.rs @@ -88,3 +88,85 @@ fn incorrect_args_count() -> anyhow::Result<()> { Ok(()) } + +#[test] +fn inline_literal_args() -> anyhow::Result<()> { + let compiled = compile! { + debug + " + fn doSomething(arg1, arg2) {}; + let thisVariableShouldStayInPlace = 123; + let returnedValue = doSomething(12, 34); + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + doSomething: + pop r8 #arg2 + pop r9 #arg1 + push ra + sub r0 sp 1 + get ra db r0 + sub sp sp 1 + j ra + main: + move r8 123 #thisVariableShouldStayInPlace + push r8 + push 12 + push 34 + jal doSomething + sub r0 sp 1 + get r8 db r0 + sub sp sp 1 + move r9 r15 #returnedValue + " + } + ); + + Ok(()) +} + +#[test] +fn mixed_args() -> anyhow::Result<()> { + let compiled = compile! { + debug + " + let arg1 = 123; + let returnValue = doSomething(arg1, 456); + fn doSomething(arg1, arg2) {}; + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + doSomething: + pop r8 #arg2 + pop r9 #arg1 + push ra + sub r0 sp 1 + get ra db r0 + sub sp sp 1 + j ra + main: + move r8 123 #arg1 + push r8 + push r8 + push 456 + jal doSomething + sub r0 sp 1 + get r8 db r0 + sub sp sp 1 + move r9 r15 #returnValue + " + } + ); + + Ok(()) +} diff --git a/libs/compiler/src/test/declaration_literal.rs b/libs/compiler/src/test/declaration_literal.rs index 5a881a4..b5e88cd 100644 --- a/libs/compiler/src/test/declaration_literal.rs +++ b/libs/compiler/src/test/declaration_literal.rs @@ -25,7 +25,9 @@ fn variable_declaration_numeric_literal() -> anyhow::Result<()> { #[test] fn variable_declaration_numeric_literal_stack_spillover() -> anyhow::Result<()> { - let compiled = compile! {debug r#" + let compiled = compile! { + debug + r#" let a = 0; let b = 1; let c = 2; diff --git a/libs/compiler/src/v2.rs b/libs/compiler/src/v2.rs index c411d8c..6ccc637 100644 --- a/libs/compiler/src/v2.rs +++ b/libs/compiler/src/v2.rs @@ -53,6 +53,7 @@ quick_error! { } #[derive(Default)] +#[repr(C)] pub struct CompilerConfig { pub debug: bool, } From 4f837ee9748e74e1dad1423ef6db6378a3285166 Mon Sep 17 00:00:00 2001 From: Devin Bidwell Date: Sat, 22 Nov 2025 20:18:56 -0700 Subject: [PATCH 09/22] Cleanup dead code --- LICENSE | 2 +- libs/compiler/src/v2.rs | 59 ++++++++++++++++++++++++--- libs/compiler/src/variable_manager.rs | 15 +++---- 3 files changed, 61 insertions(+), 15 deletions(-) diff --git a/LICENSE b/LICENSE index 789d263..23f03a2 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 Devin Bidwell +Copyright (c) 2024-2025 Devin Bidwell Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/libs/compiler/src/v2.rs b/libs/compiler/src/v2.rs index 6ccc637..2f41ccd 100644 --- a/libs/compiler/src/v2.rs +++ b/libs/compiler/src/v2.rs @@ -34,7 +34,7 @@ quick_error! { ScopeError(error: variable_manager::Error) { from() } - DuplicateFunction(func_name: String) { + DuplicateIdentifier(func_name: String) { display("`{func_name}` has already been defined") } UnknownIdentifier(ident: String) { @@ -111,7 +111,7 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { match expr { Expression::Function(expr_func) => self.expression_function(expr_func, scope)?, Expression::Block(expr_block) => self.expression_block(expr_block, scope)?, - Expression::DeviceDeclaration(expr_dev) => self.expression_device(expr_dev), + Expression::DeviceDeclaration(expr_dev) => self.expression_device(expr_dev)?, Expression::Declaration(var_name, expr) => { self.expression_declaration(var_name, *expr, scope)? } @@ -258,8 +258,13 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { Ok(()) } - fn expression_device(&mut self, expr: DeviceDeclarationExpression) { + fn expression_device(&mut self, expr: DeviceDeclarationExpression) -> Result<(), Error> { + if self.devices.contains_key(&expr.name) { + return Err(Error::DuplicateIdentifier(expr.name)); + } self.devices.insert(expr.name, expr.device); + + Ok(()) } fn expression_block<'v>( @@ -293,6 +298,42 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { Ok(()) } + /// Takes the result of the expression and stores it in VariableScope::RETURN_REGISTER + fn expression_return<'v>( + &mut self, + expr: Expression, + scope: &mut VariableScope<'v>, + ) -> Result<(), Error> { + match expr { + Expression::Variable(var_name) => match scope.get_location_of(var_name)? { + VariableLocation::Temporary(reg) | VariableLocation::Persistant(reg) => { + self.write_output(format!( + "move r{} r{reg} {}", + VariableScope::RETURN_REGISTER, + debug!(self, "returnValue") + ))?; + } + VariableLocation::Stack(offset) => { + self.write_output(format!( + "sub r{} sp {offset}", + VariableScope::TEMP_STACK_REGISTER + ))?; + self.write_output(format!( + "get r{} db r{}", + VariableScope::RETURN_REGISTER, + VariableScope::TEMP_STACK_REGISTER + ))?; + } + }, + Expression::Literal(Literal::Number(num)) => { + self.write_output(format!("move r{} {}", VariableScope::RETURN_REGISTER, num))?; + } + _ => return Err(Error::Unknown("Unsupported `return` statement.".into())), + } + + Ok(()) + } + /// Compile a function declaration. /// Calees are responsible for backing up any registers they wish to use. fn expression_function<'v>( @@ -307,7 +348,7 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { } = expr; if self.function_locations.contains_key(&name) { - return Err(Error::DuplicateFunction(name)); + return Err(Error::DuplicateIdentifier(name)); } self.function_metadata @@ -364,7 +405,15 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { self.write_output("push ra")?; block_scope.add_variable(format!("{name}_ra"), LocationRequest::Stack)?; - self.expression_block(body, &mut block_scope)?; + for expr in body.0 { + match expr { + Expression::Return(ret_expr) => { + self.expression_return(*ret_expr, &mut block_scope)? + } + _ => self.expression(expr, &mut block_scope)?, + } + } + // Get the saved return address and save it back into `ra` let VariableLocation::Stack(ra_stack_offset) = block_scope.get_location_of(format!("{name}_ra"))? diff --git a/libs/compiler/src/variable_manager.rs b/libs/compiler/src/variable_manager.rs index 6892f03..ecf29ca 100644 --- a/libs/compiler/src/variable_manager.rs +++ b/libs/compiler/src/variable_manager.rs @@ -26,6 +26,7 @@ quick_error! { /// A request to store a variable at a specific register type pub enum LocationRequest { + #[allow(dead_code)] /// Request to store a variable in a temprary register. Temp, /// Request to store a variable in a persistant register. @@ -62,6 +63,7 @@ impl<'a> Default for VariableScope<'a> { } impl<'a> VariableScope<'a> { + #[allow(dead_code)] pub const TEMP_REGISTER_COUNT: u8 = 7; pub const PERSIST_REGISTER_COUNT: u8 = 7; @@ -94,10 +96,6 @@ impl<'a> VariableScope<'a> { self.stack_offset } - pub fn stack_incr(&mut self) { - self.stack_offset += 1; - } - /// Adds and tracks a new scoped variable. If the location you request is full, will fall back /// to the stack. pub fn add_variable( @@ -112,8 +110,7 @@ impl<'a> VariableScope<'a> { let var_location = match location { LocationRequest::Temp => { if let Some(next_var) = self.temporary_vars.pop_front() { - let loc = VariableLocation::Temporary(next_var); - loc + VariableLocation::Temporary(next_var) } else { let loc = VariableLocation::Stack(self.stack_offset); self.stack_offset += 1; @@ -122,8 +119,7 @@ impl<'a> VariableScope<'a> { } LocationRequest::Persist => { if let Some(next_var) = self.persistant_vars.pop_front() { - let loc = VariableLocation::Persistant(next_var); - loc + VariableLocation::Persistant(next_var) } else { let loc = VariableLocation::Stack(self.stack_offset); self.stack_offset += 1; @@ -149,7 +145,7 @@ impl<'a> VariableScope<'a> { let var = self .var_lookup_table .get(var_name.as_str()) - .map(|v| v.clone()) + .cloned() .ok_or(Error::UnknownVariable(var_name))?; if let VariableLocation::Stack(inserted_at_offset) = var { @@ -165,6 +161,7 @@ impl<'a> VariableScope<'a> { self.parent.is_some() } + #[allow(dead_code)] pub fn free_temp(&mut self, var_name: impl Into) -> Result<(), Error> { let var_name = var_name.into(); let Some(location) = self.var_lookup_table.remove(var_name.as_str()) else { From 1706698ffaee4cf78210094506593f01685c103d Mon Sep 17 00:00:00 2001 From: Devin Bidwell Date: Sat, 22 Nov 2025 20:53:49 -0700 Subject: [PATCH 10/22] basic support for return values --- .../test/declaration_function_invocation.rs | 37 +++++++++++++++++++ libs/compiler/src/v2.rs | 2 +- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/libs/compiler/src/test/declaration_function_invocation.rs b/libs/compiler/src/test/declaration_function_invocation.rs index 02302a1..8be25cb 100644 --- a/libs/compiler/src/test/declaration_function_invocation.rs +++ b/libs/compiler/src/test/declaration_function_invocation.rs @@ -170,3 +170,40 @@ fn mixed_args() -> anyhow::Result<()> { Ok(()) } + +#[test] +fn with_return_statement() -> anyhow::Result<()> { + let compiled = compile! { + debug + " + fn doSomething(arg1) { + return 456; + }; + + let returned = doSomething(123); + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + doSomething: + pop r8 #arg1 + push ra + move r15 456 + sub r0 sp 1 + get ra db r0 + sub sp sp 1 + j ra + main: + push 123 + jal doSomething + move r8 r15 #returned + " + } + ); + + Ok(()) +} diff --git a/libs/compiler/src/v2.rs b/libs/compiler/src/v2.rs index 2f41ccd..19fdc15 100644 --- a/libs/compiler/src/v2.rs +++ b/libs/compiler/src/v2.rs @@ -310,7 +310,7 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { self.write_output(format!( "move r{} r{reg} {}", VariableScope::RETURN_REGISTER, - debug!(self, "returnValue") + debug!(self, "#returnValue") ))?; } VariableLocation::Stack(offset) => { From 17089b53e1cb4413f658e1bdd71a31605e76c0b0 Mon Sep 17 00:00:00 2001 From: Devin Bidwell Date: Sat, 22 Nov 2025 22:44:25 -0700 Subject: [PATCH 11/22] emit negated declarations --- 2 | 470 ++++++++++++++++++ libs/compiler/src/test/declaration_literal.rs | 23 + libs/compiler/src/v2.rs | 77 ++- libs/compiler/src/variable_manager.rs | 3 + libs/parser/src/lib.rs | 8 + libs/tokenizer/src/token.rs | 6 + 6 files changed, 560 insertions(+), 27 deletions(-) create mode 100644 2 diff --git a/2 b/2 new file mode 100644 index 0000000..68fd996 --- /dev/null +++ b/2 @@ -0,0 +1,470 @@ +use crate::variable_manager::{self, LocationRequest, VariableLocation, VariableScope}; +use parser::{ + Parser as ASTParser, + tree_node::{ + BinaryExpression, BlockExpression, DeviceDeclarationExpression, Expression, + FunctionExpression, InvocationExpression, Literal, + }, +}; +use quick_error::quick_error; +use std::{ + collections::HashMap, + io::{BufWriter, Write}, +}; + +macro_rules! debug { + ($self: expr, $debug_value: expr) => { + if $self.config.debug { + format!($debug_value) + } else { + "".into() + } + }; +} + +quick_error! { + #[derive(Debug)] + pub enum Error { + ParseError(error: parser::Error) { + from() + } + IoError(error: std::io::Error) { + from() + } + ScopeError(error: variable_manager::Error) { + from() + } + DuplicateIdentifier(func_name: String) { + display("`{func_name}` has already been defined") + } + UnknownIdentifier(ident: String) { + display("`{ident}` is not found in the current scope.") + } + InvalidDevice(device: String) { + display("`{device}` is not valid") + } + AgrumentMismatch(func_name: String) { + display("Incorrect number of arguments passed into `{func_name}`") + } + Unknown(reason: String) { + display("{reason}") + } + } +} + +#[derive(Default)] +#[repr(C)] +pub struct CompilerConfig { + pub debug: bool, +} + +pub struct Compiler<'a, W: std::io::Write> { + parser: ASTParser, + function_locations: HashMap, + function_metadata: 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, + function_locations: HashMap::new(), + function_metadata: HashMap::new(), + devices: HashMap::new(), + output: writer, + current_line: 1, + declared_main: false, + config: config.unwrap_or_default(), + } + } + + pub fn compile(mut self) -> Result<(), Error> { + 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<(), Error> { + 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<(), Error> { + match expr { + Expression::Function(expr_func) => self.expression_function(expr_func, scope)?, + Expression::Block(expr_block) => self.expression_block(expr_block, scope)?, + Expression::DeviceDeclaration(expr_dev) => self.expression_device(expr_dev)?, + Expression::Declaration(var_name, expr) => { + self.expression_declaration(var_name, *expr, scope)? + } + Expression::Invocation(expr_invoke) => { + self.expression_function_invocation(expr_invoke, scope)? + } + _ => todo!(), + }; + + Ok(()) + } + + fn emit_variable_assignment( + &mut self, + var_name: &str, + location: VariableLocation, + source_value: &str, + ) -> Result<(), Error> { + let debug_tag = if self.config.debug { + format!("#{var_name}") + } else { + String::new() + }; + todo!() + } + + fn expression_declaration<'v>( + &mut self, + var_name: String, + expr: Expression, + scope: &mut VariableScope<'v>, + ) -> Result<(), Error> { + // optimization. Check for a negated numeric literal + if let Expression::Negation(box_expr) = &expr + && let Expression::Literal(Literal::Number(neg_num)) = &**box_expr + { + return Ok(()); + } + + match expr { + Expression::Literal(Literal::Number(num)) => { + let var_location = + scope.add_variable(var_name.clone(), LocationRequest::Persist)?; + + if let VariableLocation::Temporary(reg) | VariableLocation::Persistant(reg) = + var_location + { + self.write_output(format!( + "move r{reg} {num} {}", + debug!(self, "#{var_name}") + ))?; + } else { + self.write_output(format!("push {num} {}", debug!(self, "#{var_name}")))?; + } + } + Expression::Invocation(invoke_expr) => { + self.expression_function_invocation(invoke_expr, scope)?; + + // Return value _should_ be in VariableScope::RETURN_REGISTER + match scope.add_variable(var_name.clone(), LocationRequest::Persist)? { + VariableLocation::Temporary(reg) | VariableLocation::Persistant(reg) => self + .write_output(format!( + "move r{reg} r{} {}", + VariableScope::RETURN_REGISTER, + debug!(self, "#{var_name}") + ))?, + VariableLocation::Stack(_) => self.write_output(format!( + "push r{} {}", + VariableScope::RETURN_REGISTER, + debug!(self, "#{var_name}") + ))?, + } + } + _ => { + return Err(Error::Unknown( + "`{var_name}` declaration of this type is not supported.".into(), + )); + } + } + + Ok(()) + } + + fn expression_function_invocation( + &mut self, + invoke_expr: InvocationExpression, + stack: &mut VariableScope, + ) -> Result<(), Error> { + if !self.function_locations.contains_key(&invoke_expr.name) { + return Err(Error::UnknownIdentifier(invoke_expr.name)); + } + + let Some(args) = self.function_metadata.get(&invoke_expr.name) else { + return Err(Error::UnknownIdentifier(invoke_expr.name)); + }; + + if args.len() != invoke_expr.arguments.len() { + return Err(Error::AgrumentMismatch(invoke_expr.name)); + } + + // backup all used registers to the stack + let active_registers = stack.registers().cloned().collect::>(); + for register in &active_registers { + stack.add_variable(format!("temp_{register}"), LocationRequest::Stack)?; + self.write_output(format!("push r{register}"))?; + } + for arg in invoke_expr.arguments { + match arg { + Expression::Literal(Literal::Number(num)) => { + let num_str = num.to_string(); + self.write_output(format!("push {num_str}"))?; + } + Expression::Variable(var_name) => match stack.get_location_of(var_name)? { + VariableLocation::Persistant(reg) | VariableLocation::Temporary(reg) => { + self.write_output(format!("push r{reg}"))?; + } + VariableLocation::Stack(stack_offset) => { + self.write_output(format!( + "sub r{0} sp {stack_offset}", + VariableScope::TEMP_STACK_REGISTER + ))?; + self.write_output(format!( + "get r{0} db r{0}", + VariableScope::TEMP_STACK_REGISTER + ))?; + self.write_output(format!( + "push r{0}", + VariableScope::TEMP_STACK_REGISTER + ))?; + } + }, + _ => { + return Err(Error::Unknown(format!( + "Attempted to call `{}` with an unsupported argument", + invoke_expr.name + ))); + } + } + } + + // jump to the function and store current line in ra + self.write_output(format!("jal {}", invoke_expr.name))?; + + for register in active_registers { + let VariableLocation::Stack(stack_offset) = + stack.get_location_of(format!("temp_{register}"))? + else { + return Err(Error::UnknownIdentifier(format!("temp_{register}"))); + }; + self.write_output(format!( + "sub r{0} sp {stack_offset}", + VariableScope::TEMP_STACK_REGISTER + ))?; + self.write_output(format!( + "get r{register} db r{0}", + VariableScope::TEMP_STACK_REGISTER + ))?; + } + + if stack.stack_offset() > 0 { + self.write_output(format!("sub sp sp {}", stack.stack_offset()))?; + } + + Ok(()) + } + + fn expression_device(&mut self, expr: DeviceDeclarationExpression) -> Result<(), Error> { + if self.devices.contains_key(&expr.name) { + return Err(Error::DuplicateIdentifier(expr.name)); + } + self.devices.insert(expr.name, expr.device); + + Ok(()) + } + + fn expression_binary<'v>( + &mut self, + expr: BinaryExpression, + scope: &mut VariableScope<'v>, + ) -> Result<(), Error> { + Ok(()) + } + + fn expression_block<'v>( + &mut self, + mut expr: BlockExpression, + scope: &mut VariableScope<'v>, + ) -> Result<(), Error> { + // 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(_)) + && !scope.has_parent() + { + self.write_output("main:")?; + self.declared_main = true; + } + + self.expression(expr, scope)?; + } + + Ok(()) + } + + /// Takes the result of the expression and stores it in VariableScope::RETURN_REGISTER + fn expression_return<'v>( + &mut self, + expr: Expression, + scope: &mut VariableScope<'v>, + ) -> Result<(), Error> { + match expr { + Expression::Variable(var_name) => match scope.get_location_of(var_name)? { + VariableLocation::Temporary(reg) | VariableLocation::Persistant(reg) => { + self.write_output(format!( + "move r{} r{reg} {}", + VariableScope::RETURN_REGISTER, + debug!(self, "#returnValue") + ))?; + } + VariableLocation::Stack(offset) => { + self.write_output(format!( + "sub r{} sp {offset}", + VariableScope::TEMP_STACK_REGISTER + ))?; + self.write_output(format!( + "get r{} db r{}", + VariableScope::RETURN_REGISTER, + VariableScope::TEMP_STACK_REGISTER + ))?; + } + }, + Expression::Literal(Literal::Number(num)) => { + self.write_output(format!("move r{} {}", VariableScope::RETURN_REGISTER, num))?; + } + _ => return Err(Error::Unknown("Unsupported `return` statement.".into())), + } + + Ok(()) + } + + /// Compile a function declaration. + /// Calees are responsible for backing up any registers they wish to use. + fn expression_function<'v>( + &mut self, + expr: FunctionExpression, + scope: &mut VariableScope<'v>, + ) -> Result<(), Error> { + let FunctionExpression { + name, + arguments, + body, + } = expr; + + if self.function_locations.contains_key(&name) { + return Err(Error::DuplicateIdentifier(name)); + } + + self.function_metadata + .insert(name.clone(), arguments.clone()); + + // Declare the function as a line identifier + self.write_output(format!("{}:", name))?; + + self.function_locations + .insert(name.clone(), self.current_line); + + // Create a new block scope for the function body + let mut block_scope = VariableScope::scoped(scope); + + let mut saved_variables = 0; + + // do a reverse pass to pop variables from the stack and put them into registers + for var_name in arguments + .iter() + .rev() + .take(VariableScope::PERSIST_REGISTER_COUNT as usize) + { + let loc = block_scope.add_variable(var_name, LocationRequest::Persist)?; + // we don't need to imcrement the stack offset as it's already on the stack from the + // previous scope + + match loc { + VariableLocation::Persistant(loc) => { + self.write_output(format!("pop r{loc} {}", debug!(self, "#{var_name}")))?; + } + VariableLocation::Stack(_) => { + return Err(Error::Unknown( + "Attempted to save to stack without tracking in scope".into(), + )); + } + + _ => { + return Err(Error::Unknown( + "Attempted to return a Temporary scoped variable from a Persistant request" + .into(), + )); + } + } + saved_variables += 1; + } + + // now do a forward pass in case we have spilled into the stack. We don't need to push + // anything as they already exist on the stack, but we DO need to let our block_scope be + // aware that the variables exist on the stack (left to right) + for var_name in arguments.iter().take(arguments.len() - saved_variables) { + block_scope.add_variable(var_name, LocationRequest::Stack)?; + } + + self.write_output("push ra")?; + block_scope.add_variable(format!("{name}_ra"), LocationRequest::Stack)?; + + for expr in body.0 { + match expr { + Expression::Return(ret_expr) => { + self.expression_return(*ret_expr, &mut block_scope)? + } + _ => self.expression(expr, &mut block_scope)?, + } + } + + // Get the saved return address and save it back into `ra` + let VariableLocation::Stack(ra_stack_offset) = + block_scope.get_location_of(format!("{name}_ra"))? + else { + return Err(Error::Unknown( + "Stored return address not in stack as expected".into(), + )); + }; + + self.write_output(format!( + "sub r{0} sp {ra_stack_offset}", + VariableScope::TEMP_STACK_REGISTER + ))?; + self.write_output(format!( + "get ra db r{0}", + VariableScope::TEMP_STACK_REGISTER + ))?; + + if block_scope.stack_offset() > 0 { + self.write_output(format!("sub sp sp {}", block_scope.stack_offset()))?; + } + + self.write_output("j ra")?; + Ok(()) + } +} diff --git a/libs/compiler/src/test/declaration_literal.rs b/libs/compiler/src/test/declaration_literal.rs index b5e88cd..bcfec16 100644 --- a/libs/compiler/src/test/declaration_literal.rs +++ b/libs/compiler/src/test/declaration_literal.rs @@ -62,3 +62,26 @@ fn variable_declaration_numeric_literal_stack_spillover() -> anyhow::Result<()> Ok(()) } + +#[test] +fn variable_declaration_negative() -> anyhow::Result<()> { + let compiled = compile! { + debug + " + let i = -1; + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + main: + move r8 -1 #i + " + } + ); + + Ok(()) +} diff --git a/libs/compiler/src/v2.rs b/libs/compiler/src/v2.rs index 19fdc15..979319f 100644 --- a/libs/compiler/src/v2.rs +++ b/libs/compiler/src/v2.rs @@ -2,8 +2,8 @@ use crate::variable_manager::{self, LocationRequest, VariableLocation, VariableS use parser::{ Parser as ASTParser, tree_node::{ - BlockExpression, DeviceDeclarationExpression, Expression, FunctionExpression, - InvocationExpression, Literal, + BinaryExpression, BlockExpression, DeviceDeclarationExpression, Expression, + FunctionExpression, InvocationExpression, Literal, }, }; use quick_error::quick_error; @@ -124,46 +124,61 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { Ok(()) } + fn emit_variable_assignment( + &mut self, + var_name: &str, + location: VariableLocation, + source_value: impl Into, + ) -> Result<(), Error> { + let debug_tag = if self.config.debug { + format!(" #{var_name}") + } else { + String::new() + }; + + match location { + VariableLocation::Temporary(reg) | VariableLocation::Persistant(reg) => { + self.write_output(format!("move r{reg} {}{debug_tag}", source_value.into()))?; + } + VariableLocation::Stack(_) => { + self.write_output(format!("push {}{debug_tag}", source_value.into()))?; + } + } + + Ok(()) + } + fn expression_declaration<'v>( &mut self, var_name: String, expr: Expression, scope: &mut VariableScope<'v>, ) -> Result<(), Error> { + // optimization. Check for a negated numeric literal + if let Expression::Negation(box_expr) = &expr + && let Expression::Literal(Literal::Number(neg_num)) = &**box_expr + { + let loc = scope.add_variable(&var_name, LocationRequest::Persist)?; + self.emit_variable_assignment(&var_name, loc, format!("-{neg_num}"))?; + return Ok(()); + } + match expr { Expression::Literal(Literal::Number(num)) => { let var_location = scope.add_variable(var_name.clone(), LocationRequest::Persist)?; - let num_str = num.to_string(); - if let VariableLocation::Temporary(reg) | VariableLocation::Persistant(reg) = - var_location - { - self.write_output(format!( - "move r{reg} {num_str} {}", - debug!(self, "#{var_name}") - ))?; - } else { - self.write_output(format!("push {num_str} {}", debug!(self, "#{var_name}")))?; - } + self.emit_variable_assignment(&var_name, var_location, num)?; } Expression::Invocation(invoke_expr) => { self.expression_function_invocation(invoke_expr, scope)?; - // Return value _should_ be in VariableScope::RETURN_REGISTER - match scope.add_variable(var_name.clone(), LocationRequest::Persist)? { - VariableLocation::Temporary(reg) | VariableLocation::Persistant(reg) => self - .write_output(format!( - "move r{reg} r{} {}", - VariableScope::RETURN_REGISTER, - debug!(self, "#{var_name}") - ))?, - VariableLocation::Stack(_) => self.write_output(format!( - "push r{} {}", - VariableScope::RETURN_REGISTER, - debug!(self, "#{var_name}") - ))?, - } + let loc = scope.add_variable(&var_name, LocationRequest::Persist)?; + self.emit_variable_assignment( + &var_name, + loc, + format!("r{}", VariableScope::RETURN_REGISTER), + )?; } _ => { return Err(Error::Unknown( @@ -267,6 +282,14 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { Ok(()) } + fn expression_binary<'v>( + &mut self, + expr: BinaryExpression, + scope: &mut VariableScope<'v>, + ) -> Result<(), Error> { + Ok(()) + } + fn expression_block<'v>( &mut self, mut expr: BlockExpression, diff --git a/libs/compiler/src/variable_manager.rs b/libs/compiler/src/variable_manager.rs index ecf29ca..ca47f1c 100644 --- a/libs/compiler/src/variable_manager.rs +++ b/libs/compiler/src/variable_manager.rs @@ -37,8 +37,11 @@ pub enum LocationRequest { #[derive(Clone)] pub enum VariableLocation { + /// Represents a temporary register (r1 - r7) Temporary(u8), + /// Represents a persistant register (r8 - r14) Persistant(u8), + /// Represents a a stack offset (current stack - offset = variable loc) Stack(u16), } diff --git a/libs/parser/src/lib.rs b/libs/parser/src/lib.rs index 5e597a6..f3a356e 100644 --- a/libs/parser/src/lib.rs +++ b/libs/parser/src/lib.rs @@ -214,6 +214,14 @@ impl Parser { // match priority expressions with a left parenthesis TokenType::Symbol(Symbol::LParen) => Expression::Priority(self.priority()?), + // match minus symbols to handle negative numbers or negated expressions + TokenType::Symbol(Symbol::Minus) => { + self.assign_next()?; // consume the `-` symbol + let inner_expr = self.expression()?.ok_or(Error::UnexpectedEOF)?; + + Expression::Negation(boxed!(inner_expr)) + } + _ => { return Err(Error::UnexpectedToken(current_token.clone())); } diff --git a/libs/tokenizer/src/token.rs b/libs/tokenizer/src/token.rs index 9e31ca5..c1de1c3 100644 --- a/libs/tokenizer/src/token.rs +++ b/libs/tokenizer/src/token.rs @@ -111,6 +111,12 @@ impl std::fmt::Display for Number { } } +impl std::convert::From for String { + fn from(value: Number) -> Self { + value.to_string() + } +} + #[derive(Debug, PartialEq, Hash, Eq, Clone, Copy)] pub enum Symbol { // Single Character Symbols From 6e7e5ba9f16475ee0d6834cc8eae21c6f952d064 Mon Sep 17 00:00:00 2001 From: Devin Bidwell Date: Sat, 22 Nov 2025 22:44:59 -0700 Subject: [PATCH 12/22] Removed weirdly added '2' file --- 2 | 470 -------------------------------------------------------------- 1 file changed, 470 deletions(-) delete mode 100644 2 diff --git a/2 b/2 deleted file mode 100644 index 68fd996..0000000 --- a/2 +++ /dev/null @@ -1,470 +0,0 @@ -use crate::variable_manager::{self, LocationRequest, VariableLocation, VariableScope}; -use parser::{ - Parser as ASTParser, - tree_node::{ - BinaryExpression, BlockExpression, DeviceDeclarationExpression, Expression, - FunctionExpression, InvocationExpression, Literal, - }, -}; -use quick_error::quick_error; -use std::{ - collections::HashMap, - io::{BufWriter, Write}, -}; - -macro_rules! debug { - ($self: expr, $debug_value: expr) => { - if $self.config.debug { - format!($debug_value) - } else { - "".into() - } - }; -} - -quick_error! { - #[derive(Debug)] - pub enum Error { - ParseError(error: parser::Error) { - from() - } - IoError(error: std::io::Error) { - from() - } - ScopeError(error: variable_manager::Error) { - from() - } - DuplicateIdentifier(func_name: String) { - display("`{func_name}` has already been defined") - } - UnknownIdentifier(ident: String) { - display("`{ident}` is not found in the current scope.") - } - InvalidDevice(device: String) { - display("`{device}` is not valid") - } - AgrumentMismatch(func_name: String) { - display("Incorrect number of arguments passed into `{func_name}`") - } - Unknown(reason: String) { - display("{reason}") - } - } -} - -#[derive(Default)] -#[repr(C)] -pub struct CompilerConfig { - pub debug: bool, -} - -pub struct Compiler<'a, W: std::io::Write> { - parser: ASTParser, - function_locations: HashMap, - function_metadata: 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, - function_locations: HashMap::new(), - function_metadata: HashMap::new(), - devices: HashMap::new(), - output: writer, - current_line: 1, - declared_main: false, - config: config.unwrap_or_default(), - } - } - - pub fn compile(mut self) -> Result<(), Error> { - 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<(), Error> { - 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<(), Error> { - match expr { - Expression::Function(expr_func) => self.expression_function(expr_func, scope)?, - Expression::Block(expr_block) => self.expression_block(expr_block, scope)?, - Expression::DeviceDeclaration(expr_dev) => self.expression_device(expr_dev)?, - Expression::Declaration(var_name, expr) => { - self.expression_declaration(var_name, *expr, scope)? - } - Expression::Invocation(expr_invoke) => { - self.expression_function_invocation(expr_invoke, scope)? - } - _ => todo!(), - }; - - Ok(()) - } - - fn emit_variable_assignment( - &mut self, - var_name: &str, - location: VariableLocation, - source_value: &str, - ) -> Result<(), Error> { - let debug_tag = if self.config.debug { - format!("#{var_name}") - } else { - String::new() - }; - todo!() - } - - fn expression_declaration<'v>( - &mut self, - var_name: String, - expr: Expression, - scope: &mut VariableScope<'v>, - ) -> Result<(), Error> { - // optimization. Check for a negated numeric literal - if let Expression::Negation(box_expr) = &expr - && let Expression::Literal(Literal::Number(neg_num)) = &**box_expr - { - return Ok(()); - } - - match expr { - Expression::Literal(Literal::Number(num)) => { - let var_location = - scope.add_variable(var_name.clone(), LocationRequest::Persist)?; - - if let VariableLocation::Temporary(reg) | VariableLocation::Persistant(reg) = - var_location - { - self.write_output(format!( - "move r{reg} {num} {}", - debug!(self, "#{var_name}") - ))?; - } else { - self.write_output(format!("push {num} {}", debug!(self, "#{var_name}")))?; - } - } - Expression::Invocation(invoke_expr) => { - self.expression_function_invocation(invoke_expr, scope)?; - - // Return value _should_ be in VariableScope::RETURN_REGISTER - match scope.add_variable(var_name.clone(), LocationRequest::Persist)? { - VariableLocation::Temporary(reg) | VariableLocation::Persistant(reg) => self - .write_output(format!( - "move r{reg} r{} {}", - VariableScope::RETURN_REGISTER, - debug!(self, "#{var_name}") - ))?, - VariableLocation::Stack(_) => self.write_output(format!( - "push r{} {}", - VariableScope::RETURN_REGISTER, - debug!(self, "#{var_name}") - ))?, - } - } - _ => { - return Err(Error::Unknown( - "`{var_name}` declaration of this type is not supported.".into(), - )); - } - } - - Ok(()) - } - - fn expression_function_invocation( - &mut self, - invoke_expr: InvocationExpression, - stack: &mut VariableScope, - ) -> Result<(), Error> { - if !self.function_locations.contains_key(&invoke_expr.name) { - return Err(Error::UnknownIdentifier(invoke_expr.name)); - } - - let Some(args) = self.function_metadata.get(&invoke_expr.name) else { - return Err(Error::UnknownIdentifier(invoke_expr.name)); - }; - - if args.len() != invoke_expr.arguments.len() { - return Err(Error::AgrumentMismatch(invoke_expr.name)); - } - - // backup all used registers to the stack - let active_registers = stack.registers().cloned().collect::>(); - for register in &active_registers { - stack.add_variable(format!("temp_{register}"), LocationRequest::Stack)?; - self.write_output(format!("push r{register}"))?; - } - for arg in invoke_expr.arguments { - match arg { - Expression::Literal(Literal::Number(num)) => { - let num_str = num.to_string(); - self.write_output(format!("push {num_str}"))?; - } - Expression::Variable(var_name) => match stack.get_location_of(var_name)? { - VariableLocation::Persistant(reg) | VariableLocation::Temporary(reg) => { - self.write_output(format!("push r{reg}"))?; - } - VariableLocation::Stack(stack_offset) => { - self.write_output(format!( - "sub r{0} sp {stack_offset}", - VariableScope::TEMP_STACK_REGISTER - ))?; - self.write_output(format!( - "get r{0} db r{0}", - VariableScope::TEMP_STACK_REGISTER - ))?; - self.write_output(format!( - "push r{0}", - VariableScope::TEMP_STACK_REGISTER - ))?; - } - }, - _ => { - return Err(Error::Unknown(format!( - "Attempted to call `{}` with an unsupported argument", - invoke_expr.name - ))); - } - } - } - - // jump to the function and store current line in ra - self.write_output(format!("jal {}", invoke_expr.name))?; - - for register in active_registers { - let VariableLocation::Stack(stack_offset) = - stack.get_location_of(format!("temp_{register}"))? - else { - return Err(Error::UnknownIdentifier(format!("temp_{register}"))); - }; - self.write_output(format!( - "sub r{0} sp {stack_offset}", - VariableScope::TEMP_STACK_REGISTER - ))?; - self.write_output(format!( - "get r{register} db r{0}", - VariableScope::TEMP_STACK_REGISTER - ))?; - } - - if stack.stack_offset() > 0 { - self.write_output(format!("sub sp sp {}", stack.stack_offset()))?; - } - - Ok(()) - } - - fn expression_device(&mut self, expr: DeviceDeclarationExpression) -> Result<(), Error> { - if self.devices.contains_key(&expr.name) { - return Err(Error::DuplicateIdentifier(expr.name)); - } - self.devices.insert(expr.name, expr.device); - - Ok(()) - } - - fn expression_binary<'v>( - &mut self, - expr: BinaryExpression, - scope: &mut VariableScope<'v>, - ) -> Result<(), Error> { - Ok(()) - } - - fn expression_block<'v>( - &mut self, - mut expr: BlockExpression, - scope: &mut VariableScope<'v>, - ) -> Result<(), Error> { - // 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(_)) - && !scope.has_parent() - { - self.write_output("main:")?; - self.declared_main = true; - } - - self.expression(expr, scope)?; - } - - Ok(()) - } - - /// Takes the result of the expression and stores it in VariableScope::RETURN_REGISTER - fn expression_return<'v>( - &mut self, - expr: Expression, - scope: &mut VariableScope<'v>, - ) -> Result<(), Error> { - match expr { - Expression::Variable(var_name) => match scope.get_location_of(var_name)? { - VariableLocation::Temporary(reg) | VariableLocation::Persistant(reg) => { - self.write_output(format!( - "move r{} r{reg} {}", - VariableScope::RETURN_REGISTER, - debug!(self, "#returnValue") - ))?; - } - VariableLocation::Stack(offset) => { - self.write_output(format!( - "sub r{} sp {offset}", - VariableScope::TEMP_STACK_REGISTER - ))?; - self.write_output(format!( - "get r{} db r{}", - VariableScope::RETURN_REGISTER, - VariableScope::TEMP_STACK_REGISTER - ))?; - } - }, - Expression::Literal(Literal::Number(num)) => { - self.write_output(format!("move r{} {}", VariableScope::RETURN_REGISTER, num))?; - } - _ => return Err(Error::Unknown("Unsupported `return` statement.".into())), - } - - Ok(()) - } - - /// Compile a function declaration. - /// Calees are responsible for backing up any registers they wish to use. - fn expression_function<'v>( - &mut self, - expr: FunctionExpression, - scope: &mut VariableScope<'v>, - ) -> Result<(), Error> { - let FunctionExpression { - name, - arguments, - body, - } = expr; - - if self.function_locations.contains_key(&name) { - return Err(Error::DuplicateIdentifier(name)); - } - - self.function_metadata - .insert(name.clone(), arguments.clone()); - - // Declare the function as a line identifier - self.write_output(format!("{}:", name))?; - - self.function_locations - .insert(name.clone(), self.current_line); - - // Create a new block scope for the function body - let mut block_scope = VariableScope::scoped(scope); - - let mut saved_variables = 0; - - // do a reverse pass to pop variables from the stack and put them into registers - for var_name in arguments - .iter() - .rev() - .take(VariableScope::PERSIST_REGISTER_COUNT as usize) - { - let loc = block_scope.add_variable(var_name, LocationRequest::Persist)?; - // we don't need to imcrement the stack offset as it's already on the stack from the - // previous scope - - match loc { - VariableLocation::Persistant(loc) => { - self.write_output(format!("pop r{loc} {}", debug!(self, "#{var_name}")))?; - } - VariableLocation::Stack(_) => { - return Err(Error::Unknown( - "Attempted to save to stack without tracking in scope".into(), - )); - } - - _ => { - return Err(Error::Unknown( - "Attempted to return a Temporary scoped variable from a Persistant request" - .into(), - )); - } - } - saved_variables += 1; - } - - // now do a forward pass in case we have spilled into the stack. We don't need to push - // anything as they already exist on the stack, but we DO need to let our block_scope be - // aware that the variables exist on the stack (left to right) - for var_name in arguments.iter().take(arguments.len() - saved_variables) { - block_scope.add_variable(var_name, LocationRequest::Stack)?; - } - - self.write_output("push ra")?; - block_scope.add_variable(format!("{name}_ra"), LocationRequest::Stack)?; - - for expr in body.0 { - match expr { - Expression::Return(ret_expr) => { - self.expression_return(*ret_expr, &mut block_scope)? - } - _ => self.expression(expr, &mut block_scope)?, - } - } - - // Get the saved return address and save it back into `ra` - let VariableLocation::Stack(ra_stack_offset) = - block_scope.get_location_of(format!("{name}_ra"))? - else { - return Err(Error::Unknown( - "Stored return address not in stack as expected".into(), - )); - }; - - self.write_output(format!( - "sub r{0} sp {ra_stack_offset}", - VariableScope::TEMP_STACK_REGISTER - ))?; - self.write_output(format!( - "get ra db r{0}", - VariableScope::TEMP_STACK_REGISTER - ))?; - - if block_scope.stack_offset() > 0 { - self.write_output(format!("sub sp sp {}", block_scope.stack_offset()))?; - } - - self.write_output("j ra")?; - Ok(()) - } -} From c3adcf57f5300d746bd61dcd227c3bd1c65e092d Mon Sep 17 00:00:00 2001 From: Devin Bidwell Date: Sat, 22 Nov 2025 23:02:49 -0700 Subject: [PATCH 13/22] More support for negative numbers --- .../test/declaration_function_invocation.rs | 36 ++++++++++++++++++- libs/compiler/src/v2.rs | 17 ++++++++- 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/libs/compiler/src/test/declaration_function_invocation.rs b/libs/compiler/src/test/declaration_function_invocation.rs index 8be25cb..788bb5f 100644 --- a/libs/compiler/src/test/declaration_function_invocation.rs +++ b/libs/compiler/src/test/declaration_function_invocation.rs @@ -192,7 +192,7 @@ fn with_return_statement() -> anyhow::Result<()> { doSomething: pop r8 #arg1 push ra - move r15 456 + move r15 456 #returnValue sub r0 sp 1 get ra db r0 sub sp sp 1 @@ -207,3 +207,37 @@ fn with_return_statement() -> anyhow::Result<()> { Ok(()) } + +#[test] +fn with_negative_return_literal() -> anyhow::Result<()> { + let compiled = compile! { + debug + " + fn doSomething() { + return -1; + }; + let i = doSomething(); + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + doSomething: + push ra + move r15 -1 #returnValue + sub r0 sp 1 + get ra db r0 + sub sp sp 1 + j ra + main: + jal doSomething + move r8 r15 #i + " + } + ); + + Ok(()) +} diff --git a/libs/compiler/src/v2.rs b/libs/compiler/src/v2.rs index 979319f..c19c203 100644 --- a/libs/compiler/src/v2.rs +++ b/libs/compiler/src/v2.rs @@ -327,6 +327,17 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { expr: Expression, scope: &mut VariableScope<'v>, ) -> Result<(), Error> { + if let Expression::Negation(neg_expr) = &expr + && let Expression::Literal(Literal::Number(neg_num)) = &**neg_expr + { + self.emit_variable_assignment( + "returnValue", + VariableLocation::Persistant(VariableScope::RETURN_REGISTER), + format!("-{neg_num}"), + )?; + return Ok(()); + }; + match expr { Expression::Variable(var_name) => match scope.get_location_of(var_name)? { VariableLocation::Temporary(reg) | VariableLocation::Persistant(reg) => { @@ -349,7 +360,11 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { } }, Expression::Literal(Literal::Number(num)) => { - self.write_output(format!("move r{} {}", VariableScope::RETURN_REGISTER, num))?; + self.emit_variable_assignment( + "returnValue", + VariableLocation::Persistant(VariableScope::RETURN_REGISTER), + num, + )?; } _ => return Err(Error::Unknown("Unsupported `return` statement.".into())), } From 92c1047ef843a783adfc91aa287ac6d8c1c372dc Mon Sep 17 00:00:00 2001 From: Devin Bidwell Date: Sun, 23 Nov 2025 00:10:48 -0700 Subject: [PATCH 14/22] Various parser fixes --- libs/parser/src/lib.rs | 11 ++++++++--- libs/parser/src/sys_call.rs | 16 ++++++++++++++-- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/libs/parser/src/lib.rs b/libs/parser/src/lib.rs index f3a356e..63b5b32 100644 --- a/libs/parser/src/lib.rs +++ b/libs/parser/src/lib.rs @@ -731,7 +731,10 @@ impl Parser { match invocation.name.as_str() { // system calls - "yield" => Ok(SysCall::System(sys_call::System::Yield)), + "yield" => { + check_length(self, &invocation.arguments, 0)?; + Ok(SysCall::System(sys_call::System::Yield)) + } "sleep" => { check_length(self, &invocation.arguments, 1)?; let mut arg = invocation.arguments.iter(); @@ -752,7 +755,7 @@ impl Parser { Ok(SysCall::System(sys_call::System::LoadFromDevice( device, - variable.clone(), + LiteralOrVariable::Variable(variable.clone()), ))) } "setOnDevice" => { @@ -772,7 +775,9 @@ impl Parser { let variable = literal_or_variable!(args.next()); Ok(SysCall::System(sys_call::System::SetOnDevice( - device, logic_type, variable, + device, + Literal::String(logic_type), + variable, ))) } // math calls diff --git a/libs/parser/src/sys_call.rs b/libs/parser/src/sys_call.rs index eec5ade..38a40ae 100644 --- a/libs/parser/src/sys_call.rs +++ b/libs/parser/src/sys_call.rs @@ -1,3 +1,5 @@ +use crate::tree_node::Literal; + use super::LiteralOrVariable; #[derive(Debug, PartialEq, Eq)] @@ -111,13 +113,20 @@ pub enum System { /// ## Examples /// `l r0 d0 Setting` /// `l r1 d5 Pressure` - LoadFromDevice(LiteralOrVariable, String), + LoadFromDevice(LiteralOrVariable, LiteralOrVariable), + /// Function which gets a LogicType from all connected network devices that match + /// the provided device hash and name, aggregating them via a batchMode + /// ## In Game + /// lbn r? deviceHash nameHash logicType batchMode + /// ## Examples + /// lbn r0 HASH("StructureWallLight") HASH("wallLight") On Minimum + LoadBatchNamed(LiteralOrVariable, Literal, Literal, Literal), /// Represents a function which stores a setting into a specific device. /// ## In Game /// `s d? logicType r?` /// ## Example /// `s d0 Setting r0` - SetOnDevice(LiteralOrVariable, String, LiteralOrVariable), + SetOnDevice(LiteralOrVariable, Literal, LiteralOrVariable), } impl std::fmt::Display for System { @@ -127,6 +136,9 @@ impl std::fmt::Display for System { System::Sleep(a) => write!(f, "sleep({})", a), System::Hash(a) => write!(f, "HASH({})", a), System::LoadFromDevice(a, b) => write!(f, "loadFromDevice({}, {})", a, b), + System::LoadBatchNamed(a, b, c, d) => { + write!(f, "loadBatchNamed({}, {}, {}, {})", a, b, c, d) + } System::SetOnDevice(a, b, c) => write!(f, "setOnDevice({}, {}, {})", a, b, c), } } From b3c64a8e17fd635bdd9b5eb2c6490e65e2d5803f Mon Sep 17 00:00:00 2001 From: Devin Bidwell Date: Sun, 23 Nov 2025 00:24:15 -0700 Subject: [PATCH 15/22] Added loadBatch and loadBatchNamed to the parser --- libs/parser/src/lib.rs | 30 ++++++++++++++++++++++++++++++ libs/parser/src/sys_call.rs | 8 ++++++++ 2 files changed, 38 insertions(+) diff --git a/libs/parser/src/lib.rs b/libs/parser/src/lib.rs index 63b5b32..06fbecd 100644 --- a/libs/parser/src/lib.rs +++ b/libs/parser/src/lib.rs @@ -758,6 +758,36 @@ impl Parser { LiteralOrVariable::Variable(variable.clone()), ))) } + "loadBatch" => { + check_length(self, &invocation.arguments, 3)?; + let mut args = invocation.arguments.iter(); + + let device_hash = literal_or_variable!(args.next()); + let logic_type = get_arg!(Literal, literal_or_variable!(args.next())); + let batch_mode = get_arg!(Literal, literal_or_variable!(args.next())); + + Ok(SysCall::System(sys_call::System::LoadBatch( + device_hash, + logic_type, + batch_mode, + ))) + } + "loadBatchNamed" => { + check_length(self, &invocation.arguments, 4)?; + let mut args = invocation.arguments.iter(); + + let device_hash = literal_or_variable!(args.next()); + let name_hash = get_arg!(Literal, literal_or_variable!(args.next())); + let logic_type = get_arg!(Literal, literal_or_variable!(args.next())); + let batch_mode = get_arg!(Literal, literal_or_variable!(args.next())); + + Ok(SysCall::System(sys_call::System::LoadBatchNamed( + device_hash, + name_hash, + logic_type, + batch_mode, + ))) + } "setOnDevice" => { check_length(self, &invocation.arguments, 3)?; let mut args = invocation.arguments.iter(); diff --git a/libs/parser/src/sys_call.rs b/libs/parser/src/sys_call.rs index 38a40ae..cb306ca 100644 --- a/libs/parser/src/sys_call.rs +++ b/libs/parser/src/sys_call.rs @@ -121,6 +121,13 @@ pub enum System { /// ## Examples /// lbn r0 HASH("StructureWallLight") HASH("wallLight") On Minimum LoadBatchNamed(LiteralOrVariable, Literal, Literal, Literal), + /// Loads a LogicType from all connected network devices, aggregating them via a + /// batchMode + /// ## In Game + /// lb r? deviceHash loggicType batchMode + /// ## Examples + /// lb r0 HASH("StructureWallLight") On Minimum + LoadBatch(LiteralOrVariable, Literal, Literal), /// Represents a function which stores a setting into a specific device. /// ## In Game /// `s d? logicType r?` @@ -136,6 +143,7 @@ impl std::fmt::Display for System { System::Sleep(a) => write!(f, "sleep({})", a), System::Hash(a) => write!(f, "HASH({})", a), System::LoadFromDevice(a, b) => write!(f, "loadFromDevice({}, {})", a, b), + System::LoadBatch(a, b, c) => write!(f, "loadBatch({}, {}, {})", a, b, c), System::LoadBatchNamed(a, b, c, d) => { write!(f, "loadBatchNamed({}, {}, {}, {})", a, b, c, d) } From ad9178bc313e2ab68a71c030202f368ce85fdea1 Mon Sep 17 00:00:00 2001 From: Devin Bidwell Date: Mon, 24 Nov 2025 00:37:35 -0700 Subject: [PATCH 16/22] Change return values of compiler functions to return optional variableLocations --- libs/compiler/src/v2.rs | 81 ++++++++++++++++++++++++++--------------- 1 file changed, 51 insertions(+), 30 deletions(-) diff --git a/libs/compiler/src/v2.rs b/libs/compiler/src/v2.rs index c19c203..de566ee 100644 --- a/libs/compiler/src/v2.rs +++ b/libs/compiler/src/v2.rs @@ -93,13 +93,15 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { let Some(expr) = expr else { return Ok(()) }; self.write_output("j main")?; - self.expression(expr, &mut VariableScope::default()) + self.expression(expr, &mut VariableScope::default())?; + Ok(()) } fn write_output(&mut self, output: impl Into) -> Result<(), Error> { self.output.write_all(output.into().as_bytes())?; self.output.write_all(b"\n")?; self.current_line += 1; + Ok(()) } @@ -107,27 +109,37 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { &mut self, expr: Expression, scope: &mut VariableScope<'v>, - ) -> Result<(), Error> { - match expr { - Expression::Function(expr_func) => self.expression_function(expr_func, scope)?, - Expression::Block(expr_block) => self.expression_block(expr_block, scope)?, - Expression::DeviceDeclaration(expr_dev) => self.expression_device(expr_dev)?, + ) -> Result, Error> { + let loc = match expr { + Expression::Function(expr_func) => { + self.expression_function(expr_func, scope)?; + None + } + Expression::Block(expr_block) => { + self.expression_block(expr_block, scope)?; + None + } + Expression::DeviceDeclaration(expr_dev) => { + self.expression_device(expr_dev)?; + None + } Expression::Declaration(var_name, expr) => { self.expression_declaration(var_name, *expr, scope)? } Expression::Invocation(expr_invoke) => { - self.expression_function_invocation(expr_invoke, scope)? + self.expression_function_invocation(expr_invoke, scope)?; + None } _ => todo!(), }; - Ok(()) + Ok(loc) } fn emit_variable_assignment( &mut self, var_name: &str, - location: VariableLocation, + location: &VariableLocation, source_value: impl Into, ) -> Result<(), Error> { let debug_tag = if self.config.debug { @@ -153,22 +165,23 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { var_name: String, expr: Expression, scope: &mut VariableScope<'v>, - ) -> Result<(), Error> { + ) -> Result, Error> { // optimization. Check for a negated numeric literal if let Expression::Negation(box_expr) = &expr && let Expression::Literal(Literal::Number(neg_num)) = &**box_expr { let loc = scope.add_variable(&var_name, LocationRequest::Persist)?; - self.emit_variable_assignment(&var_name, loc, format!("-{neg_num}"))?; - return Ok(()); + self.emit_variable_assignment(&var_name, &loc, format!("-{neg_num}"))?; + return Ok(Some(loc)); } - match expr { + let loc = match expr { Expression::Literal(Literal::Number(num)) => { let var_location = scope.add_variable(var_name.clone(), LocationRequest::Persist)?; - self.emit_variable_assignment(&var_name, var_location, num)?; + self.emit_variable_assignment(&var_name, &var_location, num)?; + var_location } Expression::Invocation(invoke_expr) => { self.expression_function_invocation(invoke_expr, scope)?; @@ -176,18 +189,19 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { let loc = scope.add_variable(&var_name, LocationRequest::Persist)?; self.emit_variable_assignment( &var_name, - loc, + &loc, format!("r{}", VariableScope::RETURN_REGISTER), )?; + loc } _ => { return Err(Error::Unknown( "`{var_name}` declaration of this type is not supported.".into(), )); } - } + }; - Ok(()) + Ok(Some(loc)) } fn expression_function_invocation( @@ -286,8 +300,16 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { &mut self, expr: BinaryExpression, scope: &mut VariableScope<'v>, - ) -> Result<(), Error> { - Ok(()) + ) -> Result { + let (op, l, r) = match expr { + BinaryExpression::Add(l, r) => ("add", *l, *r), + BinaryExpression::Multiply(l, r) => ("mul", *l, *r), + BinaryExpression::Divide(l, r) => ("div", *l, *r), + BinaryExpression::Subtract(l, r) => ("sub", *l, *r), + BinaryExpression::Exponent(l, r) => ("pow", *l, *r), + }; + + todo!() } fn expression_block<'v>( @@ -326,16 +348,13 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { &mut self, expr: Expression, scope: &mut VariableScope<'v>, - ) -> Result<(), Error> { + ) -> Result { if let Expression::Negation(neg_expr) = &expr && let Expression::Literal(Literal::Number(neg_num)) = &**neg_expr { - self.emit_variable_assignment( - "returnValue", - VariableLocation::Persistant(VariableScope::RETURN_REGISTER), - format!("-{neg_num}"), - )?; - return Ok(()); + let loc = VariableLocation::Persistant(VariableScope::RETURN_REGISTER); + self.emit_variable_assignment("returnValue", &loc, format!("-{neg_num}"))?; + return Ok(loc); }; match expr { @@ -362,14 +381,14 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { Expression::Literal(Literal::Number(num)) => { self.emit_variable_assignment( "returnValue", - VariableLocation::Persistant(VariableScope::RETURN_REGISTER), + &VariableLocation::Persistant(VariableScope::RETURN_REGISTER), num, )?; } _ => return Err(Error::Unknown("Unsupported `return` statement.".into())), } - Ok(()) + Ok(VariableLocation::Persistant(VariableScope::RETURN_REGISTER)) } /// Compile a function declaration. @@ -446,9 +465,11 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { for expr in body.0 { match expr { Expression::Return(ret_expr) => { - self.expression_return(*ret_expr, &mut block_scope)? + self.expression_return(*ret_expr, &mut block_scope)?; + } + _ => { + self.expression(expr, &mut block_scope)?; } - _ => self.expression(expr, &mut block_scope)?, } } From c019b093d2ea90c261aff5699fa16e56aa63ec0b Mon Sep 17 00:00:00 2001 From: Devin Bidwell Date: Mon, 24 Nov 2025 16:52:59 -0700 Subject: [PATCH 17/22] wip --- libs/compiler/src/v2.rs | 49 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/libs/compiler/src/v2.rs b/libs/compiler/src/v2.rs index de566ee..16c7502 100644 --- a/libs/compiler/src/v2.rs +++ b/libs/compiler/src/v2.rs @@ -301,6 +301,45 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { expr: BinaryExpression, scope: &mut VariableScope<'v>, ) -> Result { + enum VariableType { + Location(VariableLocation), + Literal(String), + } + let mut comp_bin_expr = move |expr: Expression| -> Result { + if let Expression::Negation(box_expr) = &expr + && let Expression::Literal(Literal::Number(num)) = &**box_expr + { + return Ok(VariableType::Literal(format!("-{num}"))); + } + + let var_type = match expr { + Expression::Binary(bin) => { + VariableType::Location(self.expression_binary(bin, scope)?) + } + Expression::Variable(var_name) => { + VariableType::Location(scope.get_location_of(var_name)?) + } + Expression::Invocation(invocation_expression) => { + let temp_ret = scope.add_variable("temp_ret", LocationRequest::Temp)?; + self.expression_function_invocation(invocation_expression, scope)?; + self.emit_variable_assignment( + "temp_ret", + &temp_ret, + format!("r{}", VariableScope::RETURN_REGISTER), + )?; + VariableType::Location(temp_ret) + } + Expression::Literal(Literal::Number(num)) => VariableType::Literal(num.into()), + _ => { + return Err(Error::Unknown( + "Unsupported expression in binary expression.".into(), + )); + } + }; + + Ok(var_type) + }; + let (op, l, r) = match expr { BinaryExpression::Add(l, r) => ("add", *l, *r), BinaryExpression::Multiply(l, r) => ("mul", *l, *r), @@ -309,6 +348,16 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { BinaryExpression::Exponent(l, r) => ("pow", *l, *r), }; + let mut l = comp_bin_expr(l)?; + let mut r = comp_bin_expr(r)?; + + // make sure l and r are in registers. If they aren't, backup 2 temp registers to the stack + // and + + if let VariableType::Location(VariableLocation::Stack(offset)) = l {} + + if let VariableType::Location(VariableLocation::Stack(offset)) = r {} + todo!() } From ae8199fa8cfb1b35d22253f479aafb84554b222b Mon Sep 17 00:00:00 2001 From: Devin Bidwell Date: Mon, 24 Nov 2025 22:03:50 -0700 Subject: [PATCH 18/22] Somewhat working binary expressions --- libs/compiler/src/test/binary_expression.rs | 92 ++++++ libs/compiler/src/test/mod.rs | 1 + libs/compiler/src/v2.rs | 349 +++++++++++++++----- libs/parser/src/lib.rs | 5 + 4 files changed, 372 insertions(+), 75 deletions(-) create mode 100644 libs/compiler/src/test/binary_expression.rs diff --git a/libs/compiler/src/test/binary_expression.rs b/libs/compiler/src/test/binary_expression.rs new file mode 100644 index 0000000..68ccba6 --- /dev/null +++ b/libs/compiler/src/test/binary_expression.rs @@ -0,0 +1,92 @@ +use crate::compile; +use indoc::indoc; +use pretty_assertions::assert_eq; + +#[test] +fn simple_binary_expression() -> anyhow::Result<()> { + let compiled = compile! { + debug + " + let i = 1 + 2; + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + main: + add r1 1 2 + move r8 r1 #i + " + } + ); + + Ok(()) +} + +#[test] +fn nested_binary_expressions() -> anyhow::Result<()> { + let compiled = compile! { + debug + " + fn calculateArgs(arg1, arg2, arg3) { + return (arg1 + arg2) * arg3; + }; + + let returned = calculateArgs(10, 20, 30) + 100; + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + calculateArgs: + pop r8 #arg3 + pop r9 #arg2 + pop r10 #arg1 + push ra + add r1 r10 r9 + mul r2 r1 r8 + move r15 r2 + sub r0 sp 1 + get ra db r0 + sub sp sp 1 + j ra + main: + push 10 + push 20 + push 30 + jal calculateArgs + move r1 r15 #__binary_temp_3 + add r2 r1 100 + move r8 r2 #returned + " + } + ); + + Ok(()) +} + +#[test] +fn stress_test_negation_with_stack_spillover() -> anyhow::Result<()> { + let compiled = compile! { + debug + " + let negationHell = (-1 + -2) * (-3 + (-4 * (-5 + -6))); + " + }; + + assert_eq!( + compiled, + indoc! { + " + " + } + ); + + Ok(()) +} diff --git a/libs/compiler/src/test/mod.rs b/libs/compiler/src/test/mod.rs index 20a9f64..09fa1aa 100644 --- a/libs/compiler/src/test/mod.rs +++ b/libs/compiler/src/test/mod.rs @@ -40,6 +40,7 @@ macro_rules! compile { output!(writer) }}; } +mod binary_expression; mod declaration_function_invocation; mod declaration_literal; mod function_declaration; diff --git a/libs/compiler/src/v2.rs b/libs/compiler/src/v2.rs index 16c7502..bd16f8c 100644 --- a/libs/compiler/src/v2.rs +++ b/libs/compiler/src/v2.rs @@ -58,6 +58,13 @@ pub struct CompilerConfig { pub debug: bool, } +struct CompilationResult { + location: VariableLocation, + /// If Some, this is the name of the temporary variable that holds the result. + /// It must be freed by the caller when done. + temp_name: Option, +} + pub struct Compiler<'a, W: std::io::Write> { parser: ASTParser, function_locations: HashMap, @@ -67,6 +74,7 @@ pub struct Compiler<'a, W: std::io::Write> { current_line: usize, declared_main: bool, config: CompilerConfig, + temp_counter: usize, } impl<'a, W: std::io::Write> Compiler<'a, W> { @@ -84,6 +92,7 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { current_line: 1, declared_main: false, config: config.unwrap_or_default(), + temp_counter: 0, } } @@ -93,7 +102,8 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { let Some(expr) = expr else { return Ok(()) }; self.write_output("j main")?; - self.expression(expr, &mut VariableScope::default())?; + // We ignore the result of the root expression (usually a block) + let _ = self.expression(expr, &mut VariableScope::default())?; Ok(()) } @@ -105,35 +115,96 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { Ok(()) } + fn next_temp_name(&mut self) -> String { + self.temp_counter += 1; + format!("__binary_temp_{}", self.temp_counter) + } + fn expression<'v>( &mut self, expr: Expression, scope: &mut VariableScope<'v>, - ) -> Result, Error> { - let loc = match expr { + ) -> Result, Error> { + match expr { Expression::Function(expr_func) => { self.expression_function(expr_func, scope)?; - None + Ok(None) } Expression::Block(expr_block) => { self.expression_block(expr_block, scope)?; - None + Ok(None) } Expression::DeviceDeclaration(expr_dev) => { self.expression_device(expr_dev)?; - None + Ok(None) } Expression::Declaration(var_name, expr) => { - self.expression_declaration(var_name, *expr, scope)? + let loc = self.expression_declaration(var_name, *expr, scope)?; + Ok(loc.map(|l| CompilationResult { + location: l, + temp_name: None, + })) } Expression::Invocation(expr_invoke) => { self.expression_function_invocation(expr_invoke, scope)?; - None + // Invocation returns result in r15 (RETURN_REGISTER). + // If used as an expression, we must move it to a temp to avoid overwrite. + let temp_name = self.next_temp_name(); + let temp_loc = scope.add_variable(&temp_name, LocationRequest::Temp)?; + self.emit_variable_assignment( + &temp_name, + &temp_loc, + format!("r{}", VariableScope::RETURN_REGISTER), + )?; + Ok(Some(CompilationResult { + location: temp_loc, + temp_name: Some(temp_name), + })) } - _ => todo!(), - }; + Expression::Binary(bin_expr) => { + let result = self.expression_binary(bin_expr, scope)?; + Ok(Some(result)) + } + Expression::Literal(Literal::Number(num)) => { + let temp_name = self.next_temp_name(); + let loc = scope.add_variable(&temp_name, LocationRequest::Temp)?; + self.emit_variable_assignment(&temp_name, &loc, num.to_string())?; + Ok(Some(CompilationResult { + location: loc, + temp_name: Some(temp_name), + })) + } + Expression::Variable(name) => { + let loc = scope.get_location_of(&name)?; + Ok(Some(CompilationResult { + location: loc, + temp_name: None, // User variable, do not free + })) + } + Expression::Priority(inner_expr) => self.expression(*inner_expr, scope), + Expression::Negation(inner_expr) => { + // Compile negation as 0 - inner + let (inner_str, cleanup) = self.compile_operand(*inner_expr, scope)?; + let result_name = self.next_temp_name(); + let result_loc = scope.add_variable(&result_name, LocationRequest::Temp)?; + let result_reg = self.resolve_register(&result_loc)?; - Ok(loc) + self.write_output(format!("sub {result_reg} 0 {inner_str}"))?; + + if let Some(name) = cleanup { + scope.free_temp(name)?; + } + + Ok(Some(CompilationResult { + location: result_loc, + temp_name: Some(result_name), + })) + } + _ => Err(Error::Unknown(format!( + "Expression type not yet supported in general expression context: {:?}", + expr + ))), + } } fn emit_variable_assignment( @@ -194,10 +265,52 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { )?; loc } + // Support assigning binary expressions to variables directly + Expression::Binary(bin_expr) => { + let result = self.expression_binary(bin_expr, scope)?; + let var_loc = scope.add_variable(&var_name, LocationRequest::Persist)?; + + // Move result from temp to new persistent variable + let result_reg = self.resolve_register(&result.location)?; + self.emit_variable_assignment(&var_name, &var_loc, result_reg)?; + + // Free the temp result + if let Some(name) = result.temp_name { + scope.free_temp(name)?; + } + var_loc + } + Expression::Variable(name) => { + let src_loc = scope.get_location_of(&name)?; + let var_loc = scope.add_variable(&var_name, LocationRequest::Persist)?; + + // Handle loading from stack if necessary + let src_str = match src_loc { + VariableLocation::Temporary(r) | VariableLocation::Persistant(r) => { + format!("r{r}") + } + VariableLocation::Stack(offset) => { + self.write_output(format!( + "sub r{0} sp {offset}", + VariableScope::TEMP_STACK_REGISTER + ))?; + self.write_output(format!( + "get r{0} db r{0}", + VariableScope::TEMP_STACK_REGISTER + ))?; + format!("r{}", VariableScope::TEMP_STACK_REGISTER) + } + }; + self.emit_variable_assignment(&var_name, &var_loc, src_str)?; + var_loc + } + Expression::Priority(inner) => { + return self.expression_declaration(var_name, *inner, scope); + } _ => { - return Err(Error::Unknown( - "`{var_name}` declaration of this type is not supported.".into(), - )); + return Err(Error::Unknown(format!( + "`{var_name}` declaration of this type is not supported/implemented." + ))); } }; @@ -252,9 +365,18 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { ))?; } }, + Expression::Binary(bin_expr) => { + // Compile the binary expression to a temp register + let result = self.expression_binary(bin_expr, stack)?; + let reg_str = self.resolve_register(&result.location)?; + self.write_output(format!("push {reg_str}"))?; + if let Some(name) = result.temp_name { + stack.free_temp(name)?; + } + } _ => { return Err(Error::Unknown(format!( - "Attempted to call `{}` with an unsupported argument", + "Attempted to call `{}` with an unsupported argument type", invoke_expr.name ))); } @@ -296,69 +418,109 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { Ok(()) } + /// Helper to resolve a location to a register string (e.g., "r0"). + /// Note: This does not handle Stack locations automatically, as they require + /// instruction emission to load. Use `compile_operand` for general handling. + fn resolve_register(&self, loc: &VariableLocation) -> Result { + match loc { + VariableLocation::Temporary(r) | VariableLocation::Persistant(r) => Ok(format!("r{r}")), + VariableLocation::Stack(_) => Err(Error::Unknown( + "Cannot resolve Stack location directly to register string without context".into(), + )), + } + } + + /// Compiles an expression and ensures the result is available as a string valid for an + /// IC10 operand (either a register "rX" or a literal value "123"). + /// If the result was stored in a new temporary register, returns the name of that temp + /// so the caller can free it. + fn compile_operand( + &mut self, + expr: Expression, + scope: &mut VariableScope, + ) -> Result<(String, Option), Error> { + // Optimization for literals + if let Expression::Literal(Literal::Number(n)) = expr { + return Ok((n.to_string(), None)); + } + + // Optimization for negated literals used as operands. + // E.g., `1 + -2` -> return "-2" string, no register used. + if let Expression::Negation(inner) = &expr + && let Expression::Literal(Literal::Number(n)) = &**inner + { + return Ok((format!("-{}", n), None)); + } + + let result = self + .expression(expr, scope)? + .ok_or(Error::Unknown("Expression did not return a value".into()))?; + + match result.location { + VariableLocation::Temporary(r) | VariableLocation::Persistant(r) => { + Ok((format!("r{r}"), result.temp_name)) + } + VariableLocation::Stack(offset) => { + // If it's on the stack, we must load it into a temp to use it as an operand + let temp_name = self.next_temp_name(); + let temp_loc = scope.add_variable(&temp_name, LocationRequest::Temp)?; + let temp_reg = self.resolve_register(&temp_loc)?; + + self.write_output(format!( + "sub r{0} sp {offset}", + VariableScope::TEMP_STACK_REGISTER + ))?; + self.write_output(format!( + "get {temp_reg} db r{0}", + VariableScope::TEMP_STACK_REGISTER + ))?; + + // If the original result had a temp name (unlikely for Stack, but possible logic), + // we technically should free it if it's not needed, but Stack usually implies it's safe there. + // We return the NEW temp name to be freed. + Ok((temp_reg, Some(temp_name))) + } + } + } + fn expression_binary<'v>( &mut self, expr: BinaryExpression, scope: &mut VariableScope<'v>, - ) -> Result { - enum VariableType { - Location(VariableLocation), - Literal(String), + ) -> Result { + let (op_str, left_expr, right_expr) = match expr { + BinaryExpression::Add(l, r) => ("add", l, r), + BinaryExpression::Multiply(l, r) => ("mul", l, r), + BinaryExpression::Divide(l, r) => ("div", l, r), + BinaryExpression::Subtract(l, r) => ("sub", l, r), + BinaryExpression::Exponent(l, r) => ("pow", l, r), + }; + + // Compile LHS + let (lhs_str, lhs_cleanup) = self.compile_operand(*left_expr, scope)?; + // Compile RHS + let (rhs_str, rhs_cleanup) = self.compile_operand(*right_expr, scope)?; + + // Allocate result register + let result_name = self.next_temp_name(); + let result_loc = scope.add_variable(&result_name, LocationRequest::Temp)?; + let result_reg = self.resolve_register(&result_loc)?; + + // Emit instruction: op result lhs rhs + self.write_output(format!("{op_str} {result_reg} {lhs_str} {rhs_str}"))?; + + // Clean up operand temps + if let Some(name) = lhs_cleanup { + scope.free_temp(name)?; + } + if let Some(name) = rhs_cleanup { + scope.free_temp(name)?; } - let mut comp_bin_expr = move |expr: Expression| -> Result { - if let Expression::Negation(box_expr) = &expr - && let Expression::Literal(Literal::Number(num)) = &**box_expr - { - return Ok(VariableType::Literal(format!("-{num}"))); - } - let var_type = match expr { - Expression::Binary(bin) => { - VariableType::Location(self.expression_binary(bin, scope)?) - } - Expression::Variable(var_name) => { - VariableType::Location(scope.get_location_of(var_name)?) - } - Expression::Invocation(invocation_expression) => { - let temp_ret = scope.add_variable("temp_ret", LocationRequest::Temp)?; - self.expression_function_invocation(invocation_expression, scope)?; - self.emit_variable_assignment( - "temp_ret", - &temp_ret, - format!("r{}", VariableScope::RETURN_REGISTER), - )?; - VariableType::Location(temp_ret) - } - Expression::Literal(Literal::Number(num)) => VariableType::Literal(num.into()), - _ => { - return Err(Error::Unknown( - "Unsupported expression in binary expression.".into(), - )); - } - }; - - Ok(var_type) - }; - - let (op, l, r) = match expr { - BinaryExpression::Add(l, r) => ("add", *l, *r), - BinaryExpression::Multiply(l, r) => ("mul", *l, *r), - BinaryExpression::Divide(l, r) => ("div", *l, *r), - BinaryExpression::Subtract(l, r) => ("sub", *l, *r), - BinaryExpression::Exponent(l, r) => ("pow", *l, *r), - }; - - let mut l = comp_bin_expr(l)?; - let mut r = comp_bin_expr(r)?; - - // make sure l and r are in registers. If they aren't, backup 2 temp registers to the stack - // and - - if let VariableType::Location(VariableLocation::Stack(offset)) = l {} - - if let VariableType::Location(VariableLocation::Stack(offset)) = r {} - - todo!() + Ok(CompilationResult { + location: result_loc, + temp_name: Some(result_name), + }) } fn expression_block<'v>( @@ -386,7 +548,21 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { self.declared_main = true; } - self.expression(expr, scope)?; + match expr { + Expression::Return(ret_expr) => { + self.expression_return(*ret_expr, scope)?; + } + _ => { + let result = self.expression(expr, scope)?; + // If the expression was a statement that returned a temp result (e.g. `1 + 2;` line), + // we must free it to avoid leaking registers. + if let Some(comp_res) = result + && let Some(name) = comp_res.temp_name + { + scope.free_temp(name)?; + } + } + } } Ok(()) @@ -434,7 +610,24 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { num, )?; } - _ => return Err(Error::Unknown("Unsupported `return` statement.".into())), + Expression::Binary(bin_expr) => { + let result = self.expression_binary(bin_expr, scope)?; + let result_reg = self.resolve_register(&result.location)?; + self.write_output(format!( + "move r{} {}", + VariableScope::RETURN_REGISTER, + result_reg + ))?; + if let Some(name) = result.temp_name { + scope.free_temp(name)?; + } + } + _ => { + return Err(Error::Unknown(format!( + "Unsupported `return` statement: {:?}", + expr + ))); + } } Ok(VariableLocation::Persistant(VariableScope::RETURN_REGISTER)) @@ -517,7 +710,13 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { self.expression_return(*ret_expr, &mut block_scope)?; } _ => { - self.expression(expr, &mut block_scope)?; + let result = self.expression(expr, &mut block_scope)?; + // Free unused statement results + if let Some(comp_res) = result + && let Some(name) = comp_res.temp_name + { + block_scope.free_temp(name)?; + } } } } diff --git a/libs/parser/src/lib.rs b/libs/parser/src/lib.rs index 06fbecd..af35d12 100644 --- a/libs/parser/src/lib.rs +++ b/libs/parser/src/lib.rs @@ -259,6 +259,11 @@ impl Parser { } // A priority expression ( -> (1 + 2) <- + 3 ) TokenType::Symbol(Symbol::LParen) => self.priority().map(Expression::Priority), + TokenType::Symbol(Symbol::Minus) => { + self.assign_next()?; + let inner = self.get_binary_child_node()?; + Ok(Expression::Negation(boxed!(inner))) + } // A function invocation TokenType::Identifier(_) if self_matches_peek!(self, TokenType::Symbol(Symbol::LParen)) => From 56f0e292b76bd8008a7924a1785da3e4c86bc4dd Mon Sep 17 00:00:00 2001 From: Devin Bidwell Date: Mon, 24 Nov 2025 22:13:49 -0700 Subject: [PATCH 19/22] wip -- still working on binary expressions --- libs/compiler/src/test/binary_expression.rs | 8 +++ libs/compiler/src/v2.rs | 1 + libs/parser/src/lib.rs | 60 +++++++++++++-------- 3 files changed, 47 insertions(+), 22 deletions(-) diff --git a/libs/compiler/src/test/binary_expression.rs b/libs/compiler/src/test/binary_expression.rs index 68ccba6..8f890d4 100644 --- a/libs/compiler/src/test/binary_expression.rs +++ b/libs/compiler/src/test/binary_expression.rs @@ -84,6 +84,14 @@ fn stress_test_negation_with_stack_spillover() -> anyhow::Result<()> { compiled, indoc! { " + j main + main: + add r1 -1 -2 + add r2 -5 -6 + mul r3 -4 r2 + add r4 -3 r3 + mul r5 r1 r4 + move r8 r5 #negationHell " } ); diff --git a/libs/compiler/src/v2.rs b/libs/compiler/src/v2.rs index bd16f8c..a0e0098 100644 --- a/libs/compiler/src/v2.rs +++ b/libs/compiler/src/v2.rs @@ -747,3 +747,4 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { Ok(()) } } + diff --git a/libs/parser/src/lib.rs b/libs/parser/src/lib.rs index af35d12..4decc64 100644 --- a/libs/parser/src/lib.rs +++ b/libs/parser/src/lib.rs @@ -152,7 +152,33 @@ impl Parser { Ok(self.current_token.as_ref()) } + /// Parses an expression, handling binary operations with correct precedence. fn expression(&mut self) -> Result, Error> { + // Parse the Left Hand Side (unary/primary expression) + let lhs = self.unary()?; + + let Some(lhs) = lhs else { + return Ok(None); + }; + + // check if the next or current token is an operator + if self_matches_peek!(self, TokenType::Symbol(s) if s.is_operator()) { + return Ok(Some(Expression::Binary(self.binary(lhs)?))); + } + // This is an edge case. We need to move back one token if the current token is an operator + // so the binary expression can pick up the operator + else if self_matches_current!(self, TokenType::Symbol(s) if s.is_operator()) { + self.tokenizer.seek(SeekFrom::Current(-1))?; + return Ok(Some(Expression::Binary(self.binary(lhs)?))); + } + + Ok(Some(lhs)) + } + + /// Parses a unary or primary expression. + /// This handles prefix operators (like negation) and atomic expressions (literals, variables, etc.), + /// but stops before consuming binary operators. + fn unary(&mut self) -> Result, Error> { macro_rules! matches_keyword { ($keyword:expr, $($pattern:pat),+) => { matches!($keyword, $($pattern)|+) @@ -167,7 +193,7 @@ impl Parser { return Ok(None); } - let expr = Some(match current_token.token_type { + let expr = match current_token.token_type { // match unsupported keywords TokenType::Keyword(e) if matches_keyword!(e, Keyword::Enum, Keyword::If, Keyword::Else) => @@ -217,7 +243,10 @@ impl Parser { // match minus symbols to handle negative numbers or negated expressions TokenType::Symbol(Symbol::Minus) => { self.assign_next()?; // consume the `-` symbol - let inner_expr = self.expression()?.ok_or(Error::UnexpectedEOF)?; + // IMPORTANT: We call `unary()` here, NOT `expression()`. + // This ensures negation binds tightly to the operand and doesn't consume binary ops. + // e.g. `-1 + 2` parses as `(-1) + 2` + let inner_expr = self.unary()?.ok_or(Error::UnexpectedEOF)?; Expression::Negation(boxed!(inner_expr)) } @@ -225,23 +254,8 @@ impl Parser { _ => { return Err(Error::UnexpectedToken(current_token.clone())); } - }); - - let Some(expr) = expr else { - return Ok(None); }; - // check if the next or current token is an operator - if self_matches_peek!(self, TokenType::Symbol(s) if s.is_operator()) { - return Ok(Some(Expression::Binary(self.binary(expr)?))); - } - // This is an edge case. We need to move back one token if the current token is an operator - // so the binary expression can pick up the operator - else if self_matches_current!(self, TokenType::Symbol(s) if s.is_operator()) { - self.tokenizer.seek(SeekFrom::Current(-1))?; - return Ok(Some(Expression::Binary(self.binary(expr)?))); - } - Ok(Some(expr)) } @@ -259,17 +273,19 @@ impl Parser { } // A priority expression ( -> (1 + 2) <- + 3 ) TokenType::Symbol(Symbol::LParen) => self.priority().map(Expression::Priority), - TokenType::Symbol(Symbol::Minus) => { - self.assign_next()?; - let inner = self.get_binary_child_node()?; - Ok(Expression::Negation(boxed!(inner))) - } // A function invocation TokenType::Identifier(_) if self_matches_peek!(self, TokenType::Symbol(Symbol::LParen)) => { self.invocation().map(Expression::Invocation) } + // Handle Negation + TokenType::Symbol(Symbol::Minus) => { + self.assign_next()?; + // recurse to handle double negation or simple negation of atoms + let inner = self.get_binary_child_node()?; + Ok(Expression::Negation(boxed!(inner))) + } _ => Err(Error::UnexpectedToken(current_token.clone())), } } From 37d56436156d50c794ca00b9a512764d42aab277 Mon Sep 17 00:00:00 2001 From: Devin Bidwell Date: Mon, 24 Nov 2025 22:53:00 -0700 Subject: [PATCH 20/22] Added support for the mod operator --- libs/compiler/src/v2.rs | 2 +- libs/parser/src/lib.rs | 148 +++------------------------------ libs/parser/src/test/blocks.rs | 21 +++++ libs/parser/src/test/mod.rs | 115 +++++++++++++++++++++++++ libs/parser/src/tree_node.rs | 2 + libs/tokenizer/src/lib.rs | 4 +- libs/tokenizer/src/token.rs | 9 +- 7 files changed, 161 insertions(+), 140 deletions(-) create mode 100644 libs/parser/src/test/blocks.rs create mode 100644 libs/parser/src/test/mod.rs diff --git a/libs/compiler/src/v2.rs b/libs/compiler/src/v2.rs index a0e0098..8e0fd28 100644 --- a/libs/compiler/src/v2.rs +++ b/libs/compiler/src/v2.rs @@ -494,6 +494,7 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { BinaryExpression::Divide(l, r) => ("div", l, r), BinaryExpression::Subtract(l, r) => ("sub", l, r), BinaryExpression::Exponent(l, r) => ("pow", l, r), + BinaryExpression::Modulo(l, r) => ("mod", l, r), }; // Compile LHS @@ -747,4 +748,3 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { Ok(()) } } - diff --git a/libs/parser/src/lib.rs b/libs/parser/src/lib.rs index 4decc64..9e2a635 100644 --- a/libs/parser/src/lib.rs +++ b/libs/parser/src/lib.rs @@ -1,3 +1,6 @@ +#[cfg(test)] +mod test; + pub mod sys_call; pub mod tree_node; @@ -409,7 +412,7 @@ impl Parser { // Loop through operators, and build the binary expressions for multiplication and division operators for (i, operator) in operators.iter().enumerate() { - if operator == &Symbol::Asterisk || operator == &Symbol::Slash { + if matches!(operator, Symbol::Slash | Symbol::Asterisk | Symbol::Percent) { let index = i - current_iteration; let left = expressions.remove(index); let right = expressions.remove(index); @@ -423,6 +426,10 @@ impl Parser { index, Expression::Binary(BinaryExpression::Divide(boxed!(left), boxed!(right))), ), + Symbol::Percent => expressions.insert( + index, + Expression::Binary(BinaryExpression::Modulo(boxed!(left), boxed!(right))), + ), // safety: we have already checked for the operator _ => unreachable!(), } @@ -431,7 +438,8 @@ impl Parser { } // remove all the multiplication and division operators from the operators vector - operators.retain(|symbol| symbol != &Symbol::Asterisk && symbol != &Symbol::Slash); + operators + .retain(|symbol| !matches!(symbol, Symbol::Asterisk | Symbol::Percent | Symbol::Slash)); current_iteration = 0; // Loop through operators, and build the binary expressions for addition and subtraction operators @@ -458,7 +466,7 @@ impl Parser { } // remove all the addition and subtraction operators from the operators vector - operators.retain(|symbol| symbol != &Symbol::Plus && symbol != &Symbol::Minus); + operators.retain(|symbol| !matches!(symbol, Symbol::Plus | Symbol::Minus)); // Ensure there is only one expression left in the expressions vector, and no operators left if expressions.len() != 1 || !operators.is_empty() { @@ -921,137 +929,3 @@ impl Parser { } } } - -#[cfg(test)] -mod tests { - use super::*; - use anyhow::Result; - - macro_rules! parser { - ($input:expr) => { - Parser::new(Tokenizer::from($input.to_owned())) - }; - } - - #[test] - fn test_unsupported_keywords() -> Result<()> { - let mut parser = parser!("enum x;"); - assert!(parser.parse().is_err()); - - let mut parser = parser!("if x {}"); - assert!(parser.parse().is_err()); - - let mut parser = parser!("else {}"); - assert!(parser.parse().is_err()); - - Ok(()) - } - - #[test] - fn test_declarations() -> Result<()> { - let input = r#" - let x = 5; - // The below line should fail - let y = 234 - "#; - let tokenizer = Tokenizer::from(input.to_owned()); - let mut parser = Parser::new(tokenizer); - - let expression = parser.parse()?.unwrap(); - - assert_eq!("(let x = 5)", expression.to_string()); - - assert!(parser.parse().is_err()); - - Ok(()) - } - - #[test] - fn test_block() -> Result<()> { - let input = r#" - { - let x = 5; - let y = 10; - } - "#; - let tokenizer = Tokenizer::from(input.to_owned()); - let mut parser = Parser::new(tokenizer); - - let expression = parser.parse()?.unwrap(); - - assert_eq!("{ (let x = 5); (let y = 10); }", expression.to_string()); - - Ok(()) - } - - #[test] - fn test_function_expression() -> Result<()> { - let input = r#" - // This is a function. The parser is starting to get more complex - fn add(x, y) { - let z = x; - } - "#; - - let tokenizer = Tokenizer::from(input.to_owned()); - let mut parser = Parser::new(tokenizer); - - let expression = parser.parse()?.unwrap(); - - assert_eq!( - "(fn add(x, y) { { (let z = x); } })", - expression.to_string() - ); - - Ok(()) - } - - #[test] - fn test_function_invocation() -> Result<()> { - let input = r#" - add(); - "#; - - let tokenizer = Tokenizer::from(input.to_owned()); - let mut parser = Parser::new(tokenizer); - - let expression = parser.parse()?.unwrap(); - - assert_eq!("add()", expression.to_string()); - - Ok(()) - } - - #[test] - fn test_priority_expression() -> Result<()> { - let input = r#" - let x = (4); - "#; - - let tokenizer = Tokenizer::from(input.to_owned()); - let mut parser = Parser::new(tokenizer); - - let expression = parser.parse()?.unwrap(); - - assert_eq!("(let x = (4))", expression.to_string()); - - Ok(()) - } - - #[test] - fn test_binary_expression() -> Result<()> { - 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()); - - let expr = parser!("(5 - 2) * 10").parse()?.unwrap(); - assert_eq!("(((5 - 2)) * 10)", expr.to_string()); - - Ok(()) - } -} diff --git a/libs/parser/src/test/blocks.rs b/libs/parser/src/test/blocks.rs new file mode 100644 index 0000000..39e06c6 --- /dev/null +++ b/libs/parser/src/test/blocks.rs @@ -0,0 +1,21 @@ +use tokenizer::Tokenizer; + +use crate::Parser; + +#[test] +fn test_block() -> anyhow::Result<()> { + let mut parser = crate::parser!( + r#" + { + let x = 5; + let y = 10; + } + "# + ); + + let expression = parser.parse()?.unwrap(); + + assert_eq!("{ (let x = 5); (let y = 10); }", expression.to_string()); + + Ok(()) +} diff --git a/libs/parser/src/test/mod.rs b/libs/parser/src/test/mod.rs new file mode 100644 index 0000000..5822da9 --- /dev/null +++ b/libs/parser/src/test/mod.rs @@ -0,0 +1,115 @@ +#[macro_export] +macro_rules! parser { + ($input:expr) => { + Parser::new(Tokenizer::from($input.to_owned())) + }; +} + +mod blocks; +use super::Parser; +use super::Tokenizer; +use anyhow::Result; + +#[test] +fn test_unsupported_keywords() -> Result<()> { + let mut parser = parser!("enum x;"); + assert!(parser.parse().is_err()); + + let mut parser = parser!("if x {}"); + assert!(parser.parse().is_err()); + + let mut parser = parser!("else {}"); + assert!(parser.parse().is_err()); + + Ok(()) +} + +#[test] +fn test_declarations() -> Result<()> { + let input = r#" + let x = 5; + // The below line should fail + let y = 234 + "#; + let tokenizer = Tokenizer::from(input.to_owned()); + let mut parser = Parser::new(tokenizer); + + let expression = parser.parse()?.unwrap(); + + assert_eq!("(let x = 5)", expression.to_string()); + + assert!(parser.parse().is_err()); + + Ok(()) +} + +#[test] +fn test_function_expression() -> Result<()> { + let input = r#" + // This is a function. The parser is starting to get more complex + fn add(x, y) { + let z = x; + } + "#; + + let tokenizer = Tokenizer::from(input.to_owned()); + let mut parser = Parser::new(tokenizer); + + let expression = parser.parse()?.unwrap(); + + assert_eq!( + "(fn add(x, y) { { (let z = x); } })", + expression.to_string() + ); + + Ok(()) +} + +#[test] +fn test_function_invocation() -> Result<()> { + let input = r#" + add(); + "#; + + let tokenizer = Tokenizer::from(input.to_owned()); + let mut parser = Parser::new(tokenizer); + + let expression = parser.parse()?.unwrap(); + + assert_eq!("add()", expression.to_string()); + + Ok(()) +} + +#[test] +fn test_priority_expression() -> Result<()> { + let input = r#" + let x = (4); + "#; + + let tokenizer = Tokenizer::from(input.to_owned()); + let mut parser = Parser::new(tokenizer); + + let expression = parser.parse()?.unwrap(); + + assert_eq!("(let x = (4))", expression.to_string()); + + Ok(()) +} + +#[test] +fn test_binary_expression() -> Result<()> { + 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()); + + let expr = parser!("(5 - 2) * 10").parse()?.unwrap(); + assert_eq!("(((5 - 2)) * 10)", expr.to_string()); + + Ok(()) +} diff --git a/libs/parser/src/tree_node.rs b/libs/parser/src/tree_node.rs index 4cfc112..456a9c4 100644 --- a/libs/parser/src/tree_node.rs +++ b/libs/parser/src/tree_node.rs @@ -23,6 +23,7 @@ pub enum BinaryExpression { Divide(Box, Box), Subtract(Box, Box), Exponent(Box, Box), + Modulo(Box, Box), } impl std::fmt::Display for BinaryExpression { @@ -33,6 +34,7 @@ impl std::fmt::Display for BinaryExpression { BinaryExpression::Divide(l, r) => write!(f, "({} / {})", l, r), BinaryExpression::Subtract(l, r) => write!(f, "({} - {})", l, r), BinaryExpression::Exponent(l, r) => write!(f, "({} ** {})", l, r), + BinaryExpression::Modulo(l, r) => write!(f, "({} % {})", l, r), } } } diff --git a/libs/tokenizer/src/lib.rs b/libs/tokenizer/src/lib.rs index 215d053..ad767f6 100644 --- a/libs/tokenizer/src/lib.rs +++ b/libs/tokenizer/src/lib.rs @@ -221,6 +221,7 @@ impl Tokenizer { '.' => symbol!(Dot), '^' => symbol!(Caret), + '%' => symbol!(Percent), // multi-character symbols '<' if self.peek_next_char()? == Some('=') => { @@ -736,7 +737,7 @@ mod tests { #[test] fn test_symbol_parse() -> Result<()> { let mut tokenizer = Tokenizer::from(String::from( - "^ ! () [] {} , . ; : + - * / < > = != && || >= <=**", + "^ ! () [] {} , . ; : + - * / < > = != && || >= <=**%", )); let expected_tokens = vec![ @@ -765,6 +766,7 @@ mod tests { TokenType::Symbol(Symbol::GreaterThanOrEqual), TokenType::Symbol(Symbol::LessThanOrEqual), TokenType::Symbol(Symbol::Exp), + TokenType::Symbol(Symbol::Percent), ]; for expected_token in expected_tokens { diff --git a/libs/tokenizer/src/token.rs b/libs/tokenizer/src/token.rs index c1de1c3..93a090c 100644 --- a/libs/tokenizer/src/token.rs +++ b/libs/tokenizer/src/token.rs @@ -158,6 +158,8 @@ pub enum Symbol { Dot, /// Represents the `^` symbol Caret, + /// Represents the `%` symbol + Percent, // Double Character Symbols /// Represents the `==` symbol @@ -180,7 +182,12 @@ impl Symbol { pub fn is_operator(&self) -> bool { matches!( self, - Symbol::Plus | Symbol::Minus | Symbol::Asterisk | Symbol::Slash | Symbol::Exp + Symbol::Plus + | Symbol::Minus + | Symbol::Asterisk + | Symbol::Slash + | Symbol::Exp + | Symbol::Percent ) } From 30b67fec542e0d6a921b5bf51c9cd3033a440b84 Mon Sep 17 00:00:00 2001 From: Devin Bidwell Date: Mon, 24 Nov 2025 23:55:33 -0700 Subject: [PATCH 21/22] compiler now on par with where it was in the v1 version --- libs/compiler/test_files/math.slang | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libs/compiler/test_files/math.slang b/libs/compiler/test_files/math.slang index 2fa5adc..1d9adb2 100644 --- a/libs/compiler/test_files/math.slang +++ b/libs/compiler/test_files/math.slang @@ -1,7 +1,7 @@ fn addTemperatures(temp1, temp2) { - let toReturn = temp1 + temp2; + return temp1 + temp2; }; -addTemperatures(15c, 120c); -addTemperatures(1500f, 20c); +let newTemp1 = addTemperatures(15c, 120c); +let newTemp2 = addTemperatures(50c, 20c); From 543ec5532a2d8f54492afaeb6102079435892c4a Mon Sep 17 00:00:00 2001 From: Devin Bidwell Date: Mon, 24 Nov 2025 23:56:49 -0700 Subject: [PATCH 22/22] rename v2 -> v1 --- libs/compiler/src/lib.rs | 4 ++-- libs/compiler/src/{v2.rs => v1.rs} | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename libs/compiler/src/{v2.rs => v1.rs} (100%) diff --git a/libs/compiler/src/lib.rs b/libs/compiler/src/lib.rs index f7cb92c..db2ac3f 100644 --- a/libs/compiler/src/lib.rs +++ b/libs/compiler/src/lib.rs @@ -1,6 +1,6 @@ #[cfg(test)] mod test; -mod v2; +mod v1; mod variable_manager; -pub use v2::{Compiler, CompilerConfig, Error}; +pub use v1::{Compiler, CompilerConfig, Error}; diff --git a/libs/compiler/src/v2.rs b/libs/compiler/src/v1.rs similarity index 100% rename from libs/compiler/src/v2.rs rename to libs/compiler/src/v1.rs