@@ -10,7 +10,7 @@ pub mod test_time_helpers;
1010use crate :: string_functions:: ptr_to_slice;
1111use chrono:: { TimeZone , Timelike } ;
1212use num:: { Float , PrimInt } ;
13- use std:: { fmt :: Display , io:: Write , str:: FromStr } ;
13+ use std:: { io:: Write , str:: FromStr } ;
1414
1515// can't determine string buffer length of an empty string, therefore
1616// _TO_STRING functions use the default string length.
@@ -89,7 +89,6 @@ pub unsafe extern "C" fn REAL_TO_STRING_EXT(input: f64, dest: *mut u8) -> i32 {
8989unsafe fn string_to_int < T > ( src : * const u8 ) -> T
9090where
9191 T : PrimInt ,
92- <T as num:: Num >:: FromStrRadixErr : std:: fmt:: Display ,
9392{
9493 let slice = ptr_to_slice ( src) ;
9594 let ( string, radix) = match slice {
@@ -100,15 +99,29 @@ where
10099 _ => ( std:: str:: from_utf8 ( slice) , 10 ) ,
101100 } ;
102101
102+ // Parse the longest valid prefix instead of panicking on malformed input.
103+ // For example "12j3" yields 12 and "123.456" yields 123, while a string with no valid prefix yields 0.
103104 match string {
104- Ok ( s) => match T :: from_str_radix ( s, radix) {
105- Ok ( number) => number,
106- Err ( e) => panic ! ( "Could not parse number from '{s}': {e}" ) ,
107- } ,
108- Err ( e) => panic ! ( "Encoding error: {e}" ) ,
105+ Ok ( s) => parse_longest_prefix ( s, |candidate| T :: from_str_radix ( candidate, radix) . ok ( ) ) ,
106+ Err ( _) => T :: zero ( ) ,
109107 }
110108}
111109
110+ /// Returns the value parsed from the longest prefix of `s` accepted by `parse`, or the type's
111+ /// default (`0`) when no non-empty prefix is valid.
112+ fn parse_longest_prefix < T : num:: Zero > ( s : & str , parse : impl Fn ( & str ) -> Option < T > ) -> T {
113+ let mut end = s. len ( ) ;
114+ while end > 0 {
115+ if s. is_char_boundary ( end) {
116+ if let Some ( number) = parse ( & s[ ..end] ) {
117+ return number;
118+ }
119+ }
120+ end -= 1 ;
121+ }
122+ T :: zero ( )
123+ }
124+
112125/// # Safety
113126/// Uses raw pointers, inherently unsafe.
114127#[ allow( non_snake_case) ]
@@ -128,17 +141,14 @@ pub unsafe extern "C" fn STRING_TO_DINT(src: *const u8) -> i32 {
128141unsafe fn string_to_float < T > ( src : * const u8 ) -> T
129142where
130143 T : Float + FromStr ,
131- <T as FromStr >:: Err : Display ,
132144{
133145 let slice = ptr_to_slice ( src) ;
134- let string = std:: str:: from_utf8 ( slice) ;
135146
136- match string {
137- Ok ( s) => match s. parse ( ) {
138- Ok ( number) => number,
139- Err ( e) => panic ! ( "Could not parse number from '{s}': {e}" ) ,
140- } ,
141- Err ( e) => panic ! ( "Encoding error: {e}" ) ,
147+ // Parse the longest valid prefix instead of panicking on malformed input.
148+ // For example "1.2j3" yields 1.2, while "asdf" or an empty string yield 0.0.
149+ match std:: str:: from_utf8 ( slice) {
150+ Ok ( s) => parse_longest_prefix ( s, |candidate| candidate. parse :: < T > ( ) . ok ( ) ) ,
151+ Err ( _) => T :: zero ( ) ,
142152 }
143153}
144154
@@ -354,10 +364,48 @@ mod test {
354364 }
355365
356366 #[ test]
357- # [ should_panic ]
358- fn string_to_lint_conversion_panics_if_given_invalid_string ( ) {
367+ fn string_to_lint_parses_longest_valid_prefix ( ) {
368+ // a string with no valid prefix yields 0 instead of panicking
359369 let string = "ab456\0 " ;
360- let _ = unsafe { STRING_TO_LINT ( string. as_ptr ( ) ) } ;
370+ let result = unsafe { STRING_TO_LINT ( string. as_ptr ( ) ) } ;
371+ assert_eq ! ( 0_i64 , result) ;
372+
373+ // parsing stops at the first invalid character
374+ let string = "12j3\0 " ;
375+ let result = unsafe { STRING_TO_LINT ( string. as_ptr ( ) ) } ;
376+ assert_eq ! ( 12_i64 , result) ;
377+
378+ // the integer part of a decimal number is parsed (the '.' ends the prefix)
379+ let string = "123.456\0 " ;
380+ let result = unsafe { STRING_TO_LINT ( string. as_ptr ( ) ) } ;
381+ assert_eq ! ( 123_i64 , result) ;
382+
383+ // empty string yields 0
384+ let string = "\0 " ;
385+ let result = unsafe { STRING_TO_LINT ( string. as_ptr ( ) ) } ;
386+ assert_eq ! ( 0_i64 , result) ;
387+
388+ // radix prefixes are still honoured, and trailing garbage is trimmed off the digits.
389+ // Note 'E'/'F' are valid hex digits here, not an exponent: "16#FFE0" parses fully.
390+ let string = "16#FFxy\0 " ;
391+ let result = unsafe { STRING_TO_LINT ( string. as_ptr ( ) ) } ;
392+ assert_eq ! ( 255_i64 , result) ;
393+
394+ let string = "16#FFE0\0 " ;
395+ let result = unsafe { STRING_TO_LINT ( string. as_ptr ( ) ) } ;
396+ assert_eq ! ( 65504_i64 , result) ;
397+
398+ let string = "8#1239\0 " ; // 9 is not a valid octal digit, so the prefix is "123"
399+ let result = unsafe { STRING_TO_LINT ( string. as_ptr ( ) ) } ;
400+ assert_eq ! ( 0o123_i64 , result) ;
401+
402+ let string = "2#10102\0 " ; // 2 is not a valid binary digit, so the prefix is "1010"
403+ let result = unsafe { STRING_TO_LINT ( string. as_ptr ( ) ) } ;
404+ assert_eq ! ( 0b1010_i64 , result) ;
405+
406+ let string = "0xdeadZZ\0 " ;
407+ let result = unsafe { STRING_TO_LINT ( string. as_ptr ( ) ) } ;
408+ assert_eq ! ( 0xdead_i64 , result) ;
361409 }
362410
363411 #[ test]
@@ -375,10 +423,30 @@ mod test {
375423 }
376424
377425 #[ test]
378- # [ should_panic ]
379- fn string_to_lreal_conversion_panics_if_given_invalid_string ( ) {
426+ fn string_to_lreal_parses_longest_valid_prefix ( ) {
427+ // parsing stops at the first invalid character instead of panicking
380428 let string = "1,25f\0 " ;
381- let _ = unsafe { STRING_TO_LREAL ( string. as_ptr ( ) ) } ;
429+ let result = unsafe { STRING_TO_LREAL ( string. as_ptr ( ) ) } ;
430+ assert_eq ! ( 1.0 , result) ;
431+
432+ let string = "1.2j3\0 " ;
433+ let result = unsafe { STRING_TO_LREAL ( string. as_ptr ( ) ) } ;
434+ assert_eq ! ( 1.2 , result) ;
435+
436+ // ST escape sequences (here $R$N -> CR LF) trailing the number are ignored
437+ let string = "123.456\r \n \0 " ;
438+ let result = unsafe { STRING_TO_LREAL ( string. as_ptr ( ) ) } ;
439+ assert_eq ! ( 123.456 , result) ;
440+
441+ // a string with no valid prefix yields 0.0
442+ let string = "asdf\0 " ;
443+ let result = unsafe { STRING_TO_LREAL ( string. as_ptr ( ) ) } ;
444+ assert_eq ! ( 0.0 , result) ;
445+
446+ // empty string yields 0.0
447+ let string = "\0 " ;
448+ let result = unsafe { STRING_TO_LREAL ( string. as_ptr ( ) ) } ;
449+ assert_eq ! ( 0.0 , result) ;
382450 }
383451
384452 #[ test]
0 commit comments