Skip to content

Commit

Permalink
feat: allow underscores in integer literals
Browse files Browse the repository at this point in the history
  • Loading branch information
hayleykwan committed Dec 9, 2023
1 parent 3cf1f92 commit 7862d79
Showing 1 changed file with 41 additions and 0 deletions.
41 changes: 41 additions & 0 deletions compiler/noirc_frontend/src/lexer/lexer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -324,10 +324,29 @@ impl<'a> Lexer<'a> {

let integer_str = self.eat_while(Some(initial_char), |ch| {
ch.is_ascii_digit() | ch.is_ascii_hexdigit() | (ch == 'x')
ch.is_ascii_digit() | ch.is_ascii_hexdigit() | (ch == 'x') | (ch == '_')
});

let end = self.position;

// We want to enforce some simple rules about usage of underscores:
// 1. Underscores cannot appear at the end of a integer literal. e.g. 0x123_.
// 2. There cannot be more than one underscore consecutively, e.g. 0x5__5, 5__5.
//
// We're not concerned with an underscore at the beginning of a decimal literal
// such as `_5` as this would be lexed into an ident rather than an integer literal.
let invalid_underscore_location = integer_str.ends_with('_');
let consecutive_underscores = integer_str.contains("__");
if invalid_underscore_location || consecutive_underscores {
return Err(LexerErrorKind::InvalidIntegerLiteral {
span: Span::inclusive(start, end),
found: integer_str,
});
}

// Underscores needs to be stripped out before the literal can be converted to a `FieldElement.
let integer_str = integer_str.replace('_', "");

let integer = match FieldElement::try_from_str(&integer_str) {
None => {
return Err(LexerErrorKind::InvalidIntegerLiteral {
Expand Down Expand Up @@ -935,10 +954,32 @@ mod tests {

let expected = vec![Token::Int(5_i128.into())];
let mut lexer = Lexer::new(input);
let test_cases: Vec<(&str, Token)> = vec![
("0x05", Token::Int(5_i128.into())),
("0x1234_5678", Token::Int(0x1234_5678_u128.into())),
("0x_01", Token::Int(0x1_u128.into())),
];

for token in expected.into_iter() {
for (input, expected_token) in test_cases {
let mut lexer = Lexer::new(input);
let got = lexer.next_token().unwrap();
assert_eq!(got, token);
assert_eq!(got.token(), &expected_token);
}
}

#[test]
fn test_reject_invalid_underscores_in_integer_literal() {
let test_cases: Vec<&str> = vec!["0x05_", "5_", "5__5", "0x5__5"];

for input in test_cases {
let mut lexer = Lexer::new(input);
let token = lexer.next_token();
assert!(
matches!(token, Err(LexerErrorKind::InvalidIntegerLiteral { .. })),
"expected {input} to throw error"
);
}
}

Expand Down

0 comments on commit 7862d79

Please sign in to comment.