@@ -450,18 +450,15 @@ pub(crate) fn parse<'a>(
450450 } else {
451451 ( Base :: Decimal , unsigned)
452452 } ;
453- if rest. is_empty ( ) {
454- return Err ( ExtendedParserError :: NotNumeric ) ;
455- }
456453
457454 // Parse the integral part of the number
458455 let mut chars = rest. chars ( ) . enumerate ( ) . fuse ( ) . peekable ( ) ;
459- let mut digits = BigUint :: zero ( ) ;
456+ let mut digits: Option < BigUint > = None ;
460457 let mut scale = 0u64 ;
461- let mut exponent = BigInt :: zero ( ) ;
458+ let mut exponent: Option < BigInt > = None ;
462459 while let Some ( d) = chars. peek ( ) . and_then ( |& ( _, c) | base. digit ( c) ) {
463460 chars. next ( ) ;
464- digits = digits * base as u8 + d;
461+ digits = Some ( digits. unwrap_or_default ( ) * base as u8 + d) ;
465462 }
466463
467464 // Parse fractional/exponent part of the number for supported bases.
@@ -472,7 +469,7 @@ pub(crate) fn parse<'a>(
472469 chars. next ( ) ;
473470 while let Some ( d) = chars. peek ( ) . and_then ( |& ( _, c) | base. digit ( c) ) {
474471 chars. next ( ) ;
475- ( digits, scale) = ( digits * base as u8 + d, scale + 1 ) ;
472+ ( digits, scale) = ( Some ( digits. unwrap_or_default ( ) * base as u8 + d) , scale + 1 ) ;
476473 }
477474 }
478475
@@ -487,6 +484,8 @@ pub(crate) fn parse<'a>(
487484 . peek ( )
488485 . is_some_and ( |& ( _, c) | c. to_ascii_lowercase ( ) == exp_char)
489486 {
487+ // Save the iterator position in case we do not parse any exponent.
488+ let save_chars = chars. clone ( ) ;
490489 chars. next ( ) ;
491490 let exp_negative = match chars. peek ( ) {
492491 Some ( ( _, '-' ) ) => {
@@ -501,23 +500,40 @@ pub(crate) fn parse<'a>(
501500 } ;
502501 while let Some ( d) = chars. peek ( ) . and_then ( |& ( _, c) | Base :: Decimal . digit ( c) ) {
503502 chars. next ( ) ;
504- exponent = exponent * 10 + d as i64 ;
503+ exponent = Some ( exponent. unwrap_or_default ( ) * 10 + d as i64 ) ;
505504 }
506- if exp_negative {
507- exponent = -exponent;
505+ if let Some ( exp) = & exponent {
506+ if exp_negative {
507+ exponent = Some ( -exp) ;
508+ }
509+ } else {
510+ // No exponent actually parsed, reset iterator to return partial match.
511+ chars = save_chars;
508512 }
509513 }
510514 }
511515
512- // If nothing has been parsed, check if this is a special value, or declare the parsing unsuccessful
513- if let Some ( ( 0 , _) ) = chars. peek ( ) {
516+ // If no digit has been parsed, check if this is a special value, or declare the parsing unsuccessful
517+ if digits. is_none ( ) {
518+ // If we trimmed an initial `0x`/`0b`, return a partial match.
519+ if rest != unsigned {
520+ let ebd = if negative {
521+ ExtendedBigDecimal :: MinusZero
522+ } else {
523+ ExtendedBigDecimal :: zero ( )
524+ } ;
525+ return Err ( ExtendedParserError :: PartialMatch ( ebd, & unsigned[ 1 ..] ) ) ;
526+ }
527+
514528 return if target == ParseTarget :: Integral {
515529 Err ( ExtendedParserError :: NotNumeric )
516530 } else {
517531 parse_special_value ( unsigned, negative, allowed_suffixes)
518532 } ;
519533 }
520534
535+ let mut digits = digits. unwrap ( ) ;
536+
521537 if let Some ( ( _, ch) ) = chars. peek ( ) {
522538 if let Some ( times) = allowed_suffixes
523539 . iter ( )
@@ -529,7 +545,8 @@ pub(crate) fn parse<'a>(
529545 }
530546 }
531547
532- let ebd_result = construct_extended_big_decimal ( digits, negative, base, scale, exponent) ;
548+ let ebd_result =
549+ construct_extended_big_decimal ( digits, negative, base, scale, exponent. unwrap_or_default ( ) ) ;
533550
534551 // Return what has been parsed so far. If there are extra characters, mark the
535552 // parsing as a partial match.
@@ -625,6 +642,15 @@ mod tests {
625642 i64 :: extended_parse( & format!( "{}" , i64 :: MIN as i128 - 1 ) ) ,
626643 Err ( ExtendedParserError :: Overflow ( i64 :: MIN ) )
627644 ) ) ;
645+
646+ assert ! ( matches!(
647+ i64 :: extended_parse( "" ) ,
648+ Err ( ExtendedParserError :: NotNumeric )
649+ ) ) ;
650+ assert ! ( matches!(
651+ i64 :: extended_parse( "." ) ,
652+ Err ( ExtendedParserError :: NotNumeric )
653+ ) ) ;
628654 }
629655
630656 #[ test]
@@ -659,6 +685,16 @@ mod tests {
659685 Ok ( 0.15 ) ,
660686 f64 :: extended_parse( ".150000000000000000000000000231313" )
661687 ) ;
688+ assert ! ( matches!( f64 :: extended_parse( "123.15e" ) ,
689+ Err ( ExtendedParserError :: PartialMatch ( f, "e" ) ) if f == 123.15 ) ) ;
690+ assert ! ( matches!( f64 :: extended_parse( "123.15E" ) ,
691+ Err ( ExtendedParserError :: PartialMatch ( f, "E" ) ) if f == 123.15 ) ) ;
692+ assert ! ( matches!( f64 :: extended_parse( "123.15e-" ) ,
693+ Err ( ExtendedParserError :: PartialMatch ( f, "e-" ) ) if f == 123.15 ) ) ;
694+ assert ! ( matches!( f64 :: extended_parse( "123.15e+" ) ,
695+ Err ( ExtendedParserError :: PartialMatch ( f, "e+" ) ) if f == 123.15 ) ) ;
696+ assert ! ( matches!( f64 :: extended_parse( "123.15e." ) ,
697+ Err ( ExtendedParserError :: PartialMatch ( f, "e." ) ) if f == 123.15 ) ) ;
662698 assert ! ( matches!( f64 :: extended_parse( "1.2.3" ) ,
663699 Err ( ExtendedParserError :: PartialMatch ( f, ".3" ) ) if f == 1.2 ) ) ;
664700 assert ! ( matches!( f64 :: extended_parse( "123.15p5" ) ,
@@ -811,6 +847,48 @@ mod tests {
811847 ExtendedBigDecimal :: extended_parse( & format!( "-0e{}" , i64 :: MIN + 2 ) ) ,
812848 Ok ( ExtendedBigDecimal :: MinusZero )
813849 ) ;
850+
851+ /* Invalid numbers */
852+ assert_eq ! (
853+ Err ( ExtendedParserError :: NotNumeric ) ,
854+ ExtendedBigDecimal :: extended_parse( "" )
855+ ) ;
856+ assert_eq ! (
857+ Err ( ExtendedParserError :: NotNumeric ) ,
858+ ExtendedBigDecimal :: extended_parse( "." )
859+ ) ;
860+ assert_eq ! (
861+ Err ( ExtendedParserError :: NotNumeric ) ,
862+ ExtendedBigDecimal :: extended_parse( "e" )
863+ ) ;
864+ assert_eq ! (
865+ Err ( ExtendedParserError :: NotNumeric ) ,
866+ ExtendedBigDecimal :: extended_parse( ".e" )
867+ ) ;
868+ assert_eq ! (
869+ Err ( ExtendedParserError :: NotNumeric ) ,
870+ ExtendedBigDecimal :: extended_parse( "-e" )
871+ ) ;
872+ assert_eq ! (
873+ Err ( ExtendedParserError :: NotNumeric ) ,
874+ ExtendedBigDecimal :: extended_parse( "+.e" )
875+ ) ;
876+ assert_eq ! (
877+ Err ( ExtendedParserError :: NotNumeric ) ,
878+ ExtendedBigDecimal :: extended_parse( "e10" )
879+ ) ;
880+ assert_eq ! (
881+ Err ( ExtendedParserError :: NotNumeric ) ,
882+ ExtendedBigDecimal :: extended_parse( "e-10" )
883+ ) ;
884+ assert_eq ! (
885+ Err ( ExtendedParserError :: NotNumeric ) ,
886+ ExtendedBigDecimal :: extended_parse( "-e10" )
887+ ) ;
888+ assert_eq ! (
889+ Err ( ExtendedParserError :: NotNumeric ) ,
890+ ExtendedBigDecimal :: extended_parse( "+e10" )
891+ ) ;
814892 }
815893
816894 #[ test]
@@ -831,6 +909,15 @@ mod tests {
831909 // but we can check that the number still gets parsed properly: 0x0.8e5 is 0x8e5 / 16**3
832910 assert_eq ! ( Ok ( 0.555908203125 ) , f64 :: extended_parse( "0x0.8e5" ) ) ;
833911
912+ assert ! ( matches!( f64 :: extended_parse( "0x0.1p" ) ,
913+ Err ( ExtendedParserError :: PartialMatch ( f, "p" ) ) if f == 0.0625 ) ) ;
914+ assert ! ( matches!( f64 :: extended_parse( "0x0.1p-" ) ,
915+ Err ( ExtendedParserError :: PartialMatch ( f, "p-" ) ) if f == 0.0625 ) ) ;
916+ assert ! ( matches!( f64 :: extended_parse( "0x.1p+" ) ,
917+ Err ( ExtendedParserError :: PartialMatch ( f, "p+" ) ) if f == 0.0625 ) ) ;
918+ assert ! ( matches!( f64 :: extended_parse( "0x.1p." ) ,
919+ Err ( ExtendedParserError :: PartialMatch ( f, "p." ) ) if f == 0.0625 ) ) ;
920+
834921 assert_eq ! (
835922 Ok ( ExtendedBigDecimal :: BigDecimal (
836923 BigDecimal :: from_str( "0.0625" ) . unwrap( )
@@ -887,6 +974,42 @@ mod tests {
887974 ExtendedBigDecimal :: MinusZero
888975 ) )
889976 ) ) ;
977+
978+ // Not actually hex numbers, but the prefixes look like it.
979+ assert ! ( matches!( f64 :: extended_parse( "0x" ) ,
980+ Err ( ExtendedParserError :: PartialMatch ( f, "x" ) ) if f == 0.0 ) ) ;
981+ assert ! ( matches!( f64 :: extended_parse( "0x." ) ,
982+ Err ( ExtendedParserError :: PartialMatch ( f, "x." ) ) if f == 0.0 ) ) ;
983+ assert ! ( matches!( f64 :: extended_parse( "0xp" ) ,
984+ Err ( ExtendedParserError :: PartialMatch ( f, "xp" ) ) if f == 0.0 ) ) ;
985+ assert ! ( matches!( f64 :: extended_parse( "0xp-2" ) ,
986+ Err ( ExtendedParserError :: PartialMatch ( f, "xp-2" ) ) if f == 0.0 ) ) ;
987+ assert ! ( matches!( f64 :: extended_parse( "0x.p-2" ) ,
988+ Err ( ExtendedParserError :: PartialMatch ( f, "x.p-2" ) ) if f == 0.0 ) ) ;
989+ assert ! ( matches!( f64 :: extended_parse( "0X" ) ,
990+ Err ( ExtendedParserError :: PartialMatch ( f, "X" ) ) if f == 0.0 ) ) ;
991+ assert ! ( matches!( f64 :: extended_parse( "-0x" ) ,
992+ Err ( ExtendedParserError :: PartialMatch ( f, "x" ) ) if f == -0.0 ) ) ;
993+ assert ! ( matches!( f64 :: extended_parse( "+0x" ) ,
994+ Err ( ExtendedParserError :: PartialMatch ( f, "x" ) ) if f == 0.0 ) ) ;
995+ assert ! ( matches!( f64 :: extended_parse( "-0x." ) ,
996+ Err ( ExtendedParserError :: PartialMatch ( f, "x." ) ) if f == -0.0 ) ) ;
997+ assert ! ( matches!(
998+ u64 :: extended_parse( "0x" ) ,
999+ Err ( ExtendedParserError :: PartialMatch ( 0 , "x" ) )
1000+ ) ) ;
1001+ assert ! ( matches!(
1002+ u64 :: extended_parse( "-0x" ) ,
1003+ Err ( ExtendedParserError :: PartialMatch ( 0 , "x" ) )
1004+ ) ) ;
1005+ assert ! ( matches!(
1006+ i64 :: extended_parse( "0x" ) ,
1007+ Err ( ExtendedParserError :: PartialMatch ( 0 , "x" ) )
1008+ ) ) ;
1009+ assert ! ( matches!(
1010+ i64 :: extended_parse( "-0x" ) ,
1011+ Err ( ExtendedParserError :: PartialMatch ( 0 , "x" ) )
1012+ ) ) ;
8901013 }
8911014
8921015 #[ test]
@@ -920,6 +1043,27 @@ mod tests {
9201043 assert_eq ! ( Ok ( 0b1011 ) , u64 :: extended_parse( "+0b1011" ) ) ;
9211044 assert_eq ! ( Ok ( -0b1011 ) , i64 :: extended_parse( "-0b1011" ) ) ;
9221045
1046+ assert ! ( matches!(
1047+ u64 :: extended_parse( "0b" ) ,
1048+ Err ( ExtendedParserError :: PartialMatch ( 0 , "b" ) )
1049+ ) ) ;
1050+ assert ! ( matches!(
1051+ u64 :: extended_parse( "0b." ) ,
1052+ Err ( ExtendedParserError :: PartialMatch ( 0 , "b." ) )
1053+ ) ) ;
1054+ assert ! ( matches!(
1055+ u64 :: extended_parse( "-0b" ) ,
1056+ Err ( ExtendedParserError :: PartialMatch ( 0 , "b" ) )
1057+ ) ) ;
1058+ assert ! ( matches!(
1059+ i64 :: extended_parse( "0b" ) ,
1060+ Err ( ExtendedParserError :: PartialMatch ( 0 , "b" ) )
1061+ ) ) ;
1062+ assert ! ( matches!(
1063+ i64 :: extended_parse( "-0b" ) ,
1064+ Err ( ExtendedParserError :: PartialMatch ( 0 , "b" ) )
1065+ ) ) ;
1066+
9231067 // Binary not allowed for floats
9241068 assert ! ( matches!(
9251069 f64 :: extended_parse( "0b100" ) ,
@@ -935,6 +1079,15 @@ mod tests {
9351079 ebd == ExtendedBigDecimal :: zero( ) ,
9361080 _ => false ,
9371081 } ) ;
1082+
1083+ assert ! ( match ExtendedBigDecimal :: extended_parse( "0b" ) {
1084+ Err ( ExtendedParserError :: PartialMatch ( ebd, "b" ) ) => ebd == ExtendedBigDecimal :: zero( ) ,
1085+ _ => false ,
1086+ } ) ;
1087+ assert ! ( match ExtendedBigDecimal :: extended_parse( "0b." ) {
1088+ Err ( ExtendedParserError :: PartialMatch ( ebd, "b." ) ) => ebd == ExtendedBigDecimal :: zero( ) ,
1089+ _ => false ,
1090+ } ) ;
9381091 }
9391092
9401093 #[ test]
0 commit comments