From 37d56436156d50c794ca00b9a512764d42aab277 Mon Sep 17 00:00:00 2001 From: Devin Bidwell Date: Mon, 24 Nov 2025 22:53:00 -0700 Subject: [PATCH] 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 ) }