@@ -62,12 +62,97 @@ impl<'a> Iterator for WhitespaceSplitter<'a> {
6262 }
6363}
6464
65- fn parse_suffix ( s : & str ) -> Result < ( f64 , Option < Suffix > ) > {
65+ fn find_numeric_beginning ( s : & str ) -> Option < & str > {
66+ let mut decimal_point_seen = false ;
67+ if s. is_empty ( ) {
68+ return None ;
69+ }
70+
71+ for ( idx, c) in s. char_indices ( ) {
72+ if c == '-' && idx == 0 {
73+ continue ;
74+ }
75+ if c. is_ascii_digit ( ) {
76+ continue ;
77+ }
78+ if c == '.' && !decimal_point_seen {
79+ decimal_point_seen = true ;
80+ continue ;
81+ }
82+ if s[ ..idx] . parse :: < f64 > ( ) . is_err ( ) {
83+ return None ;
84+ }
85+ return Some ( & s[ ..idx] ) ;
86+ }
87+
88+ Some ( s)
89+ }
90+
91+ // finds the valid beginning part of an input string, or None.
92+ fn find_valid_number_with_suffix < ' a > ( s : & ' a str , unit : & Unit ) -> Option < & ' a str > {
93+ let numeric_part = find_numeric_beginning ( s) ?;
94+
95+ let accepts_suffix = unit != & Unit :: None ;
96+ let accepts_i = [ Unit :: Auto , Unit :: Iec ( true ) ] . contains ( unit) ;
97+
98+ let mut characters = s. chars ( ) . skip ( numeric_part. len ( ) ) ;
99+ let potential_suffix = characters. next ( ) ;
100+ let potential_i = characters. next ( ) ;
101+
102+ if !accepts_suffix {
103+ return Some ( numeric_part) ;
104+ }
105+
106+ match ( potential_suffix, potential_i) {
107+ ( Some ( suffix) , None ) if RawSuffix :: try_from ( & suffix) . is_ok ( ) => {
108+ Some ( & s[ ..=numeric_part. len ( ) ] )
109+ }
110+ ( Some ( suffix) , Some ( 'i' ) ) if accepts_i && RawSuffix :: try_from ( & suffix) . is_ok ( ) => {
111+ Some ( & s[ ..numeric_part. len ( ) + 2 ] )
112+ }
113+ ( Some ( suffix) , Some ( _) ) if RawSuffix :: try_from ( & suffix) . is_ok ( ) => {
114+ Some ( & s[ ..=numeric_part. len ( ) ] )
115+ }
116+ _ => Some ( numeric_part) ,
117+ }
118+ }
119+
120+ fn detailed_error_message ( s : & str , unit : & Unit ) -> Option < String > {
121+ if s. is_empty ( ) {
122+ return Some ( translate ! ( "numfmt-error-invalid-number-empty" ) ) ;
123+ }
124+
125+ let valid_part = find_valid_number_with_suffix ( s, unit)
126+ . ok_or ( translate ! ( "numfmt-error-invalid-number" , "input" => s. quote( ) ) )
127+ . ok ( ) ?;
128+
129+ if valid_part != s && valid_part. parse :: < f64 > ( ) . is_ok ( ) {
130+ return match s. chars ( ) . nth ( valid_part. len ( ) ) {
131+ Some ( v) if RawSuffix :: try_from ( & v) . is_ok ( ) => Some (
132+ translate ! ( "numfmt-error-rejecting-suffix" , "number" => valid_part, "suffix" => s[ valid_part. len( ) ..] ) ,
133+ ) ,
134+
135+ _ => Some ( translate ! ( "numfmt-error-invalid-suffix" , "input" => s. quote( ) ) ) ,
136+ } ;
137+ }
138+
139+ if valid_part != s && valid_part. parse :: < f64 > ( ) . is_err ( ) {
140+ return Some (
141+ translate ! ( "numfmt-error-invalid-specific-suffix" , "input" => s. quote( ) , "suffix" => s[ valid_part. len( ) ..] . quote( ) ) ,
142+ ) ;
143+ }
144+ None
145+ }
146+
147+ fn parse_suffix ( s : & str , unit : & Unit ) -> Result < ( f64 , Option < Suffix > ) > {
66148 if s. is_empty ( ) {
67149 return Err ( translate ! ( "numfmt-error-invalid-number-empty" ) ) ;
68150 }
69151
70152 let with_i = s. ends_with ( 'i' ) ;
153+ if with_i && ![ Unit :: Auto , Unit :: Iec ( true ) ] . contains ( unit) {
154+ return Err ( translate ! ( "numfmt-error-invalid-suffix" , "input" => s. quote( ) ) ) ;
155+ }
71156 let mut iter = s. chars ( ) ;
72157 if with_i {
73158 iter. next_back ( ) ;
@@ -86,17 +171,7 @@ fn parse_suffix(s: &str) -> Result<(f64, Option<Suffix>)> {
86171 Some ( 'Q' ) => Some ( ( RawSuffix :: Q , with_i) ) ,
87172 Some ( '0' ..='9' ) if !with_i => None ,
88173 _ => {
89- // If with_i is true, the string ends with 'i' but there's no valid suffix letter
90- // This is always an invalid suffix (e.g., "1i", "2Ai")
91- if with_i {
92- return Err ( translate ! ( "numfmt-error-invalid-suffix" , "input" => s. quote( ) ) ) ;
93- }
94- // For other cases, check if the number part (without the last character) is valid
95- let number_part = & s[ ..s. len ( ) - 1 ] ;
96- if number_part. is_empty ( ) || number_part. parse :: < f64 > ( ) . is_err ( ) {
97- return Err ( translate ! ( "numfmt-error-invalid-number" , "input" => s. quote( ) ) ) ;
98- }
99- return Err ( translate ! ( "numfmt-error-invalid-suffix" , "input" => s. quote( ) ) ) ;
174+ return Err ( translate ! ( "numfmt-error-invalid-number" , "input" => s. quote( ) ) ) ;
100175 }
101176 } ;
102177
@@ -164,7 +239,8 @@ fn remove_suffix(i: f64, s: Option<Suffix>, u: &Unit) -> Result<f64> {
164239}
165240
166241fn transform_from ( s : & str , opts : & TransformOptions ) -> Result < f64 > {
167- let ( i, suffix) = parse_suffix ( s) ?;
242+ let ( i, suffix) = parse_suffix ( s, & opts. from )
243+ . map_err ( |original| detailed_error_message ( s, & opts. from ) . unwrap_or ( original) ) ?;
168244 let i = i * ( opts. from_unit as f64 ) ;
169245
170246 remove_suffix ( i, suffix, & opts. from ) . map ( |n| {
@@ -491,7 +567,7 @@ mod tests {
491567
492568 #[ test]
493569 fn test_parse_suffix_q_r_k ( ) {
494- let result = parse_suffix ( "1Q" ) ;
570+ let result = parse_suffix ( "1Q" , & Unit :: Auto ) ;
495571 assert ! ( result. is_ok( ) ) ;
496572 let ( number, suffix) = result. unwrap ( ) ;
497573 assert_eq ! ( number, 1.0 ) ;
@@ -500,7 +576,7 @@ mod tests {
500576 assert_eq ! ( raw_suffix as i32 , RawSuffix :: Q as i32 ) ;
501577 assert ! ( !with_i) ;
502578
503- let result = parse_suffix ( "2R" ) ;
579+ let result = parse_suffix ( "2R" , & Unit :: Auto ) ;
504580 assert ! ( result. is_ok( ) ) ;
505581 let ( number, suffix) = result. unwrap ( ) ;
506582 assert_eq ! ( number, 2.0 ) ;
@@ -509,7 +585,7 @@ mod tests {
509585 assert_eq ! ( raw_suffix as i32 , RawSuffix :: R as i32 ) ;
510586 assert ! ( !with_i) ;
511587
512- let result = parse_suffix ( "3k" ) ;
588+ let result = parse_suffix ( "3k" , & Unit :: Auto ) ;
513589 assert ! ( result. is_ok( ) ) ;
514590 let ( number, suffix) = result. unwrap ( ) ;
515591 assert_eq ! ( number, 3.0 ) ;
@@ -518,7 +594,7 @@ mod tests {
518594 assert_eq ! ( raw_suffix as i32 , RawSuffix :: K as i32 ) ;
519595 assert ! ( !with_i) ;
520596
521- let result = parse_suffix ( "4Qi" ) ;
597+ let result = parse_suffix ( "4Qi" , & Unit :: Auto ) ;
522598 assert ! ( result. is_ok( ) ) ;
523599 let ( number, suffix) = result. unwrap ( ) ;
524600 assert_eq ! ( number, 4.0 ) ;
@@ -527,7 +603,7 @@ mod tests {
527603 assert_eq ! ( raw_suffix as i32 , RawSuffix :: Q as i32 ) ;
528604 assert ! ( with_i) ;
529605
530- let result = parse_suffix ( "5Ri" ) ;
606+ let result = parse_suffix ( "5Ri" , & Unit :: Auto ) ;
531607 assert ! ( result. is_ok( ) ) ;
532608 let ( number, suffix) = result. unwrap ( ) ;
533609 assert_eq ! ( number, 5.0 ) ;
@@ -539,22 +615,41 @@ mod tests {
539615
540616 #[ test]
541617 fn test_parse_suffix_error_messages ( ) {
542- let result = parse_suffix ( "foo" ) ;
618+ let result = parse_suffix ( "foo" , & Unit :: Auto ) ;
543619 assert ! ( result. is_err( ) ) ;
544620 let error = result. unwrap_err ( ) ;
545621 assert ! ( error. contains( "numfmt-error-invalid-number" ) || error. contains( "invalid number" ) ) ;
546622 assert ! ( !error. contains( "invalid suffix" ) ) ;
547623
548- let result = parse_suffix ( "World" ) ;
624+ let result = parse_suffix ( "World" , & Unit :: Auto ) ;
549625 assert ! ( result. is_err( ) ) ;
550626 let error = result. unwrap_err ( ) ;
551627 assert ! ( error. contains( "numfmt-error-invalid-number" ) || error. contains( "invalid number" ) ) ;
552628 assert ! ( !error. contains( "invalid suffix" ) ) ;
629+ }
553630
554- let result = parse_suffix ( "123i" ) ;
555- assert ! ( result. is_err( ) ) ;
556- let error = result. unwrap_err ( ) ;
631+ #[ test]
632+ fn test_detailed_error_message ( ) {
633+ let result = detailed_error_message ( "123i" , & Unit :: Auto ) ;
634+ assert ! ( result. is_some( ) ) ;
635+ let error = result. unwrap ( ) ;
557636 assert ! ( error. contains( "numfmt-error-invalid-suffix" ) || error. contains( "invalid suffix" ) ) ;
637+
638+ let result = detailed_error_message ( "5MF" , & Unit :: Auto ) ;
639+ assert ! ( result. is_some( ) ) ;
640+ let error = result. unwrap ( ) ;
641+ assert ! (
642+ error. contains( "numfmt-error-invalid-specific-suffix" )
643+ || error. contains( "invalid suffix" )
644+ ) ;
645+
646+ let result = detailed_error_message ( "5KM" , & Unit :: Auto ) ;
647+ assert ! ( result. is_some( ) ) ;
648+ let error = result. unwrap ( ) ;
649+ assert ! (
650+ error. contains( "numfmt-error-invalid-specific-suffix" )
651+ || error. contains( "invalid suffix" )
652+ ) ;
558653 }
559654
560655 #[ test]
@@ -578,6 +673,72 @@ mod tests {
578673 assert_eq ! ( result. unwrap( ) , IEC_BASES [ 9 ] ) ;
579674 }
580675
676+ #[ test]
677+ fn test_find_valid_part ( ) {
678+ assert_eq ! (
679+ find_valid_number_with_suffix( "12345KL" , & Unit :: Auto ) ,
680+ Some ( "12345K" )
681+ ) ;
682+ assert_eq ! (
683+ find_valid_number_with_suffix( "12345K" , & Unit :: Auto ) ,
684+ Some ( "12345K" )
685+ ) ;
686+ assert_eq ! (
687+ find_valid_number_with_suffix( "12345" , & Unit :: Auto ) ,
688+ Some ( "12345" )
689+ ) ;
690+ assert_eq ! (
691+ find_valid_number_with_suffix( "asd12345KL" , & Unit :: Auto ) ,
692+ None
693+ ) ;
694+ assert_eq ! (
695+ find_valid_number_with_suffix( "8asdf" , & Unit :: Auto ) ,
696+ Some ( "8" )
697+ ) ;
698+ assert_eq ! ( find_valid_number_with_suffix( "5i" , & Unit :: Si ) , Some ( "5" ) ) ;
699+ assert_eq ! (
700+ find_valid_number_with_suffix( "5i" , & Unit :: Iec ( true ) ) ,
701+ Some ( "5" )
702+ ) ;
703+ assert_eq ! (
704+ find_valid_number_with_suffix( "0.1KL" , & Unit :: Auto ) ,
705+ Some ( "0.1K" )
706+ ) ;
707+ assert_eq ! (
708+ find_valid_number_with_suffix( "0.1" , & Unit :: Auto ) ,
709+ Some ( "0.1" )
710+ ) ;
711+ assert_eq ! (
712+ find_valid_number_with_suffix( "-0.1MT" , & Unit :: Auto ) ,
713+ Some ( "-0.1M" )
714+ ) ;
715+ assert_eq ! (
716+ find_valid_number_with_suffix( "-0.1PT" , & Unit :: Auto ) ,
717+ Some ( "-0.1P" )
718+ ) ;
719+ assert_eq ! (
720+ find_valid_number_with_suffix( "-0.1PT" , & Unit :: Auto ) ,
721+ Some ( "-0.1P" )
722+ ) ;
723+ assert_eq ! (
724+ find_valid_number_with_suffix( "123.4.5" , & Unit :: Auto ) ,
725+ Some ( "123.4" )
726+ ) ;
727+ assert_eq ! (
728+ find_valid_number_with_suffix( "0.55KiJ" , & Unit :: Iec ( true ) ) ,
729+ Some ( "0.55Ki" )
730+ ) ;
731+ assert_eq ! (
732+ find_valid_number_with_suffix( "0.55KiJ" , & Unit :: Iec ( false ) ) ,
733+ Some ( "0.55K" )
734+ ) ;
735+ assert_eq ! (
736+ find_valid_number_with_suffix( "123KICK" , & Unit :: Auto ) ,
737+ Some ( "123K" )
738+ ) ;
739+ assert_eq ! ( find_valid_number_with_suffix( "" , & Unit :: Auto ) , None ) ;
740+ }
741+
581742 #[ test]
582743 fn test_consider_suffix_q_r ( ) {
583744 use crate :: options:: RoundMethod ;
0 commit comments