@@ -558,8 +558,10 @@ fn parse_scalar_value(value: &str) -> Yaml {
558558 return Yaml :: Integer ( i) ;
559559 }
560560
561- // Try to parse as float
562- if let Ok ( _f) = value. parse :: < f64 > ( ) {
561+ // Try to parse as float, including the YAML 1.2 core-schema float
562+ // spellings (`.inf`, `-.inf`, `+.inf`, `.nan`, and case variants) that
563+ // Rust's `f64::from_str` does not accept.
564+ if is_yaml_float ( value) {
563565 return Yaml :: Real ( value. to_string ( ) ) ;
564566 }
565567
@@ -581,6 +583,16 @@ fn parse_scalar_value(value: &str) -> Yaml {
581583 Yaml :: String ( value. to_string ( ) )
582584}
583585
586+ /// Returns `true` if `value` is a YAML 1.2 core-schema float.
587+ fn is_yaml_float ( value : & str ) -> bool {
588+ match value {
589+ ".inf" | ".Inf" | ".INF" | "+.inf" | "+.Inf" | "+.INF" => true ,
590+ "-.inf" | "-.Inf" | "-.INF" => true ,
591+ ".nan" | ".NaN" | ".NAN" => true ,
592+ _ => value. bytes ( ) . any ( |b| b. is_ascii_digit ( ) ) && value. parse :: < f64 > ( ) . is_ok ( ) ,
593+ }
594+ }
595+
584596#[ cfg( test) ]
585597mod tests {
586598 use super :: * ;
@@ -599,6 +611,57 @@ mod tests {
599611 assert_eq ! ( yaml. yaml. as_i64( ) , Some ( 42 ) ) ;
600612 }
601613
614+ #[ test]
615+ fn test_parse_yaml_float_special_forms ( ) {
616+ // YAML 1.2 core-schema float spellings must resolve to Yaml::Real,
617+ // not Yaml::String. See tidyverse/data-dict#47.
618+ for ( text, expected) in [
619+ ( ".inf" , f64:: INFINITY ) ,
620+ ( "+.inf" , f64:: INFINITY ) ,
621+ ( ".Inf" , f64:: INFINITY ) ,
622+ ( ".INF" , f64:: INFINITY ) ,
623+ ( "+.INF" , f64:: INFINITY ) ,
624+ ( "-.inf" , f64:: NEG_INFINITY ) ,
625+ ( "-.Inf" , f64:: NEG_INFINITY ) ,
626+ ( "-.INF" , f64:: NEG_INFINITY ) ,
627+ ] {
628+ let yaml = parse ( text) . unwrap ( ) ;
629+ assert ! (
630+ matches!( yaml. yaml, Yaml :: Real ( _) ) ,
631+ "{text:?} should parse as Yaml::Real, got {:?}" ,
632+ yaml. yaml
633+ ) ;
634+ assert_eq ! (
635+ yaml. yaml. as_f64( ) ,
636+ Some ( expected) ,
637+ "{text:?} should evaluate to {expected}"
638+ ) ;
639+ }
640+
641+ for text in [ ".nan" , ".NaN" , ".NAN" ] {
642+ let yaml = parse ( text) . unwrap ( ) ;
643+ assert ! (
644+ matches!( yaml. yaml, Yaml :: Real ( _) ) ,
645+ "{text:?} should parse as Yaml::Real, got {:?}" ,
646+ yaml. yaml
647+ ) ;
648+ assert ! (
649+ yaml. yaml. as_f64( ) . unwrap( ) . is_nan( ) ,
650+ "{text:?} should evaluate to NaN"
651+ ) ;
652+ }
653+
654+ // Bare `inf` / `nan` (no leading dot) are NOT YAML floats — they stay strings.
655+ for text in [ "inf" , "nan" , "infinity" ] {
656+ let yaml = parse ( text) . unwrap ( ) ;
657+ assert ! (
658+ matches!( yaml. yaml, Yaml :: String ( _) ) ,
659+ "{text:?} should stay a Yaml::String, got {:?}" ,
660+ yaml. yaml
661+ ) ;
662+ }
663+ }
664+
602665 #[ test]
603666 fn test_parse_boolean ( ) {
604667 let yaml = parse ( "true" ) . unwrap ( ) ;
0 commit comments