@@ -1606,6 +1606,20 @@ impl<'a> Tokenizer<'a> {
16061606 return Ok ( Some ( Token :: Number ( s, false ) ) ) ;
16071607 }
16081608
1609+ if s == "0" && self . dialect . supports_non_decimal_integer_literals ( ) {
1610+ let radix = match chars. peek ( ) {
1611+ Some ( 'x' | 'X' ) => Some ( ( 16 , "a hexadecimal" ) ) ,
1612+ Some ( 'o' | 'O' ) => Some ( ( 8 , "an octal" ) ) ,
1613+ Some ( 'b' | 'B' ) => Some ( ( 2 , "a binary" ) ) ,
1614+ _ => None ,
1615+ } ;
1616+ if let Some ( ( radix, name) ) = radix {
1617+ return self
1618+ . tokenize_non_decimal_integer ( chars, radix, name)
1619+ . map ( Some ) ;
1620+ }
1621+ }
1622+
16091623 // match binary literal that starts with 0x
16101624 if s == "0" && chars. peek ( ) == Some ( & 'x' ) {
16111625 chars. next ( ) ;
@@ -2302,6 +2316,32 @@ impl<'a> Tokenizer<'a> {
23022316 Ok ( s)
23032317 }
23042318
2319+ /// Tokenizes an integer literal such as `0x1F` after its leading `0`,
2320+ /// keeping the spelling as written.
2321+ fn tokenize_non_decimal_integer (
2322+ & self ,
2323+ chars : & mut State ,
2324+ radix : u32 ,
2325+ name : & str ,
2326+ ) -> Result < Token , TokenizerError > {
2327+ let mut s = String :: from ( '0' ) ;
2328+ s. extend ( chars. next ( ) ) ;
2329+ // An underscore may directly follow the prefix, as in `0x_1F`.
2330+ if chars. peek ( ) == Some ( & '_' ) && self . dialect . supports_numeric_literal_underscores ( ) {
2331+ chars. next ( ) ;
2332+ s. push ( '_' ) ;
2333+ }
2334+ let digits = self . tokenize_number_part ( chars, |ch| ch. is_digit ( radix) ) ?;
2335+ if digits. is_empty ( ) {
2336+ return self . tokenizer_error (
2337+ chars. location ( ) ,
2338+ format ! ( "Expected {name} digit after '{}'" , & s[ ..2 ] ) ,
2339+ ) ;
2340+ }
2341+ s. push_str ( & digits) ;
2342+ Ok ( Token :: Number ( s, false ) )
2343+ }
2344+
23052345 // Consume characters until newline
23062346 fn tokenize_single_line_comment ( & self , chars : & mut State ) -> String {
23072347 peeking_take_while ( chars, |ch| match ch {
@@ -3035,10 +3075,20 @@ mod tests {
30353075 ] ,
30363076 ) ;
30373077
3038- numeric_underscore_dialects. tokenizes_to (
3078+ let hex_string_dialects = all_dialects_where ( |dialect| {
3079+ dialect. supports_numeric_literal_underscores ( )
3080+ && !dialect. supports_non_decimal_integer_literals ( )
3081+ } ) ;
3082+ hex_string_dialects. tokenizes_to (
30393083 "0xFF_FF" ,
30403084 vec ! [ Token :: HexStringLiteral ( "FF_FF" . to_string( ) ) ] ,
30413085 ) ;
3086+ for dialect in & hex_string_dialects. dialects {
3087+ let err = Tokenizer :: new ( & * * dialect, "SELECT 0x_1" )
3088+ . tokenize ( )
3089+ . unwrap_err ( ) ;
3090+ assert_eq ! ( "Unexpected character '_'" , err. message) ;
3091+ }
30423092
30433093 for dialect in & numeric_underscore_dialects. dialects {
30443094 for sql in [
@@ -3050,7 +3100,6 @@ mod tests {
30503100 "SELECT 1e_1" ,
30513101 "SELECT 1e1_" ,
30523102 "SELECT 1e1__0" ,
3053- "SELECT 0x_1" ,
30543103 "SELECT 0x1_" ,
30553104 ] {
30563105 let err = Tokenizer :: new ( & * * dialect, sql) . tokenize ( ) . unwrap_err ( ) ;
0 commit comments