Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ pub enum UnaryOp {
Negate,
Posate,
LogicalNot,
BitNot,
Deref,
AddrOf,
}
Expand All @@ -148,6 +149,11 @@ pub enum BinaryOp {
GreaterEqual,
LogicalAnd,
LogicalOr,
BitAnd,
BitOr,
BitXor,
ShiftLeft,
ShiftRight,
}

// TODO: Add source spans to AST nodes for richer diagnostics.
Expand Down
25 changes: 23 additions & 2 deletions src/codegen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,9 @@ fn type_of_expr(
found.ok_or_else(|| format!("undefined variable '{}'", var.name))
}
Expr::Unary(unary) => match unary.operator {
UnaryOp::Negate | UnaryOp::Posate | UnaryOp::LogicalNot => Ok(Type::Int),
UnaryOp::Negate | UnaryOp::Posate | UnaryOp::LogicalNot | UnaryOp::BitNot => {
Ok(Type::Int)
}
UnaryOp::Deref => {
let inner_ty = type_of_expr(&unary.expr, variables, function_types)?;
match inner_ty {
Expand Down Expand Up @@ -313,7 +315,7 @@ fn emit_expr<'ctx>(
)?;
Ok(BasicValueEnum::PointerValue(ptr))
}
UnaryOp::Negate | UnaryOp::Posate | UnaryOp::LogicalNot => {
UnaryOp::Negate | UnaryOp::Posate | UnaryOp::LogicalNot | UnaryOp::BitNot => {
let operand = emit_expr(
context,
builder,
Expand Down Expand Up @@ -342,6 +344,9 @@ fn emit_expr<'ctx>(
.build_int_z_extend(cmp, context.i32_type(), "casttmp")
.map_err(|err| format!("failed to emit zext for logical not: {err}"))?
}
UnaryOp::BitNot => builder
.build_not(operand_int, "bnottmp")
.map_err(|err| format!("failed to emit not instruction: {err}"))?,
_ => unreachable!(),
};
Ok(BasicValueEnum::IntValue(res))
Expand Down Expand Up @@ -549,6 +554,22 @@ fn emit_expr<'ctx>(
BinaryOp::Modulo => builder
.build_int_signed_rem(left, right, "remtmp")
.map_err(|err| format!("failed to emit rem instruction: {err}"))?,
BinaryOp::BitAnd => builder
.build_and(left, right, "andtmp")
.map_err(|err| format!("failed to emit and instruction: {err}"))?,
BinaryOp::BitOr => builder
.build_or(left, right, "ortmp")
.map_err(|err| format!("failed to emit or instruction: {err}"))?,
BinaryOp::BitXor => builder
.build_xor(left, right, "xortmp")
.map_err(|err| format!("failed to emit xor instruction: {err}"))?,
BinaryOp::ShiftLeft => builder
.build_left_shift(left, right, "shltmp")
.map_err(|err| format!("failed to emit shl instruction: {err}"))?,
BinaryOp::ShiftRight => builder
// Arithmetic shift: C's >> on signed ints preserves the sign bit
.build_right_shift(left, right, true, "ashrtmp")
.map_err(|err| format!("failed to emit ashr instruction: {err}"))?,
BinaryOp::Equal
| BinaryOp::NotEqual
| BinaryOp::LessThan
Expand Down
65 changes: 54 additions & 11 deletions src/lexer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ pub enum Token {
OrOr,
Exclamation,
Ampersand,
Pipe,
Caret,
Tilde,
LessLess,
GreaterGreater,
Comma,
Question,
Colon,
Expand Down Expand Up @@ -186,20 +191,30 @@ pub fn tokenize(source: &str) -> Result<Vec<Token>, String> {
}
'<' => {
chars.next();
if chars.peek() == Some(&'=') {
chars.next();
tokens.push(Token::LessEqual);
} else {
tokens.push(Token::LessThan);
match chars.peek() {
Some(&'=') => {
chars.next();
tokens.push(Token::LessEqual);
}
Some(&'<') => {
chars.next();
tokens.push(Token::LessLess);
}
_ => tokens.push(Token::LessThan),
}
}
'>' => {
chars.next();
if chars.peek() == Some(&'=') {
chars.next();
tokens.push(Token::GreaterEqual);
} else {
tokens.push(Token::GreaterThan);
match chars.peek() {
Some(&'=') => {
chars.next();
tokens.push(Token::GreaterEqual);
}
Some(&'>') => {
chars.next();
tokens.push(Token::GreaterGreater);
}
_ => tokens.push(Token::GreaterThan),
}
}
'&' => {
Expand All @@ -217,9 +232,17 @@ pub fn tokenize(source: &str) -> Result<Vec<Token>, String> {
chars.next();
tokens.push(Token::OrOr);
} else {
return Err("expected '|' after '|'".to_string());
tokens.push(Token::Pipe);
}
}
'^' => {
chars.next();
tokens.push(Token::Caret);
}
'~' => {
chars.next();
tokens.push(Token::Tilde);
}
'0'..='9' => {
let mut number = String::new();

Expand Down Expand Up @@ -502,4 +525,24 @@ mod tests {
]
);
}

#[test]
fn tokenizes_bitwise_operators() {
let source = "| ^ ~ << >> < < > >";
let tokens = tokenize(source).expect("should succeed");
assert_eq!(
tokens,
vec![
Token::Pipe,
Token::Caret,
Token::Tilde,
Token::LessLess,
Token::GreaterGreater,
Token::LessThan,
Token::LessThan,
Token::GreaterThan,
Token::GreaterThan,
]
);
}
}
161 changes: 158 additions & 3 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -334,12 +334,12 @@ impl<'a> Parser<'a> {
}

fn parse_logical_and(&mut self) -> Result<Expr, ParseError> {
let mut expr = self.parse_equality()?;
let mut expr = self.parse_bit_or()?;

loop {
if self.peek() == Some(&Token::AndAnd) {
self.next();
let right = self.parse_equality()?;
let right = self.parse_bit_or()?;
expr = Expr::Binary(BinaryExpr {
left: Box::new(expr),
operator: BinaryOp::LogicalAnd,
Expand All @@ -353,6 +353,54 @@ impl<'a> Parser<'a> {
Ok(expr)
}

fn parse_bit_or(&mut self) -> Result<Expr, ParseError> {
let mut expr = self.parse_bit_xor()?;

while self.peek() == Some(&Token::Pipe) {
self.next();
let right = self.parse_bit_xor()?;
expr = Expr::Binary(BinaryExpr {
left: Box::new(expr),
operator: BinaryOp::BitOr,
right: Box::new(right),
});
}

Ok(expr)
}

fn parse_bit_xor(&mut self) -> Result<Expr, ParseError> {
let mut expr = self.parse_bit_and()?;

while self.peek() == Some(&Token::Caret) {
self.next();
let right = self.parse_bit_and()?;
expr = Expr::Binary(BinaryExpr {
left: Box::new(expr),
operator: BinaryOp::BitXor,
right: Box::new(right),
});
}

Ok(expr)
}

fn parse_bit_and(&mut self) -> Result<Expr, ParseError> {
let mut expr = self.parse_equality()?;

while self.peek() == Some(&Token::Ampersand) {
self.next();
let right = self.parse_equality()?;
expr = Expr::Binary(BinaryExpr {
left: Box::new(expr),
operator: BinaryOp::BitAnd,
right: Box::new(right),
});
}

Ok(expr)
}

fn parse_equality(&mut self) -> Result<Expr, ParseError> {
let mut expr = self.parse_relational()?;

Expand All @@ -376,7 +424,7 @@ impl<'a> Parser<'a> {
}

fn parse_relational(&mut self) -> Result<Expr, ParseError> {
let mut expr = self.parse_additive()?;
let mut expr = self.parse_shift()?;

loop {
let operator = match self.peek() {
Expand All @@ -388,6 +436,28 @@ impl<'a> Parser<'a> {
};
self.next();

let right = self.parse_shift()?;
expr = Expr::Binary(BinaryExpr {
left: Box::new(expr),
operator,
right: Box::new(right),
});
}

Ok(expr)
}

fn parse_shift(&mut self) -> Result<Expr, ParseError> {
let mut expr = self.parse_additive()?;

loop {
let operator = match self.peek() {
Some(Token::LessLess) => BinaryOp::ShiftLeft,
Some(Token::GreaterGreater) => BinaryOp::ShiftRight,
_ => break,
};
self.next();

let right = self.parse_additive()?;
expr = Expr::Binary(BinaryExpr {
left: Box::new(expr),
Expand Down Expand Up @@ -470,6 +540,14 @@ impl<'a> Parser<'a> {
expr: Box::new(expr),
}))
}
Some(Token::Tilde) => {
self.next();
let expr = self.parse_unary()?;
Ok(Expr::Unary(UnaryExpr {
operator: UnaryOp::BitNot,
expr: Box::new(expr),
}))
}
Some(Token::Star) => {
self.next();
let expr = self.parse_unary()?;
Expand Down Expand Up @@ -982,6 +1060,83 @@ mod tests {
assert_eq!(error, ParseError::InvalidIncrementTarget);
}

#[test]
fn bitwise_operators_follow_c_precedence() {
// `1 & 2 == 2` must parse as `1 & (2 == 2)` — equality binds tighter
// than `&` in C.
let tokens = vec![
Token::Int,
Token::Identifier("main".to_string()),
Token::LeftParen,
Token::RightParen,
Token::LeftBrace,
Token::Return,
Token::Integer(1),
Token::Ampersand,
Token::Integer(2),
Token::EqualEqual,
Token::Integer(2),
Token::Semicolon,
Token::RightBrace,
];

let program = parse(&tokens).expect("parser should accept bitwise expression");

assert_eq!(
program.functions[0].body[0],
Statement::Return(ReturnStatement {
expr: Expr::Binary(BinaryExpr {
left: Box::new(Expr::IntegerLiteral(IntegerLiteral { value: 1 })),
operator: BinaryOp::BitAnd,
right: Box::new(Expr::Binary(BinaryExpr {
left: Box::new(Expr::IntegerLiteral(IntegerLiteral { value: 2 })),
operator: BinaryOp::Equal,
right: Box::new(Expr::IntegerLiteral(IntegerLiteral { value: 2 })),
})),
}),
})
);
}

#[test]
fn parses_bitand_of_address_of() {
// `x & &y` must lex as two Ampersands and parse as BitAnd(x, AddrOf(y)).
let tokens = vec![
Token::Int,
Token::Identifier("main".to_string()),
Token::LeftParen,
Token::RightParen,
Token::LeftBrace,
Token::Return,
Token::Identifier("x".to_string()),
Token::Ampersand,
Token::Ampersand,
Token::Identifier("y".to_string()),
Token::Semicolon,
Token::RightBrace,
];

let program = parse(&tokens).expect("parser should accept `x & &y`");

assert_eq!(
program.functions[0].body[0],
Statement::Return(ReturnStatement {
expr: Expr::Binary(BinaryExpr {
left: Box::new(Expr::Variable(VariableExpr {
name: "x".to_string(),
})),
operator: BinaryOp::BitAnd,
right: Box::new(Expr::Unary(UnaryExpr {
operator: UnaryOp::AddrOf,
expr: Box::new(Expr::Variable(VariableExpr {
name: "y".to_string(),
})),
})),
}),
})
);
}

#[test]
fn parses_ternary_conditional() {
let tokens = vec![
Expand Down
Loading
Loading