@@ -1360,36 +1360,12 @@ impl<'a> Tokenizer<'a> {
13601360 ) ;
13611361 }
13621362
1363- // Some dialects support underscore as number separator
1364- // There can only be one at a time and it must be followed by another digit
1365- let is_number_separator = |ch : char , next_char : Option < char > | {
1366- self . dialect . supports_numeric_literal_underscores ( )
1367- && ch == '_'
1368- && next_char. is_some_and ( |next_ch| next_ch. is_ascii_hexdigit ( ) )
1369- } ;
1370-
1371- let mut s = peeking_next_take_while ( chars, |ch, next_ch| {
1372- ch. is_ascii_digit ( ) || is_number_separator ( ch, next_ch)
1373- } ) ;
1374-
1375- if self . dialect . supports_numeric_literal_underscores ( )
1376- && chars. peek ( ) == Some ( & '_' )
1377- {
1378- return self . tokenizer_error ( chars. location ( ) , "Unexpected character '_'" ) ;
1379- }
1363+ let mut s = self . tokenize_number_part ( chars, |ch| ch. is_ascii_digit ( ) ) ?;
13801364
13811365 // match binary literal that starts with 0x
13821366 if s == "0" && chars. peek ( ) == Some ( & 'x' ) {
13831367 chars. next ( ) ;
1384- let s2 = peeking_next_take_while ( chars, |ch, next_ch| {
1385- ch. is_ascii_hexdigit ( ) || is_number_separator ( ch, next_ch)
1386- } ) ;
1387- if self . dialect . supports_numeric_literal_underscores ( )
1388- && chars. peek ( ) == Some ( & '_' )
1389- {
1390- return self
1391- . tokenizer_error ( chars. location ( ) , "Unexpected character '_'" ) ;
1392- }
1368+ let s2 = self . tokenize_number_part ( chars, |ch| ch. is_ascii_hexdigit ( ) ) ?;
13931369 return Ok ( Some ( Token :: HexStringLiteral ( s2) ) ) ;
13941370 }
13951371
@@ -1410,23 +1386,8 @@ impl<'a> Tokenizer<'a> {
14101386 }
14111387 }
14121388
1413- if s != "."
1414- && self . dialect . supports_numeric_literal_underscores ( )
1415- && chars. peek ( ) == Some ( & '_' )
1416- {
1417- return self . tokenizer_error ( chars. location ( ) , "Unexpected character '_'" ) ;
1418- }
1419-
14201389 // Consume fractional digits.
1421- s += & peeking_next_take_while ( chars, |ch, next_ch| {
1422- ch. is_ascii_digit ( ) || is_number_separator ( ch, next_ch)
1423- } ) ;
1424-
1425- if self . dialect . supports_numeric_literal_underscores ( )
1426- && chars. peek ( ) == Some ( & '_' )
1427- {
1428- return self . tokenizer_error ( chars. location ( ) , "Unexpected character '_'" ) ;
1429- }
1390+ s += & self . tokenize_number_part ( chars, |ch| ch. is_ascii_digit ( ) ) ?;
14301391
14311392 // No fraction -> Token::Period
14321393 if s == "." {
@@ -1450,12 +1411,16 @@ impl<'a> Tokenizer<'a> {
14501411
14511412 match char_clone. peek ( ) {
14521413 // Definitely an exponent, get original iterator up to speed and use it
1453- Some ( & c) if c. is_ascii_digit ( ) => {
1414+ Some ( & c)
1415+ if c. is_ascii_digit ( )
1416+ || ( c == '_'
1417+ && self . dialect . supports_numeric_literal_underscores ( ) ) =>
1418+ {
14541419 for _ in 0 ..exponent_part. len ( ) {
14551420 chars. next ( ) ;
14561421 }
14571422 exponent_part +=
1458- & peeking_take_while ( chars, |ch| ch. is_ascii_digit ( ) ) ;
1423+ & self . tokenize_number_part ( chars, |ch| ch. is_ascii_digit ( ) ) ? ;
14591424 s += exponent_part. as_str ( ) ;
14601425 }
14611426 // Not an exponent, discard the work done
@@ -2060,6 +2025,33 @@ impl<'a> Tokenizer<'a> {
20602025 } )
20612026 }
20622027
2028+ fn tokenize_number_part (
2029+ & self ,
2030+ chars : & mut State ,
2031+ is_digit : impl Fn ( char ) -> bool ,
2032+ ) -> Result < String , TokenizerError > {
2033+ let supports_separator = self . dialect . supports_numeric_literal_underscores ( ) ;
2034+ let mut s = String :: new ( ) ;
2035+
2036+ while let Some ( & ch) = chars. peek ( ) {
2037+ if is_digit ( ch) {
2038+ chars. next ( ) ;
2039+ s. push ( ch) ;
2040+ } else if supports_separator && ch == '_' {
2041+ let next_char = chars. peekable . clone ( ) . nth ( 1 ) ;
2042+ if s. is_empty ( ) || !next_char. is_some_and ( & is_digit) {
2043+ return self . tokenizer_error ( chars. location ( ) , "Unexpected character '_'" ) ;
2044+ }
2045+ chars. next ( ) ;
2046+ s. push ( ch) ;
2047+ } else {
2048+ break ;
2049+ }
2050+ }
2051+
2052+ Ok ( s)
2053+ }
2054+
20632055 // Consume characters until newline
20642056 fn tokenize_single_line_comment ( & self , chars : & mut State ) -> String {
20652057 peeking_take_while ( chars, |ch| match ch {
@@ -2436,24 +2428,6 @@ fn peeking_take_while(chars: &mut State, mut predicate: impl FnMut(char) -> bool
24362428 s
24372429}
24382430
2439- /// Same as peeking_take_while, but also passes the next character to the predicate.
2440- fn peeking_next_take_while (
2441- chars : & mut State ,
2442- mut predicate : impl FnMut ( char , Option < char > ) -> bool ,
2443- ) -> String {
2444- let mut s = String :: new ( ) ;
2445- while let Some ( & ch) = chars. peek ( ) {
2446- let next_char = chars. peekable . clone ( ) . nth ( 1 ) ;
2447- if predicate ( ch, next_char) {
2448- chars. next ( ) ; // consume
2449- s. push ( ch) ;
2450- } else {
2451- break ;
2452- }
2453- }
2454- s
2455- }
2456-
24572431fn unescape_single_quoted_string ( chars : & mut State < ' _ > ) -> Option < String > {
24582432 Unescape :: new ( chars) . unescape ( )
24592433}
@@ -2785,7 +2759,7 @@ mod tests {
27852759 all_dialects_where ( |dialect| dialect. supports_numeric_literal_underscores ( ) ) ;
27862760
27872761 numeric_underscore_dialects. tokenizes_to (
2788- "SELECT 10_000, _10_000, 1_000.123, 1_000.123_456" ,
2762+ "SELECT 10_000, _10_000, 1_000.123, 1_000.123_456, 1e1_0 " ,
27892763 vec ! [
27902764 Token :: make_keyword( "SELECT" ) ,
27912765 Token :: Whitespace ( Whitespace :: Space ) ,
@@ -2799,15 +2773,29 @@ mod tests {
27992773 Token :: Comma ,
28002774 Token :: Whitespace ( Whitespace :: Space ) ,
28012775 Token :: Number ( "1_000.123_456" . to_string( ) , false ) , // with an underscore in the decimal digits
2776+ Token :: Comma ,
2777+ Token :: Whitespace ( Whitespace :: Space ) ,
2778+ Token :: Number ( "1e1_0" . to_string( ) , false ) , // with an underscore in the exponent
28022779 ] ,
28032780 ) ;
28042781
2782+ numeric_underscore_dialects. tokenizes_to (
2783+ "0xFF_FF" ,
2784+ vec ! [ Token :: HexStringLiteral ( "FF_FF" . to_string( ) ) ] ,
2785+ ) ;
2786+
28052787 for dialect in & numeric_underscore_dialects. dialects {
28062788 for sql in [
28072789 "SELECT 10_00_" ,
28082790 "SELECT 10___0" ,
28092791 "SELECT 1_000.123_" ,
28102792 "SELECT 1._000" ,
2793+ "SELECT 1_a" ,
2794+ "SELECT 1e_1" ,
2795+ "SELECT 1e1_" ,
2796+ "SELECT 1e1__0" ,
2797+ "SELECT 0x_1" ,
2798+ "SELECT 0x1_" ,
28112799 ] {
28122800 let err = Tokenizer :: new ( & * * dialect, sql) . tokenize ( ) . unwrap_err ( ) ;
28132801 assert_eq ! ( "Unexpected character '_'" , err. message) ;
0 commit comments