1use crate::from_r::{
6 BatchedErrors, SexpError, SexpLengthError, SexpTypeError, TryFromSexp, charsxp_to_str,
7 impl_option_try_from_sexp, map_strsxp_with,
8};
9use crate::into_r::IntoR;
10use crate::{IsNA, RLogical, SEXP, SEXPTYPE, SexpExt};
11
12#[derive(#[automatically_derived]
impl ::core::fmt::Debug for AsNumeric {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_tuple_field1_finish(f, "AsNumeric",
&&self.0)
}
}Debug, #[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for AsNumeric { }
#[automatically_derived]
impl ::core::clone::Clone for AsNumeric {
#[inline]
fn clone(&self) -> Self {
let _: ::core::clone::AssertParamIsClone<Option<f64>>;
*self
}
}Clone, #[automatically_derived]
impl ::core::marker::Copy for AsNumeric { }Copy, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for AsNumeric { }
#[automatically_derived]
impl ::core::cmp::PartialEq for AsNumeric {
#[inline]
fn eq(&self, other: &Self) -> bool { self.0 == other.0 }
}PartialEq)]
40pub struct AsNumeric(pub Option<f64>);
41
42#[derive(#[automatically_derived]
impl ::core::fmt::Debug for AsNumericVec {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_tuple_field1_finish(f, "AsNumericVec",
&&self.0)
}
}Debug, #[automatically_derived]
impl ::core::clone::Clone for AsNumericVec {
#[inline]
fn clone(&self) -> Self {
AsNumericVec(::core::clone::Clone::clone(&self.0))
}
}Clone, #[automatically_derived]
impl ::core::default::Default for AsNumericVec {
#[inline]
fn default() -> Self { AsNumericVec(::core::default::Default::default()) }
}Default, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for AsNumericVec { }
#[automatically_derived]
impl ::core::cmp::PartialEq for AsNumericVec {
#[inline]
fn eq(&self, other: &Self) -> bool { self.0 == other.0 }
}PartialEq)]
119pub struct AsNumericVec(pub Vec<Option<f64>>);
120
121impl TryFromSexp for AsNumericVec {
122 type Error = SexpError;
123 #[inline]
124 fn __mx_has_na(&self) -> bool {
125 self.0.iter().copied().any(refused_by_no_na)
126 }
127
128 fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
129 read_numeric(sexp).map(AsNumericVec)
130 }
131}
132
133impl TryFromSexp for AsNumeric {
134 type Error = SexpError;
135 #[inline]
136 fn __mx_has_na(&self) -> bool {
137 refused_by_no_na(self.0)
138 }
139
140 fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
141 check_type(sexp.type_of())?;
144 let len = sexp.len();
145 if len != 1 {
146 return Err(SexpLengthError {
147 expected: 1,
148 actual: len,
149 }
150 .into());
151 }
152 let values = read_numeric(sexp)?;
153 Ok(AsNumeric(values[0]))
154 }
155}
156
157impl crate::from_r::TryFromSexp for Option<AsNumeric> {
const NATIVE_BORROW: Option<crate::from_r::NativeBorrow> =
<AsNumeric as crate::from_r::TryFromSexp>::NATIVE_BORROW;
const CHARACTER_ONLY: bool =
<AsNumeric as crate::from_r::TryFromSexp>::CHARACTER_ONLY;
type Error = crate::from_r::SexpError;
#[inline]
fn __mx_has_na(&self) -> bool {
self.as_ref().is_some_and(<AsNumeric as
crate::from_r::TryFromSexp>::__mx_has_na)
}
fn try_from_sexp(sexp: crate::SEXP) -> Result<Self, Self::Error> {
use crate::{SEXPTYPE, SexpExt};
if sexp.type_of() == SEXPTYPE::NILSXP { return Ok(None); }
<AsNumeric as
crate::from_r::TryFromSexp>::try_from_sexp(sexp).map(Some)
}
unsafe fn try_from_sexp_unchecked(sexp: crate::SEXP)
-> Result<Self, Self::Error> {
use crate::{SEXPTYPE, SexpExt};
if sexp.type_of() == SEXPTYPE::NILSXP { return Ok(None); }
unsafe {
<AsNumeric as
crate::from_r::TryFromSexp>::try_from_sexp_unchecked(sexp).map(Some)
}
}
}impl_option_try_from_sexp!(AsNumeric);
158impl crate::from_r::TryFromSexp for Option<AsNumericVec> {
const NATIVE_BORROW: Option<crate::from_r::NativeBorrow> =
<AsNumericVec as crate::from_r::TryFromSexp>::NATIVE_BORROW;
const CHARACTER_ONLY: bool =
<AsNumericVec as crate::from_r::TryFromSexp>::CHARACTER_ONLY;
type Error = crate::from_r::SexpError;
#[inline]
fn __mx_has_na(&self) -> bool {
self.as_ref().is_some_and(<AsNumericVec as
crate::from_r::TryFromSexp>::__mx_has_na)
}
fn try_from_sexp(sexp: crate::SEXP) -> Result<Self, Self::Error> {
use crate::{SEXPTYPE, SexpExt};
if sexp.type_of() == SEXPTYPE::NILSXP { return Ok(None); }
<AsNumericVec as
crate::from_r::TryFromSexp>::try_from_sexp(sexp).map(Some)
}
unsafe fn try_from_sexp_unchecked(sexp: crate::SEXP)
-> Result<Self, Self::Error> {
use crate::{SEXPTYPE, SexpExt};
if sexp.type_of() == SEXPTYPE::NILSXP { return Ok(None); }
unsafe {
<AsNumericVec as
crate::from_r::TryFromSexp>::try_from_sexp_unchecked(sexp).map(Some)
}
}
}impl_option_try_from_sexp!(AsNumericVec);
159
160fn refused_by_no_na(value: Option<f64>) -> bool {
164 value.is_none_or(f64::is_nan)
165}
166impl IntoR for AsNumeric {
177 type Error = <Option<f64> as IntoR>::Error;
178 #[inline]
179 fn try_into_sexp(self) -> Result<SEXP, Self::Error> {
180 self.0.try_into_sexp()
181 }
182 #[inline]
183 unsafe fn try_into_sexp_unchecked(self) -> Result<SEXP, Self::Error> {
184 unsafe { self.0.try_into_sexp_unchecked() }
185 }
186 #[inline]
187 fn into_sexp(self) -> SEXP {
188 self.0.into_sexp()
189 }
190 #[inline]
191 unsafe fn into_sexp_unchecked(self) -> SEXP {
192 unsafe { self.0.into_sexp_unchecked() }
193 }
194}
195
196impl IntoR for AsNumericVec {
197 type Error = <Vec<Option<f64>> as IntoR>::Error;
198 #[inline]
199 fn try_into_sexp(self) -> Result<SEXP, Self::Error> {
200 self.0.try_into_sexp()
201 }
202 #[inline]
203 unsafe fn try_into_sexp_unchecked(self) -> Result<SEXP, Self::Error> {
204 unsafe { self.0.try_into_sexp_unchecked() }
205 }
206 #[inline]
207 fn into_sexp(self) -> SEXP {
208 self.0.into_sexp()
209 }
210 #[inline]
211 unsafe fn into_sexp_unchecked(self) -> SEXP {
212 unsafe { self.0.into_sexp_unchecked() }
213 }
214}
215
216impl crate::into_r::IntoR for Option<AsNumeric> {
type Error = <AsNumeric as crate::into_r::IntoR>::Error;
#[inline]
fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
match self {
Some(v) => v.try_into_sexp(),
None => Ok(crate::SEXP::nil()),
}
}
#[inline]
unsafe fn try_into_sexp_unchecked(self)
-> Result<crate::SEXP, Self::Error> {
match self {
Some(v) => unsafe { v.try_into_sexp_unchecked() },
None => Ok(crate::SEXP::nil()),
}
}
#[inline]
fn into_sexp(self) -> crate::SEXP {
match self { Some(v) => v.into_sexp(), None => crate::SEXP::nil(), }
}
#[inline]
unsafe fn into_sexp_unchecked(self) -> crate::SEXP {
match self {
Some(v) => unsafe { v.into_sexp_unchecked() },
None => crate::SEXP::nil(),
}
}
}impl_option_into_r_null!(AsNumeric);
217impl crate::into_r::IntoR for Option<AsNumericVec> {
type Error = <AsNumericVec as crate::into_r::IntoR>::Error;
#[inline]
fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
match self {
Some(v) => v.try_into_sexp(),
None => Ok(crate::SEXP::nil()),
}
}
#[inline]
unsafe fn try_into_sexp_unchecked(self)
-> Result<crate::SEXP, Self::Error> {
match self {
Some(v) => unsafe { v.try_into_sexp_unchecked() },
None => Ok(crate::SEXP::nil()),
}
}
#[inline]
fn into_sexp(self) -> crate::SEXP {
match self { Some(v) => v.into_sexp(), None => crate::SEXP::nil(), }
}
#[inline]
unsafe fn into_sexp_unchecked(self) -> crate::SEXP {
match self {
Some(v) => unsafe { v.into_sexp_unchecked() },
None => crate::SEXP::nil(),
}
}
}impl_option_into_r_null!(AsNumericVec);
218struct NotNumeric;
224
225type Rejected = BatchedErrors;
227
228fn check_type(actual: SEXPTYPE) -> Result<(), SexpError> {
231 match actual {
232 SEXPTYPE::REALSXP | SEXPTYPE::INTSXP | SEXPTYPE::LGLSXP | SEXPTYPE::STRSXP => Ok(()),
233 _ => Err(SexpTypeError {
234 expected: SEXPTYPE::REALSXP,
235 actual,
236 }
237 .into()),
238 }
239}
240
241fn read_numeric(sexp: SEXP) -> Result<Vec<Option<f64>>, SexpError> {
244 let actual = sexp.type_of();
245 check_type(actual)?;
246 match actual {
247 SEXPTYPE::REALSXP => {
248 let values: &[f64] = unsafe { sexp.as_slice() };
249 Ok(values.iter().map(|&v| (!v.is_na()).then_some(v)).collect())
250 }
251 SEXPTYPE::INTSXP if sexp.is_factor() => read_factor_labels(sexp),
252 SEXPTYPE::INTSXP => {
253 let values: &[i32] = unsafe { sexp.as_slice() };
254 Ok(values
255 .iter()
256 .map(|&v| (!v.is_na()).then(|| f64::from(v)))
257 .collect())
258 }
259 SEXPTYPE::LGLSXP => {
260 let values: &[RLogical] = unsafe { sexp.as_slice() };
261 Ok(values
262 .iter()
263 .map(|v| (!v.is_na()).then(|| f64::from(v.to_i32())))
264 .collect())
265 }
266 _ => {
268 let mut rejected = Rejected::default();
269 let values = map_strsxp_with(sexp, |charsxp, i| {
270 Ok(parse_charsxp(charsxp).unwrap_or_else(|NotNumeric| {
271 rejected.push(i, || unsafe { charsxp_to_str(charsxp) }.to_owned());
272 None
273 }))
274 })?;
275 finish(values, rejected)
276 }
277 }
278}
279
280fn read_factor_labels(sexp: SEXP) -> Result<Vec<Option<f64>>, SexpError> {
284 let levels = sexp.get_levels();
285 if levels.type_of() != SEXPTYPE::STRSXP {
286 return Err(SexpError::InvalidValue(
287 "malformed factor: levels are not a character vector".to_string(),
288 ));
289 }
290 let parsed: Vec<Result<Option<f64>, NotNumeric>> =
291 map_strsxp_with(levels, |charsxp, _| Ok(parse_charsxp(charsxp)))?;
292
293 let codes: &[i32] = unsafe { sexp.as_slice() };
294 let mut values = Vec::with_capacity(codes.len());
295 let mut rejected = Rejected::default();
296 for (i, &code) in codes.iter().enumerate() {
297 if code.is_na() {
298 values.push(None);
299 continue;
300 }
301 let level = usize::try_from(code)
302 .ok()
303 .and_then(|c| c.checked_sub(1))
304 .filter(|&k| k < parsed.len());
305 let Some(k) = level else {
306 return Err(SexpError::InvalidValue(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("malformed factor: code {2} at element {0} is outside levels 1..={1}",
i + 1, parsed.len(), code))
})format!(
307 "malformed factor: code {code} at element {} is outside levels 1..={}",
308 i + 1,
309 parsed.len()
310 )));
311 };
312 match parsed[k] {
313 Ok(v) => values.push(v),
314 Err(NotNumeric) => {
315 rejected.push(i, || level_label(levels, k));
316 values.push(None);
317 }
318 }
319 }
320 finish(values, rejected)
321}
322
323fn level_label(levels: SEXP, k: usize) -> String {
325 let charsxp = levels.string_elt(isize::try_from(k).expect("level index fits in R_xlen_t"));
326 unsafe { charsxp_to_str(charsxp) }.to_owned()
328}
329
330fn finish(values: Vec<Option<f64>>, rejected: Rejected) -> Result<Vec<Option<f64>>, SexpError> {
332 if rejected.is_empty() {
333 Ok(values)
334 } else {
335 Err(rejected.into_value_error("non-numeric"))
336 }
337}
338fn parse_charsxp(charsxp: SEXP) -> Result<Option<f64>, NotNumeric> {
344 if charsxp == SEXP::na_string() {
345 return Ok(None);
346 }
347 unsafe { parse_nul_terminated(charsxp_to_str(charsxp)) }
350}
351
352unsafe fn parse_nul_terminated(s: &str) -> Result<Option<f64>, NotNumeric> {
359 if is_blank(s) || is_na_token(s) {
361 return Ok(None);
362 }
363 let start = s.as_ptr().cast::<std::os::raw::c_char>();
364 let mut end: *mut std::os::raw::c_char = std::ptr::null_mut();
365 let value = unsafe { crate::sys::R_strtod(start, &mut end) };
368 let consumed = unsafe { end.cast_const().offset_from(start) };
370 let rest_blank = usize::try_from(consumed)
371 .ok()
372 .and_then(|n| s.as_bytes().get(n..))
373 .is_some_and(rest_is_blank);
374 if rest_blank {
375 Ok(Some(value))
376 } else {
377 Err(NotNumeric)
378 }
379}
380
381fn is_blank_char(c: char) -> bool {
386 c.is_whitespace() && !#[allow(non_exhaustive_omitted_patterns)] match c {
'\u{0085}' | '\u{00A0}' | '\u{2007}' | '\u{202F}' => true,
_ => false,
}matches!(c, '\u{0085}' | '\u{00A0}' | '\u{2007}' | '\u{202F}')
387}
388
389fn is_blank(s: &str) -> bool {
391 s.chars().all(is_blank_char)
392}
393
394fn is_na_token(s: &str) -> bool {
396 s.trim_matches(is_blank_char) == "NA"
397}
398
399fn rest_is_blank(rest: &[u8]) -> bool {
403 std::str::from_utf8(rest).is_ok_and(is_blank)
404}
405#[cfg(test)]
408mod tests {
409 use super::*;
410
411 #[test]
412 fn blank_strings_match_r_is_blank_string() {
413 for s in [
414 "",
415 " ",
416 " ",
417 "\t\n",
418 "\u{0B}\u{0C}\r",
419 "\u{3000}",
420 "\u{2028}",
421 "\u{2003}",
422 ] {
423 assert!(is_blank(s), "{s:?} should be blank");
424 }
425 for s in [
427 "\u{0085}", "\u{00A0}", "\u{2007}", "\u{202F}", "x", " 1 ", "\0",
428 ] {
429 assert!(!is_blank(s), "{s:?} should not be blank");
430 }
431 }
432
433 #[test]
434 fn na_token_allows_surrounding_blanks_only() {
435 for s in ["NA", " NA", "NA ", "\tNA\n"] {
436 assert!(is_na_token(s), "{s:?} should be the NA token");
437 }
438 for s in ["na", "N A", "NAN", "NA1", "<NA>", "\u{00A0}NA"] {
439 assert!(!is_na_token(s), "{s:?} should not be the NA token");
440 }
441 }
442
443 #[test]
445 fn no_na_refuses_none_and_nan() {
446 assert!(!AsNumeric(Some(2.5)).__mx_has_na());
447 assert!(!AsNumeric(Some(f64::INFINITY)).__mx_has_na());
448 assert!(AsNumeric(None).__mx_has_na());
449 assert!(AsNumeric(Some(f64::NAN)).__mx_has_na());
450
451 assert!(!AsNumericVec(vec![]).__mx_has_na());
452 assert!(!AsNumericVec(vec![Some(1.0), Some(-0.0)]).__mx_has_na());
453 assert!(AsNumericVec(vec![Some(1.0), None]).__mx_has_na());
454 assert!(AsNumericVec(vec![Some(f64::NAN), Some(1.0)]).__mx_has_na());
455 }
456
457 #[test]
461 fn no_na_is_forwarded_through_the_layers() {
462 use crate::Missing;
463 use std::collections::{BTreeMap, HashMap};
464
465 assert!(!None::<AsNumeric>.__mx_has_na());
466 assert!(!Some(AsNumeric(Some(1.0))).__mx_has_na());
467 assert!(Some(AsNumeric(None)).__mx_has_na());
468 assert!(Some(AsNumericVec(vec![None])).__mx_has_na());
469
470 assert!(!Missing::<AsNumeric>::Absent.__mx_has_na());
471 assert!(!Missing::Present(AsNumeric(Some(1.0))).__mx_has_na());
472 assert!(Missing::Present(AsNumeric(None)).__mx_has_na());
473 assert!(!Missing::<Option<AsNumeric>>::Present(None).__mx_has_na());
474 assert!(Missing::Present(Some(AsNumeric(None))).__mx_has_na());
475
476 assert!(!Err::<AsNumeric, ()>(()).__mx_has_na());
477 assert!(!Ok::<AsNumeric, ()>(AsNumeric(Some(1.0))).__mx_has_na());
478 assert!(Ok::<AsNumeric, ()>(AsNumeric(None)).__mx_has_na());
479
480 let mut map = HashMap::new();
481 map.insert("a".to_string(), AsNumeric(Some(1.0)));
482 assert!(!map.__mx_has_na());
483 map.insert("b".to_string(), AsNumeric(None));
484 assert!(map.__mx_has_na());
485 assert!(Some(map).__mx_has_na());
486 assert!(!None::<HashMap<String, AsNumeric>>.__mx_has_na());
487
488 let tree: BTreeMap<String, AsNumericVec> =
489 [("a".to_string(), AsNumericVec(vec![Some(1.0), None]))].into();
490 assert!(tree.__mx_has_na());
491
492 assert!(!Some(f64::NAN).__mx_has_na());
493 assert!(!None::<f64>.__mx_has_na());
494 assert!(!Missing::<f64>::Absent.__mx_has_na());
495 assert!(!vec![None::<f64>].__mx_has_na());
496 }
497
498 #[test]
499 fn unread_rest_must_be_blank_utf8() {
500 assert!(rest_is_blank(b""));
501 assert!(rest_is_blank(b" \t"));
502 assert!(rest_is_blank("\u{3000}".as_bytes()));
503 assert!(!rest_is_blank(b" x"));
504 assert!(!rest_is_blank(b"\xa0"), "split multi-byte character");
505 }
506}