@@ -1360,24 +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- } ) ;
1363+ let mut s = self . tokenize_number_part ( chars, |ch| ch. is_ascii_digit ( ) ) ?;
13741364
13751365 // match binary literal that starts with 0x
13761366 if s == "0" && chars. peek ( ) == Some ( & 'x' ) {
13771367 chars. next ( ) ;
1378- let s2 = peeking_next_take_while ( chars, |ch, next_ch| {
1379- ch. is_ascii_hexdigit ( ) || is_number_separator ( ch, next_ch)
1380- } ) ;
1368+ let s2 = self . tokenize_number_part ( chars, |ch| ch. is_ascii_hexdigit ( ) ) ?;
13811369 return Ok ( Some ( Token :: HexStringLiteral ( s2) ) ) ;
13821370 }
13831371
@@ -1399,9 +1387,7 @@ impl<'a> Tokenizer<'a> {
13991387 }
14001388
14011389 // Consume fractional digits.
1402- s += & peeking_next_take_while ( chars, |ch, next_ch| {
1403- ch. is_ascii_digit ( ) || is_number_separator ( ch, next_ch)
1404- } ) ;
1390+ s += & self . tokenize_number_part ( chars, |ch| ch. is_ascii_digit ( ) ) ?;
14051391
14061392 // No fraction -> Token::Period
14071393 if s == "." {
@@ -1425,12 +1411,16 @@ impl<'a> Tokenizer<'a> {
14251411
14261412 match char_clone. peek ( ) {
14271413 // Definitely an exponent, get original iterator up to speed and use it
1428- 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+ {
14291419 for _ in 0 ..exponent_part. len ( ) {
14301420 chars. next ( ) ;
14311421 }
14321422 exponent_part +=
1433- & peeking_take_while ( chars, |ch| ch. is_ascii_digit ( ) ) ;
1423+ & self . tokenize_number_part ( chars, |ch| ch. is_ascii_digit ( ) ) ? ;
14341424 s += exponent_part. as_str ( ) ;
14351425 }
14361426 // Not an exponent, discard the work done
@@ -2035,6 +2025,33 @@ impl<'a> Tokenizer<'a> {
20352025 } )
20362026 }
20372027
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+
20382055 // Consume characters until newline
20392056 fn tokenize_single_line_comment ( & self , chars : & mut State ) -> String {
20402057 peeking_take_while ( chars, |ch| match ch {
@@ -2411,24 +2428,6 @@ fn peeking_take_while(chars: &mut State, mut predicate: impl FnMut(char) -> bool
24112428 s
24122429}
24132430
2414- /// Same as peeking_take_while, but also passes the next character to the predicate.
2415- fn peeking_next_take_while (
2416- chars : & mut State ,
2417- mut predicate : impl FnMut ( char , Option < char > ) -> bool ,
2418- ) -> String {
2419- let mut s = String :: new ( ) ;
2420- while let Some ( & ch) = chars. peek ( ) {
2421- let next_char = chars. peekable . clone ( ) . nth ( 1 ) ;
2422- if predicate ( ch, next_char) {
2423- chars. next ( ) ; // consume
2424- s. push ( ch) ;
2425- } else {
2426- break ;
2427- }
2428- }
2429- s
2430- }
2431-
24322431fn unescape_single_quoted_string ( chars : & mut State < ' _ > ) -> Option < String > {
24332432 Unescape :: new ( chars) . unescape ( )
24342433}
@@ -2756,8 +2755,11 @@ mod tests {
27562755 ] ;
27572756 compare ( expected, tokens) ;
27582757
2759- all_dialects_where ( |dialect| dialect. supports_numeric_literal_underscores ( ) ) . tokenizes_to (
2760- "SELECT 10_000, _10_000, 10_00_, 10___0, 1_000.123, 1_000.123_456" ,
2758+ let numeric_underscore_dialects =
2759+ all_dialects_where ( |dialect| dialect. supports_numeric_literal_underscores ( ) ) ;
2760+
2761+ numeric_underscore_dialects. tokenizes_to (
2762+ "SELECT 10_000, _10_000, 1_000.123, 1_000.123_456, 1e1_0" ,
27612763 vec ! [
27622764 Token :: make_keyword( "SELECT" ) ,
27632765 Token :: Whitespace ( Whitespace :: Space ) ,
@@ -2767,20 +2769,38 @@ mod tests {
27672769 Token :: make_word( "_10_000" , None ) , // leading underscore tokenizes as a word (parsed as column identifier)
27682770 Token :: Comma ,
27692771 Token :: Whitespace ( Whitespace :: Space ) ,
2770- Token :: Number ( "10_00" . to_string( ) , false ) ,
2771- Token :: make_word( "_" , None ) , // trailing underscores tokenizes as a word (syntax error in some dialects)
2772- Token :: Comma ,
2773- Token :: Whitespace ( Whitespace :: Space ) ,
2774- Token :: Number ( "10" . to_string( ) , false ) ,
2775- Token :: make_word( "___0" , None ) , // multiple underscores tokenizes as a word (syntax error in some dialects)
2776- Token :: Comma ,
2777- Token :: Whitespace ( Whitespace :: Space ) ,
27782772 Token :: Number ( "1_000.123" . to_string( ) , false ) , // with decimal digits
27792773 Token :: Comma ,
27802774 Token :: Whitespace ( Whitespace :: Space ) ,
27812775 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
27822779 ] ,
27832780 ) ;
2781+
2782+ numeric_underscore_dialects. tokenizes_to (
2783+ "0xFF_FF" ,
2784+ vec ! [ Token :: HexStringLiteral ( "FF_FF" . to_string( ) ) ] ,
2785+ ) ;
2786+
2787+ for dialect in & numeric_underscore_dialects. dialects {
2788+ for sql in [
2789+ "SELECT 10_00_" ,
2790+ "SELECT 10___0" ,
2791+ "SELECT 1_000.123_" ,
2792+ "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_" ,
2799+ ] {
2800+ let err = Tokenizer :: new ( & * * dialect, sql) . tokenize ( ) . unwrap_err ( ) ;
2801+ assert_eq ! ( "Unexpected character '_'" , err. message) ;
2802+ }
2803+ }
27842804 }
27852805
27862806 #[ test]
0 commit comments