feat: add support for booleans and comparision operators

This commit is contained in:
2026-04-21 10:56:42 +02:00
parent bad6b9e116
commit eb3663dfbb
11 changed files with 293 additions and 3 deletions
+8
View File
@@ -91,6 +91,7 @@ pub enum ExprKind {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UnaryOp {
Neg,
Not,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -100,4 +101,11 @@ pub enum BinaryOp {
Mul,
Div,
Rem,
Eq,
Neq,
Lt,
Le,
Gt,
Ge,
}
+22
View File
@@ -142,6 +142,11 @@ impl<'src> Lexer<'src> {
'/' => token!(TokenKind::Slash),
'%' => token!(TokenKind::Percent),
'!' => token!(TokenKind::Bang, '=' => TokenKind::Unequal),
'=' => token!(TokenKind::Assign, '=' => TokenKind::Equal),
'<' => token!(TokenKind::LessThan, '=' => TokenKind::LessEqual),
'>' => token!(TokenKind::GreaterThan, '=' => TokenKind::GreaterEqual),
'.' => token!(TokenKind::Dot),
',' => token!(TokenKind::Comma),
':' => token!(TokenKind::Colon),
@@ -283,4 +288,21 @@ mod test {
]
)
}
#[test]
fn comparison_and_logical() {
assert_eq!(
tokenize("== != < <= > >= ! ="),
vec![
Token::new(TokenKind::Equal, "==", Span::new(0, 2)),
Token::new(TokenKind::Unequal, "!=", Span::new(3, 5)),
Token::new(TokenKind::LessThan, "<", Span::new(6, 7)),
Token::new(TokenKind::LessEqual, "<=", Span::new(8, 10)),
Token::new(TokenKind::GreaterThan, ">", Span::new(11, 12)),
Token::new(TokenKind::GreaterEqual, ">=", Span::new(13, 15)),
Token::new(TokenKind::Bang, "!", Span::new(16, 17)),
Token::new(TokenKind::Assign, "=", Span::new(18, 19)),
]
)
}
}
+48
View File
@@ -481,6 +481,7 @@ impl<'src> Parser<'src> {
fn prefix_operator(&self, op: TokenKind) -> Option<(UnaryOp, u8)> {
match op {
TokenKind::Minus => Some((UnaryOp::Neg, 30)),
TokenKind::Bang => Some((UnaryOp::Not, 30)),
_ => None,
}
@@ -490,6 +491,13 @@ impl<'src> Parser<'src> {
/// or `None` if the [TokenKind] is not a valid infix operator.
fn infix_operator(&self, op: TokenKind) -> Option<(BinaryOp, u8, u8)> {
match op {
TokenKind::Equal => Some((BinaryOp::Eq, 5, 6)),
TokenKind::Unequal => Some((BinaryOp::Neq, 5, 6)),
TokenKind::LessThan => Some((BinaryOp::Lt, 5, 6)),
TokenKind::LessEqual => Some((BinaryOp::Le, 5, 6)),
TokenKind::GreaterThan => Some((BinaryOp::Gt, 5, 6)),
TokenKind::GreaterEqual => Some((BinaryOp::Ge, 5, 6)),
TokenKind::Plus => Some((BinaryOp::Add, 10, 11)),
TokenKind::Minus => Some((BinaryOp::Sub, 10, 11)),
@@ -764,4 +772,44 @@ mod test {
})
)
}
#[test]
fn comparison_expr() {
assert_eq!(
parse("a >= 5;", Parser::parse_expr),
Success(Expr {
kind: ExprKind::Binary {
op: BinaryOp::Ge,
lhs: Box::new(Expr {
kind: ExprKind::Identifier {
name: "a".to_string()
},
span: Span::new(0, 1)
}),
rhs: Box::new(Expr {
kind: ExprKind::Integer { value: 5 },
span: Span::new(5, 6)
})
},
span: Span::new(0, 6)
})
);
}
#[test]
fn logical_not_expr() {
assert_eq!(
parse("!true;", Parser::parse_expr),
Success(Expr {
kind: ExprKind::Unary {
op: UnaryOp::Not,
expr: Box::new(Expr {
kind: ExprKind::Boolean { value: true },
span: Span::new(1, 5)
})
},
span: Span::new(0, 5)
})
);
}
}
+66 -2
View File
@@ -348,6 +348,25 @@ impl Sema {
}
}
ExprKind::Unary {
op: UnaryOp::Not,
expr,
} => {
let typed_inner = self.analyze_expr(expr);
if let Err(e) = self.unify(&Ty::Bool, &typed_inner.ty) {
self.errors.push(SemanticError::new(e, expr.span));
}
TypedExpr {
kind: TypedExprKind::Unary {
op: UnaryOp::Not,
expr: Box::new(typed_inner),
},
ty: Ty::Bool,
}
}
ExprKind::Binary { op, lhs, rhs } => {
let typed_lhs = self.analyze_expr(lhs);
let typed_rhs = self.analyze_expr(rhs);
@@ -356,8 +375,24 @@ impl Sema {
self.errors.push(SemanticError::new(e, expr.span));
}
let result_ty = typed_lhs.ty.clone();
self.deferred_binary.push((expr.span, result_ty.clone()));
let is_comparison = matches!(
op,
BinaryOp::Eq
| BinaryOp::Neq
| BinaryOp::Lt
| BinaryOp::Le
| BinaryOp::Gt
| BinaryOp::Ge
);
let result_ty = if is_comparison {
Ty::Bool
} else {
typed_lhs.ty.clone()
};
self.deferred_binary.push((expr.span, typed_lhs.ty.clone()));
TypedExpr {
kind: TypedExprKind::Binary {
op: *op,
@@ -628,4 +663,33 @@ mod test {
.contains("unary minus only works on integer types")
}));
}
#[test]
fn valid_logical_not() {
let src = "fn test(a: bool) -> bool { return !a; }";
assert!(analyze(src).is_ok());
}
#[test]
fn invalid_logical_not() {
let src = "fn test(a: i32) -> bool { return !a; }";
let errors = analyze(src).unwrap_err();
assert!(errors.iter().any(|e| e.message.contains("type mismatch")));
}
#[test]
fn valid_comparison() {
let src = "fn test(a: i32, b: i32) -> bool { return a <= b; }";
assert!(analyze(src).is_ok());
}
#[test]
fn invalid_comparison() {
let src = "fn test(a: bool, b: bool) -> bool { return a == b; }";
let errors = analyze(src).unwrap_err();
assert!(errors.iter().any(|e| {
e.message
.contains("binary operators only work on integer types")
}));
}
}
+18
View File
@@ -70,6 +70,16 @@ pub enum TokenKind {
Slash,
Percent,
Equal,
Unequal,
LessThan,
LessEqual,
GreaterThan,
GreaterEqual,
Assign,
Bang,
// Punctuation
Dot,
Comma,
@@ -109,6 +119,14 @@ impl Display for TokenKind {
TokenKind::Star => "`*`",
TokenKind::Slash => "`/`",
TokenKind::Percent => "`%`",
TokenKind::Equal => "`==`",
TokenKind::Unequal => "`!=`",
TokenKind::LessThan => "`<`",
TokenKind::LessEqual => "`<=`",
TokenKind::GreaterThan => "`>`",
TokenKind::GreaterEqual => "`>=`",
TokenKind::Assign => "`=`",
TokenKind::Bang => "`!`",
TokenKind::Dot => "`.`",
TokenKind::Comma => "`,`",
TokenKind::Colon => "`:`",