feat: add support for booleans and comparision operators
This commit is contained in:
@@ -48,6 +48,7 @@ A Rust-flavored, C-targeting language - built pipeline-first.
|
||||
## Planned Features (Backlog)
|
||||
|
||||
### Control flow
|
||||
- [x] booleans and comparision operators
|
||||
- [ ] `if` / `else` branching
|
||||
- [ ] `while` loops
|
||||
|
||||
|
||||
@@ -44,6 +44,48 @@ run_test() {
|
||||
echo
|
||||
}
|
||||
|
||||
# --- Helper Function ---
|
||||
run_harness_test() {
|
||||
local src_file="$1"
|
||||
local harness_file="$2"
|
||||
local expected_code="$3"
|
||||
local base_name
|
||||
base_name=$(basename "$src_file" .src)
|
||||
local obj_file="tests/$base_name.o"
|
||||
local exec_file="tests/$base_name"
|
||||
|
||||
echo "--- Running test: $src_file ---"
|
||||
|
||||
# 1. Compile the source file using our compiler
|
||||
echo " [1/4] Compiling..."
|
||||
cargo run --release -- "$src_file" -o "$obj_file" > /dev/null 2>&1
|
||||
|
||||
# 2. Link the object file with the system linker (gcc)
|
||||
echo " [2/4] Linking with gcc..."
|
||||
gcc "$obj_file" "$harness_file" -o "$exec_file"
|
||||
|
||||
# 3. Run the executable
|
||||
echo " [3/4] Running..."
|
||||
set +e
|
||||
./"$exec_file"
|
||||
local actual_code=$?
|
||||
set -e
|
||||
|
||||
# 4. Check the exit code
|
||||
echo " [4/4] Verifying exit code..."
|
||||
if [ "$actual_code" -eq "$expected_code" ]; then
|
||||
echo "SUCCESS: Exit code is $actual_code as expected."
|
||||
else
|
||||
echo "FAILURE: Expected exit code $expected_code, but got $actual_code."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Clean up generated files
|
||||
rm "$obj_file" "$exec_file"
|
||||
echo "------------------------------------"
|
||||
echo
|
||||
}
|
||||
|
||||
# --- Test Cases ---
|
||||
|
||||
# Test a simple positive return value.
|
||||
@@ -53,4 +95,7 @@ run_test "tests/return_42.src" 42
|
||||
# so -69 (i8) wraps around to 187.
|
||||
run_test "tests/return_neg_69.src" 187
|
||||
|
||||
# Test boolean operations.
|
||||
run_harness_test "tests/booleans.src" "tests/booleans.c" 0
|
||||
|
||||
echo "All end-to-end tests passed!"
|
||||
@@ -192,6 +192,14 @@ impl<'a> FunctionTranslator<'a> {
|
||||
let inner_val = self.translate_expr(inner);
|
||||
match op {
|
||||
UnaryOp::Neg => self.builder.ins().ineg(inner_val),
|
||||
UnaryOp::Not => {
|
||||
// `!x` is equivalent to `x == 0` for booleans (0 or 1).
|
||||
let ty = CraneliftBackend::lower_type(&inner.ty);
|
||||
let zero = self.builder.ins().iconst(ty, 0);
|
||||
self.builder
|
||||
.ins()
|
||||
.icmp(ir::condcodes::IntCC::Equal, inner_val, zero)
|
||||
}
|
||||
}
|
||||
}
|
||||
TypedExprKind::Binary { op, lhs, rhs } => {
|
||||
@@ -218,6 +226,48 @@ impl<'a> FunctionTranslator<'a> {
|
||||
self.builder.ins().urem(lhs_val, rhs_val)
|
||||
}
|
||||
}
|
||||
BinaryOp::Eq => {
|
||||
self.builder
|
||||
.ins()
|
||||
.icmp(ir::condcodes::IntCC::Equal, lhs_val, rhs_val)
|
||||
}
|
||||
BinaryOp::Neq => {
|
||||
self.builder
|
||||
.ins()
|
||||
.icmp(ir::condcodes::IntCC::NotEqual, lhs_val, rhs_val)
|
||||
}
|
||||
BinaryOp::Lt => {
|
||||
let cc = if is_signed {
|
||||
ir::condcodes::IntCC::SignedLessThan
|
||||
} else {
|
||||
ir::condcodes::IntCC::UnsignedLessThan
|
||||
};
|
||||
self.builder.ins().icmp(cc, lhs_val, rhs_val)
|
||||
}
|
||||
BinaryOp::Le => {
|
||||
let cc = if is_signed {
|
||||
ir::condcodes::IntCC::SignedLessThanOrEqual
|
||||
} else {
|
||||
ir::condcodes::IntCC::UnsignedLessThanOrEqual
|
||||
};
|
||||
self.builder.ins().icmp(cc, lhs_val, rhs_val)
|
||||
}
|
||||
BinaryOp::Gt => {
|
||||
let cc = if is_signed {
|
||||
ir::condcodes::IntCC::SignedGreaterThan
|
||||
} else {
|
||||
ir::condcodes::IntCC::UnsignedGreaterThan
|
||||
};
|
||||
self.builder.ins().icmp(cc, lhs_val, rhs_val)
|
||||
}
|
||||
BinaryOp::Ge => {
|
||||
let cc = if is_signed {
|
||||
ir::condcodes::IntCC::SignedGreaterThanOrEqual
|
||||
} else {
|
||||
ir::condcodes::IntCC::UnsignedGreaterThanOrEqual
|
||||
};
|
||||
self.builder.ins().icmp(cc, lhs_val, rhs_val)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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)),
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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")
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 => "`:`",
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
#include <stdbool.h>
|
||||
|
||||
extern bool eq(int a, int b);
|
||||
extern bool neq(int a, int b);
|
||||
extern bool lt(int a, int b);
|
||||
extern bool lte(int a, int b);
|
||||
extern bool gt(int a, int b);
|
||||
extern bool gte(int a, int b);
|
||||
extern bool not_bool(bool a);
|
||||
|
||||
int main() {
|
||||
if (!eq(5, 5)) return 1;
|
||||
if (eq(5, 6)) return 2;
|
||||
if (!neq(5, 6)) return 3;
|
||||
if (neq(5, 5)) return 4;
|
||||
if (!lt(4, 5)) return 5;
|
||||
if (lt(5, 5)) return 6;
|
||||
if (!lte(5, 5)) return 7;
|
||||
if (lte(6, 5)) return 8;
|
||||
if (!gt(6, 5)) return 9;
|
||||
if (gt(5, 5)) return 10;
|
||||
if (!gte(5, 5)) return 11;
|
||||
if (gte(4, 5)) return 12;
|
||||
if (!not_bool(false)) return 13;
|
||||
if (not_bool(true)) return 14;
|
||||
return 0; // Success
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fn eq(a: i32, b: i32) -> bool { return a == b; }
|
||||
fn neq(a: i32, b: i32) -> bool { return a != b; }
|
||||
fn lt(a: i32, b: i32) -> bool { return a < b; }
|
||||
fn lte(a: i32, b: i32) -> bool { return a <= b; }
|
||||
fn gt(a: i32, b: i32) -> bool { return a > b; }
|
||||
fn gte(a: i32, b: i32) -> bool { return a >= b; }
|
||||
fn not_bool(a: bool) -> bool { return !a; }
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
fn main() -> i32 {
|
||||
return 42;
|
||||
return 10 * 4 + 2;
|
||||
}
|
||||
Reference in New Issue
Block a user