Basic support for logical expressions

This commit is contained in:
2025-11-25 00:26:17 -07:00
parent 4e87b57961
commit dd433e1746
5 changed files with 392 additions and 42 deletions

View File

@@ -164,15 +164,22 @@ impl Parser {
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)?)));
// check if the next or current token is an operator, comparison, or logical symbol
if self_matches_peek!(
self,
TokenType::Symbol(s) if s.is_operator() || s.is_comparison() || s.is_logical()
) {
return Ok(Some(self.infix(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()) {
// This is an edge case. We need to move back one token if the current token is an
// operator, comparison, or logical symbol so the binary expression can pick up
// the operator
else if self_matches_current!(
self,
TokenType::Symbol(s) if s.is_operator() || s.is_comparison() || s.is_logical()
) {
self.tokenizer.seek(SeekFrom::Current(-1))?;
return Ok(Some(Expression::Binary(self.binary(lhs)?)));
return Ok(Some(self.infix(lhs)?));
}
Ok(Some(lhs))
@@ -254,6 +261,13 @@ impl Parser {
Expression::Negation(boxed!(inner_expr))
}
// match logical NOT `!`
TokenType::Symbol(Symbol::LogicalNot) => {
self.assign_next()?; // consume the `!` symbol
let inner_expr = self.unary()?.ok_or(Error::UnexpectedEOF)?;
Expression::Logical(LogicalExpression::Not(boxed!(inner_expr)))
}
_ => {
return Err(Error::UnexpectedToken(current_token.clone()));
}
@@ -262,7 +276,7 @@ impl Parser {
Ok(Some(expr))
}
fn get_binary_child_node(&mut self) -> Result<tree_node::Expression, Error> {
fn get_infix_child_node(&mut self) -> Result<tree_node::Expression, Error> {
let current_token = token_from_option!(self.current_token);
match current_token.token_type {
@@ -286,9 +300,15 @@ impl Parser {
TokenType::Symbol(Symbol::Minus) => {
self.assign_next()?;
// recurse to handle double negation or simple negation of atoms
let inner = self.get_binary_child_node()?;
let inner = self.get_infix_child_node()?;
Ok(Expression::Negation(boxed!(inner)))
}
// Handle Logical Not
TokenType::Symbol(Symbol::LogicalNot) => {
self.assign_next()?;
let inner = self.get_infix_child_node()?;
Ok(Expression::Logical(LogicalExpression::Not(boxed!(inner))))
}
_ => Err(Error::UnexpectedToken(current_token.clone())),
}
}
@@ -345,8 +365,8 @@ impl Parser {
})
}
/// Handles mathmatical expressions in the explicit order of PEMDAS
fn binary(&mut self, previous: Expression) -> Result<BinaryExpression, Error> {
/// Handles mathmatical and logical expressions in the explicit order of operations
fn infix(&mut self, previous: Expression) -> Result<Expression, Error> {
// 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.
@@ -354,15 +374,18 @@ impl Parser {
// first, make sure the previous expression supports binary expressions
match previous {
Expression::Binary(_) // 1 + 2 + 3
| Expression::Invocation(_) // add() + 3
| Expression::Priority(_) // (1 + 2) + 3
| Expression::Literal(Literal::Number(_)) // 1 + 2 (no addition of strings)
| Expression::Variable(_) // x + 2
| Expression::Negation(_) // -1 + 2
=> {}
Expression::Binary(_)
| Expression::Logical(_)
| Expression::Invocation(_)
| Expression::Priority(_)
| Expression::Literal(Literal::Number(_))
| Expression::Variable(_)
| Expression::Negation(_) => {}
_ => {
return Err(Error::InvalidSyntax(current_token.clone(), String::from("Invalid expression for binary operation")))
return Err(Error::InvalidSyntax(
current_token.clone(),
String::from("Invalid expression for binary/logical operation"),
));
}
}
@@ -372,12 +395,15 @@ impl Parser {
let mut operators = Vec::<Symbol>::new(); // +, +
// build the expressions and operators vectors
while token_matches!(current_token, TokenType::Symbol(s) if s.is_operator()) {
// We are guaranteed to have an operator symbol here as we checked in the while loop
while token_matches!(
current_token,
TokenType::Symbol(s) if s.is_operator() || s.is_comparison() || s.is_logical()
) {
// We are guaranteed to have an operator/comparison/logical symbol here as we checked in the while loop
let operator = extract_token_data!(current_token, TokenType::Symbol(s), s);
operators.push(operator);
self.assign_next()?;
expressions.push(self.get_binary_child_node()?);
expressions.push(self.get_infix_child_node()?);
current_token = token_from_option!(self.get_next()?).clone();
}
@@ -394,7 +420,7 @@ impl Parser {
// This means that we need to keep track of the current iteration to ensure we are
// removing the correct expressions from the vector
// Loop through operators, and build the binary expressions for exponential operators only
// --- PRECEDENCE LEVEL 1: Exponent (**) ---
for (i, operator) in operators.iter().enumerate().rev() {
if operator == &Symbol::Exp {
let right = expressions.remove(i + 1);
@@ -405,12 +431,10 @@ impl Parser {
);
}
}
// remove all the exponential operators from the operators vector
operators.retain(|symbol| symbol != &Symbol::Exp);
let mut current_iteration = 0;
// Loop through operators, and build the binary expressions for multiplication and division operators
// --- PRECEDENCE LEVEL 2: Multiplicative (*, /, %) ---
let mut current_iteration = 0;
for (i, operator) in operators.iter().enumerate() {
if matches!(operator, Symbol::Slash | Symbol::Asterisk | Symbol::Percent) {
let index = i - current_iteration;
@@ -430,21 +454,18 @@ impl Parser {
index,
Expression::Binary(BinaryExpression::Modulo(boxed!(left), boxed!(right))),
),
// safety: we have already checked for the operator
_ => unreachable!(),
}
current_iteration += 1;
}
}
// remove all the multiplication and division operators from the operators vector
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
// --- PRECEDENCE LEVEL 3: Additive (+, -) ---
current_iteration = 0;
for (i, operator) in operators.iter().enumerate() {
if operator == &Symbol::Plus || operator == &Symbol::Minus {
if matches!(operator, Symbol::Plus | Symbol::Minus) {
let index = i - current_iteration;
let left = expressions.remove(index);
let right = expressions.remove(index);
@@ -458,16 +479,120 @@ impl Parser {
index,
Expression::Binary(BinaryExpression::Subtract(boxed!(left), boxed!(right))),
),
// safety: we have already checked for the operator
_ => unreachable!(),
}
current_iteration += 1;
}
}
// remove all the addition and subtraction operators from the operators vector
operators.retain(|symbol| !matches!(symbol, Symbol::Plus | Symbol::Minus));
// --- PRECEDENCE LEVEL 4: Comparison (<, >, <=, >=) ---
current_iteration = 0;
for (i, operator) in operators.iter().enumerate() {
if operator.is_comparison() && !matches!(operator, Symbol::Equal | Symbol::NotEqual) {
let index = i - current_iteration;
let left = expressions.remove(index);
let right = expressions.remove(index);
match operator {
Symbol::LessThan => expressions.insert(
index,
Expression::Logical(LogicalExpression::LessThan(
boxed!(left),
boxed!(right),
)),
),
Symbol::GreaterThan => expressions.insert(
index,
Expression::Logical(LogicalExpression::GreaterThan(
boxed!(left),
boxed!(right),
)),
),
Symbol::LessThanOrEqual => expressions.insert(
index,
Expression::Logical(LogicalExpression::LessThanOrEqual(
boxed!(left),
boxed!(right),
)),
),
Symbol::GreaterThanOrEqual => expressions.insert(
index,
Expression::Logical(LogicalExpression::GreaterThanOrEqual(
boxed!(left),
boxed!(right),
)),
),
_ => unreachable!(),
}
current_iteration += 1;
}
}
operators.retain(|symbol| {
!symbol.is_comparison() || matches!(symbol, Symbol::Equal | Symbol::NotEqual)
});
// --- PRECEDENCE LEVEL 5: Equality (==, !=) ---
current_iteration = 0;
for (i, operator) in operators.iter().enumerate() {
if matches!(operator, Symbol::Equal | Symbol::NotEqual) {
let index = i - current_iteration;
let left = expressions.remove(index);
let right = expressions.remove(index);
match operator {
Symbol::Equal => expressions.insert(
index,
Expression::Logical(LogicalExpression::Equal(boxed!(left), boxed!(right))),
),
Symbol::NotEqual => expressions.insert(
index,
Expression::Logical(LogicalExpression::NotEqual(
boxed!(left),
boxed!(right),
)),
),
_ => unreachable!(),
}
current_iteration += 1;
}
}
operators.retain(|symbol| !matches!(symbol, Symbol::Equal | Symbol::NotEqual));
// --- PRECEDENCE LEVEL 6: Logical AND (&&) ---
current_iteration = 0;
for (i, operator) in operators.iter().enumerate() {
if matches!(operator, Symbol::LogicalAnd) {
let index = i - current_iteration;
let left = expressions.remove(index);
let right = expressions.remove(index);
expressions.insert(
index,
Expression::Logical(LogicalExpression::And(boxed!(left), boxed!(right))),
);
current_iteration += 1;
}
}
operators.retain(|symbol| !matches!(symbol, Symbol::LogicalAnd));
// --- PRECEDENCE LEVEL 7: Logical OR (||) ---
current_iteration = 0;
for (i, operator) in operators.iter().enumerate() {
if matches!(operator, Symbol::LogicalOr) {
let index = i - current_iteration;
let left = expressions.remove(index);
let right = expressions.remove(index);
expressions.insert(
index,
Expression::Logical(LogicalExpression::Or(boxed!(left), boxed!(right))),
);
current_iteration += 1;
}
}
operators.retain(|symbol| !matches!(symbol, Symbol::LogicalOr));
// Ensure there is only one expression left in the expressions vector, and no operators left
if expressions.len() != 1 || !operators.is_empty() {
return Err(Error::InvalidSyntax(
@@ -484,11 +609,7 @@ impl Parser {
self.tokenizer.seek(SeekFrom::Current(-1))?;
}
// Ensure the last expression is a binary expression
match expressions.pop().unwrap() {
Expression::Binary(binary) => Ok(binary),
_ => unreachable!(),
}
Ok(expressions.pop().unwrap())
}
fn priority(&mut self) -> Result<Box<Expression>, Error> {

View File

@@ -5,6 +5,7 @@ use tokenizer::token::Number;
pub enum Literal {
Number(Number),
String(String),
Boolean(bool),
}
impl std::fmt::Display for Literal {
@@ -12,6 +13,7 @@ impl std::fmt::Display for Literal {
match self {
Literal::Number(n) => write!(f, "{}", n),
Literal::String(s) => write!(f, "\"{}\"", s),
Literal::Boolean(b) => write!(f, "{}", if *b { 1 } else { 0 }),
}
}
}