Skip to content

Commit 2c06a64

Browse files
hayleykwanTomAFrenchkevaundray
authored
feat: allow underscores in integer literals (#3746)
# Description ## Problem\* Resolves #3283 ## Summary\* This PR adds support for underscores in numeric literals. This is restricted to not allow multiple underscores in a row and a literal cannot start or end with an underscore. ## Additional Context ## Documentation\* Check one: - [ ] No documentation needed. - [ ] Documentation included in this PR. - [ ] **[Exceptional Case]** Documentation to be submitted in a separate PR. # PR Checklist\* - [x] I have tested the changes locally. - [x] I have formatted the changes with [Prettier](https://prettier.io/) and/or `cargo fmt` on default settings. --------- Co-authored-by: Tom French <15848336+TomAFrench@users.noreply.github.com> Co-authored-by: kevaundray <kevtheappdev@gmail.com>
1 parent 4000fb2 commit 2c06a64

1 file changed

Lines changed: 44 additions & 8 deletions

File tree

  • compiler/noirc_frontend/src/lexer

compiler/noirc_frontend/src/lexer/lexer.rs

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -323,11 +323,29 @@ impl<'a> Lexer<'a> {
323323
let start = self.position;
324324

325325
let integer_str = self.eat_while(Some(initial_char), |ch| {
326-
ch.is_ascii_digit() | ch.is_ascii_hexdigit() | (ch == 'x')
326+
ch.is_ascii_digit() | ch.is_ascii_hexdigit() | (ch == 'x') | (ch == '_')
327327
});
328328

329329
let end = self.position;
330330

331+
// We want to enforce some simple rules about usage of underscores:
332+
// 1. Underscores cannot appear at the end of a integer literal. e.g. 0x123_.
333+
// 2. There cannot be more than one underscore consecutively, e.g. 0x5__5, 5__5.
334+
//
335+
// We're not concerned with an underscore at the beginning of a decimal literal
336+
// such as `_5` as this would be lexed into an ident rather than an integer literal.
337+
let invalid_underscore_location = integer_str.ends_with('_');
338+
let consecutive_underscores = integer_str.contains("__");
339+
if invalid_underscore_location || consecutive_underscores {
340+
return Err(LexerErrorKind::InvalidIntegerLiteral {
341+
span: Span::inclusive(start, end),
342+
found: integer_str,
343+
});
344+
}
345+
346+
// Underscores needs to be stripped out before the literal can be converted to a `FieldElement.
347+
let integer_str = integer_str.replace('_', "");
348+
331349
let integer = match FieldElement::try_from_str(&integer_str) {
332350
None => {
333351
return Err(LexerErrorKind::InvalidIntegerLiteral {
@@ -930,15 +948,33 @@ mod tests {
930948
}
931949

932950
#[test]
933-
fn test_eat_hex_int() {
934-
let input = "0x05";
935-
936-
let expected = vec![Token::Int(5_i128.into())];
937-
let mut lexer = Lexer::new(input);
951+
fn test_eat_integer_literals() {
952+
let test_cases: Vec<(&str, Token)> = vec![
953+
("0x05", Token::Int(5_i128.into())),
954+
("5", Token::Int(5_i128.into())),
955+
("0x1234_5678", Token::Int(0x1234_5678_u128.into())),
956+
("0x_01", Token::Int(0x1_u128.into())),
957+
("1_000_000", Token::Int(1_000_000_u128.into())),
958+
];
938959

939-
for token in expected.into_iter() {
960+
for (input, expected_token) in test_cases {
961+
let mut lexer = Lexer::new(input);
940962
let got = lexer.next_token().unwrap();
941-
assert_eq!(got, token);
963+
assert_eq!(got.token(), &expected_token);
964+
}
965+
}
966+
967+
#[test]
968+
fn test_reject_invalid_underscores_in_integer_literal() {
969+
let test_cases: Vec<&str> = vec!["0x05_", "5_", "5__5", "0x5__5"];
970+
971+
for input in test_cases {
972+
let mut lexer = Lexer::new(input);
973+
let token = lexer.next_token();
974+
assert!(
975+
matches!(token, Err(LexerErrorKind::InvalidIntegerLiteral { .. })),
976+
"expected {input} to throw error"
977+
);
942978
}
943979
}
944980

0 commit comments

Comments
 (0)