@@ -563,7 +563,96 @@ fn string_to_combo(arg: &str) -> Option<&str> {
563563 . map ( |_| arg)
564564}
565565
566+ /// Parse and round a baud rate value using GNU stty's custom rounding algorithm.
567+ ///
568+ /// Accepts decimal values with the following rounding rules:
569+ /// - If first digit after decimal > 5: round up
570+ /// - If first digit after decimal < 5: round down
571+ /// - If first digit after decimal == 5:
572+ /// - If followed by any non-zero digit: round up
573+ /// - If followed only by zeros (or nothing): banker's rounding (round to nearest even)
574+ ///
575+ /// Examples: "9600.49" -> 9600, "9600.51" -> 9600, "9600.5" -> 9600 (even), "9601.5" -> 9602 (even)
576+ /// TODO: there are two special cases "exta" → B19200 and "extb" → B38400
577+ fn parse_baud_with_rounding ( normalized : & str ) -> Option < u32 > {
578+ let mut chars = normalized. chars ( ) . peekable ( ) ;
579+ let mut value = 0u32 ;
580+
581+ // Parse integer part
582+ while let Some ( c) = chars. peek ( ) {
583+ if c. is_ascii_digit ( ) {
584+ value = value * 10 + ( chars. next ( ) . unwrap ( ) as u32 - '0' as u32 ) ;
585+ } else {
586+ break ;
587+ }
588+ }
589+
590+ // Handle fractional part if present
591+ if let Some ( '.' ) = chars. next ( ) {
592+ let d0 = chars. next ( ) ?;
593+ if !d0. is_ascii_digit ( ) {
594+ return None ;
595+ }
596+ let first_digit = d0 as u32 - '0' as u32 ;
597+
598+ if first_digit > 5 {
599+ value += 1 ;
600+ } else if first_digit == 5 {
601+ // Check if there are non-zero digits after the 5
602+ let mut has_nonzero = false ;
603+ while let Some ( c) = chars. next ( ) {
604+ if !c. is_ascii_digit ( ) {
605+ return None ;
606+ }
607+ if c != '0' {
608+ has_nonzero = true ;
609+ break ;
610+ }
611+ }
612+
613+ if has_nonzero {
614+ value += 1 ;
615+ } else {
616+ // Banker's rounding: round to nearest even
617+ value += value & 1 ;
618+ }
619+
620+ // Consume remaining digits
621+ while let Some ( c) = chars. next ( ) {
622+ if !c. is_ascii_digit ( ) {
623+ return None ;
624+ }
625+ }
626+ } else {
627+ // first_digit < 5: round down, but still validate remaining digits
628+ while let Some ( c) = chars. next ( ) {
629+ if !c. is_ascii_digit ( ) {
630+ return None ;
631+ }
632+ }
633+ }
634+ } else if chars. peek ( ) . is_some ( ) {
635+ // Non-digit character after integer part
636+ return None ;
637+ }
638+
639+ Some ( value)
640+ }
641+
566642fn string_to_baud ( arg : & str ) -> Option < AllFlags < ' _ > > {
643+ // Reject invalid formats
644+ if arg != arg. trim_end ( )
645+ || arg. trim ( ) . starts_with ( '-' )
646+ || arg. trim ( ) . starts_with ( "++" )
647+ || arg. contains ( 'E' )
648+ || arg. contains ( 'e' )
649+ {
650+ return None ;
651+ }
652+
653+ let normalized = arg. trim ( ) . trim_start_matches ( '+' ) ;
654+ let value = parse_baud_with_rounding ( normalized) ?;
655+
567656 // BSDs use a u32 for the baud rate, so any decimal number applies.
568657 #[ cfg( any(
569658 target_os = "freebsd" ,
@@ -573,9 +662,7 @@ fn string_to_baud(arg: &str) -> Option<AllFlags<'_>> {
573662 target_os = "netbsd" ,
574663 target_os = "openbsd"
575664 ) ) ]
576- if let Ok ( n) = arg. parse :: < u32 > ( ) {
577- return Some ( AllFlags :: Baud ( n) ) ;
578- }
665+ return Some ( AllFlags :: Baud ( value) ) ;
579666
580667 #[ cfg( not( any(
581668 target_os = "freebsd" ,
@@ -586,7 +673,7 @@ fn string_to_baud(arg: &str) -> Option<AllFlags<'_>> {
586673 target_os = "openbsd"
587674 ) ) ) ]
588675 for ( text, baud_rate) in BAUD_RATES {
589- if * text == arg {
676+ if text. parse :: < u32 > ( ) . ok ( ) == Some ( value ) {
590677 return Some ( AllFlags :: Baud ( * baud_rate) ) ;
591678 }
592679 }
0 commit comments