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
1 change: 1 addition & 0 deletions src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ pub enum BinaryOp {
Subtract,
Multiply,
Divide,
Modulo,
Equal,
NotEqual,
LessThan,
Expand Down
3 changes: 3 additions & 0 deletions src/codegen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,9 @@ fn emit_expr<'ctx>(
BinaryOp::Divide => builder
.build_int_signed_div(left, right, "divtmp")
.map_err(|err| format!("failed to emit div instruction: {err}"))?,
BinaryOp::Modulo => builder
.build_int_signed_rem(left, right, "remtmp")
.map_err(|err| format!("failed to emit rem instruction: {err}"))?,
BinaryOp::Equal
| BinaryOp::NotEqual
| BinaryOp::LessThan
Expand Down
93 changes: 91 additions & 2 deletions src/lexer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub enum Token {
Minus,
Star,
Slash,
Percent,
LeftParen,
RightParen,
LeftBrace,
Expand Down Expand Up @@ -57,7 +58,36 @@ pub fn tokenize(source: &str) -> Result<Vec<Token>, String> {
}
'/' => {
chars.next();
tokens.push(Token::Slash);
match chars.peek() {
Some(&'/') => {
// Line comment: skip until end of line.
for next in chars.by_ref() {
if next == '\n' {
break;
}
}
}
Some(&'*') => {
chars.next();
// Block comment: skip until the closing `*/`.
let mut terminated = false;
while let Some(next) = chars.next() {
if next == '*' && chars.peek() == Some(&'/') {
chars.next();
terminated = true;
break;
}
}
if !terminated {
return Err("unterminated block comment".to_string());
}
}
_ => tokens.push(Token::Slash),
}
}
'%' => {
chars.next();
tokens.push(Token::Percent);
}
'(' => {
chars.next();
Expand Down Expand Up @@ -187,7 +217,7 @@ pub fn tokenize(source: &str) -> Result<Vec<Token>, String> {
}

// TODO: Track line and column information for better lexer errors.
// TODO: Support comments and richer punctuation.
// TODO: Support richer punctuation (compound assignment, increment/decrement).

#[cfg(test)]
mod tests {
Expand Down Expand Up @@ -290,6 +320,65 @@ mod tests {
);
}

#[test]
fn skips_line_comments() {
let source = "int main() { // this is a comment\n return 5; // trailing\n}";
let tokens = tokenize(source).expect("tokenization should succeed");

assert_eq!(
tokens,
vec![
Token::Int,
Token::Identifier("main".to_string()),
Token::LeftParen,
Token::RightParen,
Token::LeftBrace,
Token::Return,
Token::Integer(5),
Token::Semicolon,
Token::RightBrace,
]
);
}

#[test]
fn skips_block_comments() {
let source = "int main() { return /* the answer, \n spanning lines */ 5; }";
let tokens = tokenize(source).expect("tokenization should succeed");

assert_eq!(
tokens,
vec![
Token::Int,
Token::Identifier("main".to_string()),
Token::LeftParen,
Token::RightParen,
Token::LeftBrace,
Token::Return,
Token::Integer(5),
Token::Semicolon,
Token::RightBrace,
]
);
}

#[test]
fn rejects_unterminated_block_comment() {
let error = tokenize("int main() { /* never closed").expect_err("should fail");

assert!(error.contains("unterminated block comment"));
}

#[test]
fn tokenizes_percent_operator() {
let tokens = tokenize("7 % 3").expect("tokenization should succeed");

assert_eq!(
tokens,
vec![Token::Integer(7), Token::Percent, Token::Integer(3)]
);
}

#[test]
fn tokenizes_relational_and_logical_operators() {
let source = "== != < <= > >= && || ! &";
Expand Down
1 change: 1 addition & 0 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,7 @@ impl<'a> Parser<'a> {
let operator = match self.peek() {
Some(Token::Star) => BinaryOp::Multiply,
Some(Token::Slash) => BinaryOp::Divide,
Some(Token::Percent) => BinaryOp::Modulo,
_ => break,
};
self.next();
Expand Down
19 changes: 19 additions & 0 deletions tests/compiler_e2e.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,25 @@ fn evaluates_division_and_subtraction() {
assert_program_exit_code("int main() { return 20 / 5 - 1; }\n", 3);
}

#[test]
fn evaluates_modulo() {
assert_program_exit_code("int main() { return 17 % 5; }\n", 2);
}

#[test]
fn modulo_shares_multiplicative_precedence() {
// 10 % 4 * 2 groups left-to-right: (10 % 4) * 2 = 4, plus 1 = 5
assert_program_exit_code("int main() { return 1 + 10 % 4 * 2; }\n", 5);
}

#[test]
fn ignores_comments() {
assert_program_exit_code(
"// leading comment\nint main() {\n int x = 6; // set x\n /* block\n comment */\n return x % 4;\n}\n",
2,
);
}

#[test]
fn evaluates_unary_negation() {
// -5 modulo 256 is 251
Expand Down
Loading