Added support for the mod operator

This commit is contained in:
2025-11-24 22:53:00 -07:00
parent 56f0e292b7
commit 37d5643615
7 changed files with 161 additions and 140 deletions

View File

@@ -494,6 +494,7 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
BinaryExpression::Divide(l, r) => ("div", l, r), BinaryExpression::Divide(l, r) => ("div", l, r),
BinaryExpression::Subtract(l, r) => ("sub", l, r), BinaryExpression::Subtract(l, r) => ("sub", l, r),
BinaryExpression::Exponent(l, r) => ("pow", l, r), BinaryExpression::Exponent(l, r) => ("pow", l, r),
BinaryExpression::Modulo(l, r) => ("mod", l, r),
}; };
// Compile LHS // Compile LHS
@@ -747,4 +748,3 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
Ok(()) Ok(())
} }
} }

View File

@@ -1,3 +1,6 @@
#[cfg(test)]
mod test;
pub mod sys_call; pub mod sys_call;
pub mod tree_node; pub mod tree_node;
@@ -409,7 +412,7 @@ impl Parser {
// Loop through operators, and build the binary expressions for multiplication and division operators // Loop through operators, and build the binary expressions for multiplication and division operators
for (i, operator) in operators.iter().enumerate() { 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 index = i - current_iteration;
let left = expressions.remove(index); let left = expressions.remove(index);
let right = expressions.remove(index); let right = expressions.remove(index);
@@ -423,6 +426,10 @@ impl Parser {
index, index,
Expression::Binary(BinaryExpression::Divide(boxed!(left), boxed!(right))), 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 // safety: we have already checked for the operator
_ => unreachable!(), _ => unreachable!(),
} }
@@ -431,7 +438,8 @@ impl Parser {
} }
// remove all the multiplication and division operators from the operators vector // 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; current_iteration = 0;
// Loop through operators, and build the binary expressions for addition and subtraction operators // 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 // 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 // Ensure there is only one expression left in the expressions vector, and no operators left
if expressions.len() != 1 || !operators.is_empty() { 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(())
}
}

View File

@@ -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(())
}

115
libs/parser/src/test/mod.rs Normal file
View File

@@ -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(())
}

View File

@@ -23,6 +23,7 @@ pub enum BinaryExpression {
Divide(Box<Expression>, Box<Expression>), Divide(Box<Expression>, Box<Expression>),
Subtract(Box<Expression>, Box<Expression>), Subtract(Box<Expression>, Box<Expression>),
Exponent(Box<Expression>, Box<Expression>), Exponent(Box<Expression>, Box<Expression>),
Modulo(Box<Expression>, Box<Expression>),
} }
impl std::fmt::Display for BinaryExpression { 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::Divide(l, r) => write!(f, "({} / {})", l, r),
BinaryExpression::Subtract(l, r) => write!(f, "({} - {})", l, r), BinaryExpression::Subtract(l, r) => write!(f, "({} - {})", l, r),
BinaryExpression::Exponent(l, r) => write!(f, "({} ** {})", l, r), BinaryExpression::Exponent(l, r) => write!(f, "({} ** {})", l, r),
BinaryExpression::Modulo(l, r) => write!(f, "({} % {})", l, r),
} }
} }
} }

View File

@@ -221,6 +221,7 @@ impl Tokenizer {
'.' => symbol!(Dot), '.' => symbol!(Dot),
'^' => symbol!(Caret), '^' => symbol!(Caret),
'%' => symbol!(Percent),
// multi-character symbols // multi-character symbols
'<' if self.peek_next_char()? == Some('=') => { '<' if self.peek_next_char()? == Some('=') => {
@@ -736,7 +737,7 @@ mod tests {
#[test] #[test]
fn test_symbol_parse() -> Result<()> { fn test_symbol_parse() -> Result<()> {
let mut tokenizer = Tokenizer::from(String::from( let mut tokenizer = Tokenizer::from(String::from(
"^ ! () [] {} , . ; : + - * / < > = != && || >= <=**", "^ ! () [] {} , . ; : + - * / < > = != && || >= <=**%",
)); ));
let expected_tokens = vec![ let expected_tokens = vec![
@@ -765,6 +766,7 @@ mod tests {
TokenType::Symbol(Symbol::GreaterThanOrEqual), TokenType::Symbol(Symbol::GreaterThanOrEqual),
TokenType::Symbol(Symbol::LessThanOrEqual), TokenType::Symbol(Symbol::LessThanOrEqual),
TokenType::Symbol(Symbol::Exp), TokenType::Symbol(Symbol::Exp),
TokenType::Symbol(Symbol::Percent),
]; ];
for expected_token in expected_tokens { for expected_token in expected_tokens {

View File

@@ -158,6 +158,8 @@ pub enum Symbol {
Dot, Dot,
/// Represents the `^` symbol /// Represents the `^` symbol
Caret, Caret,
/// Represents the `%` symbol
Percent,
// Double Character Symbols // Double Character Symbols
/// Represents the `==` symbol /// Represents the `==` symbol
@@ -180,7 +182,12 @@ impl Symbol {
pub fn is_operator(&self) -> bool { pub fn is_operator(&self) -> bool {
matches!( matches!(
self, self,
Symbol::Plus | Symbol::Minus | Symbol::Asterisk | Symbol::Slash | Symbol::Exp Symbol::Plus
| Symbol::Minus
| Symbol::Asterisk
| Symbol::Slash
| Symbol::Exp
| Symbol::Percent
) )
} }