1use std::collections::HashSet;
5
6use super::CallArgs;
7use crate::interpreter::value::*;
8use minir_macros::builtin;
9
10#[builtin(name = "toml_parse", min_args = 1, namespace = "utils")]
28fn builtin_toml_parse(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
29 let call_args = CallArgs::new(args, named);
30 let text = call_args.string("text", 0)?;
31
32 let doc: toml_edit::DocumentMut = text.parse().map_err(|e: toml_edit::TomlError| {
33 RError::new(RErrorKind::Other, format!("TOML parse error: {e}"))
34 })?;
35
36 table_to_rvalue(doc.as_table())
37}
38
39#[builtin(name = "toml_serialize", min_args = 1, namespace = "utils")]
54fn builtin_toml_serialize(args: &[RValue], _named: &[(String, RValue)]) -> Result<RValue, RError> {
55 let value = args
56 .first()
57 .ok_or_else(|| RError::new(RErrorKind::Argument, "argument 'x' is missing".to_string()))?;
58
59 let RValue::List(list) = value else {
60 return Err(RError::new(
61 RErrorKind::Type,
62 "toml_serialize() requires a named list as input".to_string(),
63 ));
64 };
65
66 let table = rlist_to_table(list)?;
67 let mut doc = toml_edit::DocumentMut::new();
68 for (key, item) in table.iter() {
70 doc.insert(key, item.clone());
71 }
72
73 Ok(RValue::vec(Vector::Character(
74 vec![Some(doc.to_string())].into(),
75 )))
76}
77
78#[builtin(name = "read.toml", min_args = 1, namespace = "utils")]
87fn builtin_read_toml(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
88 let call_args = CallArgs::new(args, named);
89 let path = call_args.string("file", 0)?;
90
91 let content = std::fs::read_to_string(&path).map_err(|e| {
92 RError::new(
93 RErrorKind::Other,
94 format!("cannot open file '{}': {}", path, e),
95 )
96 })?;
97
98 let doc: toml_edit::DocumentMut = content.parse().map_err(|e: toml_edit::TomlError| {
99 RError::new(
100 RErrorKind::Other,
101 format!("TOML parse error in '{}': {}", path, e),
102 )
103 })?;
104
105 table_to_rvalue(doc.as_table())
106}
107
108#[builtin(name = "write.toml", min_args = 2, namespace = "utils")]
118fn builtin_write_toml(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
119 let call_args = CallArgs::new(args, named);
120
121 let value = args
122 .first()
123 .ok_or_else(|| RError::new(RErrorKind::Argument, "argument 'x' is missing".to_string()))?;
124 let path = call_args.string("file", 1)?;
125
126 let RValue::List(list) = value else {
127 return Err(RError::new(
128 RErrorKind::Type,
129 "write.toml() requires a named list as input".to_string(),
130 ));
131 };
132
133 let table = rlist_to_table(list)?;
134 let mut doc = toml_edit::DocumentMut::new();
135 for (key, item) in table.iter() {
136 doc.insert(key, item.clone());
137 }
138
139 std::fs::write(&path, doc.to_string()).map_err(|e| {
140 RError::new(
141 RErrorKind::Other,
142 format!("cannot write to file '{}': {}", path, e),
143 )
144 })?;
145
146 Ok(RValue::Null)
147}
148
149fn table_to_rvalue(table: &toml_edit::Table) -> Result<RValue, RError> {
155 let mut entries: Vec<(Option<String>, RValue)> = Vec::with_capacity(table.len());
156 for (key, item) in table.iter() {
157 entries.push((Some(key.to_string()), item_to_rvalue(item)?));
158 }
159 Ok(RValue::List(RList::new(entries)))
160}
161
162fn item_to_rvalue(item: &toml_edit::Item) -> Result<RValue, RError> {
164 match item {
165 toml_edit::Item::None => Ok(RValue::Null),
166 toml_edit::Item::Value(v) => value_to_rvalue(v),
167 toml_edit::Item::Table(t) => table_to_rvalue(t),
168 toml_edit::Item::ArrayOfTables(aot) => array_of_tables_to_rvalue(aot),
169 }
170}
171
172fn value_to_rvalue(value: &toml_edit::Value) -> Result<RValue, RError> {
174 match value {
175 toml_edit::Value::String(s) => Ok(RValue::vec(Vector::Character(
176 vec![Some(s.value().to_string())].into(),
177 ))),
178 toml_edit::Value::Integer(i) => {
179 Ok(RValue::vec(Vector::Integer(vec![Some(*i.value())].into())))
180 }
181 toml_edit::Value::Float(f) => {
182 Ok(RValue::vec(Vector::Double(vec![Some(*f.value())].into())))
183 }
184 toml_edit::Value::Boolean(b) => {
185 Ok(RValue::vec(Vector::Logical(vec![Some(*b.value())].into())))
186 }
187 toml_edit::Value::Datetime(dt) => {
188 Ok(RValue::vec(Vector::Character(
190 vec![Some(dt.value().to_string())].into(),
191 )))
192 }
193 toml_edit::Value::Array(arr) => toml_array_to_rvalue(arr),
194 toml_edit::Value::InlineTable(t) => inline_table_to_rvalue(t),
195 }
196}
197
198fn inline_table_to_rvalue(table: &toml_edit::InlineTable) -> Result<RValue, RError> {
200 let mut entries: Vec<(Option<String>, RValue)> = Vec::with_capacity(table.len());
201 for (key, value) in table.iter() {
202 entries.push((Some(key.to_string()), value_to_rvalue(value)?));
203 }
204 Ok(RValue::List(RList::new(entries)))
205}
206
207fn toml_array_to_rvalue(arr: &toml_edit::Array) -> Result<RValue, RError> {
213 let items: Vec<&toml_edit::Value> = arr.iter().collect();
214
215 if items.is_empty() {
216 return Ok(RValue::List(RList::new(vec![])));
217 }
218
219 if let Some(vec) = try_toml_array_as_vector(&items) {
221 return Ok(vec);
222 }
223
224 if let Some(df) = try_toml_array_as_dataframe(&items)? {
226 return Ok(df);
227 }
228
229 let elements: Result<Vec<(Option<String>, RValue)>, RError> = items
231 .iter()
232 .map(|v| Ok((None, value_to_rvalue(v)?)))
233 .collect();
234 Ok(RValue::List(RList::new(elements?)))
235}
236
237fn try_toml_array_as_vector(items: &[&toml_edit::Value]) -> Option<RValue> {
240 let mut has_string = false;
242 let mut has_int = false;
243 let mut has_float = false;
244 let mut has_bool = false;
245 let mut has_datetime = false;
246 let mut has_non_scalar = false;
247
248 for item in items {
249 match item {
250 toml_edit::Value::String(_) => has_string = true,
251 toml_edit::Value::Integer(_) => has_int = true,
252 toml_edit::Value::Float(_) => has_float = true,
253 toml_edit::Value::Boolean(_) => has_bool = true,
254 toml_edit::Value::Datetime(_) => has_datetime = true,
255 toml_edit::Value::Array(_) | toml_edit::Value::InlineTable(_) => {
256 has_non_scalar = true;
257 }
258 }
259 }
260
261 if has_non_scalar {
262 return None;
263 }
264
265 if has_datetime {
267 let result: Vec<Option<String>> = items
268 .iter()
269 .map(|v| match v {
270 toml_edit::Value::Datetime(dt) => Some(dt.value().to_string()),
271 toml_edit::Value::String(s) => Some(s.value().to_string()),
272 _ => Some(format!("{}", v)),
273 })
274 .collect();
275 return Some(RValue::vec(Vector::Character(result.into())));
276 }
277
278 if has_string {
280 let result: Vec<Option<String>> = items
281 .iter()
282 .map(|v| match v {
283 toml_edit::Value::String(s) => Some(s.value().to_string()),
284 toml_edit::Value::Boolean(b) => {
285 Some(if *b.value() { "TRUE" } else { "FALSE" }.to_string())
286 }
287 toml_edit::Value::Integer(i) => Some(i.value().to_string()),
288 toml_edit::Value::Float(f) => Some(f.value().to_string()),
289 _ => None,
290 })
291 .collect();
292 return Some(RValue::vec(Vector::Character(result.into())));
293 }
294
295 if has_bool && !has_int && !has_float {
297 let result: Vec<Option<bool>> = items
298 .iter()
299 .map(|v| match v {
300 toml_edit::Value::Boolean(b) => Some(*b.value()),
301 _ => None,
302 })
303 .collect();
304 return Some(RValue::vec(Vector::Logical(result.into())));
305 }
306
307 if has_float {
309 let result: Vec<Option<f64>> = items
310 .iter()
311 .map(|v| match v {
312 toml_edit::Value::Float(f) => Some(*f.value()),
313 toml_edit::Value::Integer(i) => {
314 #[allow(clippy::cast_precision_loss)]
316 Some(*i.value() as f64)
317 }
318 toml_edit::Value::Boolean(b) => Some(if *b.value() { 1.0 } else { 0.0 }),
319 _ => None,
320 })
321 .collect();
322 return Some(RValue::vec(Vector::Double(result.into())));
323 }
324
325 if has_int {
327 let result: Vec<Option<i64>> = items
328 .iter()
329 .map(|v| match v {
330 toml_edit::Value::Integer(i) => Some(*i.value()),
331 toml_edit::Value::Boolean(b) => Some(i64::from(*b.value())),
332 _ => None,
333 })
334 .collect();
335 return Some(RValue::vec(Vector::Integer(result.into())));
336 }
337
338 if has_bool {
340 let result: Vec<Option<i64>> = items
341 .iter()
342 .map(|v| match v {
343 toml_edit::Value::Boolean(b) => Some(i64::from(*b.value())),
344 _ => None,
345 })
346 .collect();
347 return Some(RValue::vec(Vector::Integer(result.into())));
348 }
349
350 None
351}
352
353fn try_toml_array_as_dataframe(items: &[&toml_edit::Value]) -> Result<Option<RValue>, RError> {
356 let mut tables: Vec<&toml_edit::InlineTable> = Vec::with_capacity(items.len());
358 for item in items {
359 if let toml_edit::Value::InlineTable(t) = item {
360 tables.push(t);
361 } else {
362 return Ok(None);
363 }
364 }
365
366 if tables.is_empty() {
367 return Ok(None);
368 }
369
370 let first_keys: HashSet<&str> = tables[0].iter().map(|(k, _)| k).collect();
372 for table in &tables[1..] {
373 let keys: HashSet<&str> = table.iter().map(|(k, _)| k).collect();
374 if keys != first_keys {
375 return Ok(None);
376 }
377 }
378
379 let col_names: Vec<String> = tables[0].iter().map(|(k, _)| k.to_string()).collect();
381 let nrows = tables.len();
382
383 let mut list_cols: Vec<(Option<String>, RValue)> = Vec::new();
385 for col_name in &col_names {
386 let vals: Vec<&toml_edit::Value> = tables
387 .iter()
388 .map(|t| {
389 t.get(col_name.as_str())
390 .expect("key verified to exist in all tables")
391 })
392 .collect();
393 let col_value = coerce_toml_column(&vals)?;
394 list_cols.push((Some(col_name.clone()), col_value));
395 }
396
397 let mut list = RList::new(list_cols);
398 list.set_attr(
399 "class".to_string(),
400 RValue::vec(Vector::Character(
401 vec![Some("data.frame".to_string())].into(),
402 )),
403 );
404 list.set_attr(
405 "names".to_string(),
406 RValue::vec(Vector::Character(
407 col_names.into_iter().map(Some).collect::<Vec<_>>().into(),
408 )),
409 );
410 let row_names: Vec<Option<i64>> = (1..=i64::try_from(nrows)?).map(Some).collect();
411 list.set_attr(
412 "row.names".to_string(),
413 RValue::vec(Vector::Integer(row_names.into())),
414 );
415
416 Ok(Some(RValue::List(list)))
417}
418
419fn array_of_tables_to_rvalue(aot: &toml_edit::ArrayOfTables) -> Result<RValue, RError> {
424 let tables: Vec<&toml_edit::Table> = aot.iter().collect();
425
426 if tables.is_empty() {
427 return Ok(RValue::List(RList::new(vec![])));
428 }
429
430 let first_keys: HashSet<&str> = tables[0].iter().map(|(k, _)| k).collect();
432 let all_same_keys = tables[1..]
433 .iter()
434 .all(|t| t.iter().map(|(k, _)| k).collect::<HashSet<_>>() == first_keys);
435
436 if all_same_keys && !first_keys.is_empty() {
437 let col_names: Vec<String> = tables[0].iter().map(|(k, _)| k.to_string()).collect();
438 let nrows = tables.len();
439
440 let mut list_cols: Vec<(Option<String>, RValue)> = Vec::new();
441 for col_name in &col_names {
442 let vals: Vec<&toml_edit::Item> = tables
443 .iter()
444 .map(|t| {
445 t.get(col_name.as_str())
446 .expect("key verified to exist in all tables")
447 })
448 .collect();
449 let col_value = coerce_toml_item_column(&vals)?;
450 list_cols.push((Some(col_name.clone()), col_value));
451 }
452
453 let mut list = RList::new(list_cols);
454 list.set_attr(
455 "class".to_string(),
456 RValue::vec(Vector::Character(
457 vec![Some("data.frame".to_string())].into(),
458 )),
459 );
460 list.set_attr(
461 "names".to_string(),
462 RValue::vec(Vector::Character(
463 col_names.into_iter().map(Some).collect::<Vec<_>>().into(),
464 )),
465 );
466 let row_names: Vec<Option<i64>> = (1..=i64::try_from(nrows)?).map(Some).collect();
467 list.set_attr(
468 "row.names".to_string(),
469 RValue::vec(Vector::Integer(row_names.into())),
470 );
471
472 return Ok(RValue::List(list));
473 }
474
475 let elements: Result<Vec<(Option<String>, RValue)>, RError> = tables
477 .iter()
478 .map(|t| Ok((None, table_to_rvalue(t)?)))
479 .collect();
480 Ok(RValue::List(RList::new(elements?)))
481}
482
483fn coerce_toml_column(vals: &[&toml_edit::Value]) -> Result<RValue, RError> {
485 let mut has_string = false;
486 let mut has_int = false;
487 let mut has_float = false;
488 let mut has_bool = false;
489 let mut has_datetime = false;
490 let mut has_complex = false; for v in vals {
493 match v {
494 toml_edit::Value::String(_) => has_string = true,
495 toml_edit::Value::Integer(_) => has_int = true,
496 toml_edit::Value::Float(_) => has_float = true,
497 toml_edit::Value::Boolean(_) => has_bool = true,
498 toml_edit::Value::Datetime(_) => has_datetime = true,
499 _ => has_complex = true,
500 }
501 }
502
503 if has_complex {
505 let elements: Result<Vec<(Option<String>, RValue)>, RError> = vals
506 .iter()
507 .map(|v| Ok((None, value_to_rvalue(v)?)))
508 .collect();
509 return Ok(RValue::List(RList::new(elements?)));
510 }
511
512 if has_string || has_datetime {
514 let result: Vec<Option<String>> = vals
515 .iter()
516 .map(|v| match v {
517 toml_edit::Value::String(s) => Some(s.value().to_string()),
518 toml_edit::Value::Datetime(dt) => Some(dt.value().to_string()),
519 toml_edit::Value::Boolean(b) => {
520 Some(if *b.value() { "TRUE" } else { "FALSE" }.to_string())
521 }
522 toml_edit::Value::Integer(i) => Some(i.value().to_string()),
523 toml_edit::Value::Float(f) => Some(f.value().to_string()),
524 _ => None,
525 })
526 .collect();
527 return Ok(RValue::vec(Vector::Character(result.into())));
528 }
529
530 if has_bool && !has_int && !has_float {
532 let result: Vec<Option<bool>> = vals
533 .iter()
534 .map(|v| match v {
535 toml_edit::Value::Boolean(b) => Some(*b.value()),
536 _ => None,
537 })
538 .collect();
539 return Ok(RValue::vec(Vector::Logical(result.into())));
540 }
541
542 if has_float {
544 let result: Vec<Option<f64>> = vals
545 .iter()
546 .map(|v| match v {
547 toml_edit::Value::Float(f) => Some(*f.value()),
548 toml_edit::Value::Integer(i) =>
549 {
550 #[allow(clippy::cast_precision_loss)]
551 Some(*i.value() as f64)
552 }
553 toml_edit::Value::Boolean(b) => Some(if *b.value() { 1.0 } else { 0.0 }),
554 _ => None,
555 })
556 .collect();
557 return Ok(RValue::vec(Vector::Double(result.into())));
558 }
559
560 if has_int {
562 let result: Vec<Option<i64>> = vals
563 .iter()
564 .map(|v| match v {
565 toml_edit::Value::Integer(i) => Some(*i.value()),
566 toml_edit::Value::Boolean(b) => Some(i64::from(*b.value())),
567 _ => None,
568 })
569 .collect();
570 return Ok(RValue::vec(Vector::Integer(result.into())));
571 }
572
573 if has_bool {
575 let result: Vec<Option<i64>> = vals
576 .iter()
577 .map(|v| match v {
578 toml_edit::Value::Boolean(b) => Some(i64::from(*b.value())),
579 _ => None,
580 })
581 .collect();
582 return Ok(RValue::vec(Vector::Integer(result.into())));
583 }
584
585 Ok(RValue::vec(Vector::Logical(
587 Vec::<Option<bool>>::new().into(),
588 )))
589}
590
591fn coerce_toml_item_column(vals: &[&toml_edit::Item]) -> Result<RValue, RError> {
593 let mut values: Vec<&toml_edit::Value> = Vec::with_capacity(vals.len());
595 let mut has_non_value = false;
596
597 for item in vals {
598 if let Some(v) = item.as_value() {
599 values.push(v);
600 } else {
601 has_non_value = true;
602 break;
603 }
604 }
605
606 if has_non_value {
607 let elements: Result<Vec<(Option<String>, RValue)>, RError> = vals
609 .iter()
610 .map(|item| Ok((None, item_to_rvalue(item)?)))
611 .collect();
612 return Ok(RValue::List(RList::new(elements?)));
613 }
614
615 coerce_toml_column(&values)
616}
617
618fn rlist_to_table(list: &RList) -> Result<toml_edit::Table, RError> {
624 let mut table = toml_edit::Table::new();
625
626 for (name, value) in &list.values {
627 let key = name.as_ref().ok_or_else(|| {
628 RError::new(
629 RErrorKind::Type,
630 "TOML requires all list elements to be named".to_string(),
631 )
632 })?;
633
634 match value {
635 RValue::Null => {
636 }
638 RValue::Vector(rv) => {
639 table.insert(key, toml_edit::Item::Value(vector_to_toml(&rv.inner)?));
640 }
641 RValue::List(inner_list) => {
642 if is_simple_list(inner_list) {
644 table.insert(
645 key,
646 toml_edit::Item::Value(toml_edit::Value::InlineTable(
647 rlist_to_inline_table(inner_list)?,
648 )),
649 );
650 } else {
651 let subtable = rlist_to_table(inner_list)?;
652 table.insert(key, toml_edit::Item::Table(subtable));
653 }
654 }
655 RValue::Function(_) => {
656 return Err(RError::new(
657 RErrorKind::Type,
658 format!("cannot convert function to TOML (key '{key}')"),
659 ));
660 }
661 RValue::Environment(_) => {
662 return Err(RError::new(
663 RErrorKind::Type,
664 format!("cannot convert environment to TOML (key '{key}')"),
665 ));
666 }
667 RValue::Language(_) => {
668 return Err(RError::new(
669 RErrorKind::Type,
670 format!("cannot convert language object to TOML (key '{key}')"),
671 ));
672 }
673 RValue::Promise(_) => {
674 return Err(RError::new(
675 RErrorKind::Type,
676 format!("cannot convert promise to TOML (key '{key}') — force it first"),
677 ));
678 }
679 }
680 }
681
682 Ok(table)
683}
684
685fn is_simple_list(list: &RList) -> bool {
688 list.values.iter().all(|(_, v)| match v {
689 RValue::Vector(rv) => rv.inner.len() <= 1,
690 RValue::Null => true,
691 _ => false,
692 })
693}
694
695fn rlist_to_inline_table(list: &RList) -> Result<toml_edit::InlineTable, RError> {
697 let mut table = toml_edit::InlineTable::new();
698
699 for (name, value) in &list.values {
700 let key = name.as_ref().ok_or_else(|| {
701 RError::new(
702 RErrorKind::Type,
703 "TOML requires all list elements to be named".to_string(),
704 )
705 })?;
706
707 match value {
708 RValue::Null => {}
709 RValue::Vector(rv) => {
710 table.insert(key, vector_to_toml(&rv.inner)?);
711 }
712 RValue::List(inner) => {
713 table.insert(
714 key,
715 toml_edit::Value::InlineTable(rlist_to_inline_table(inner)?),
716 );
717 }
718 _ => {
719 return Err(RError::new(
720 RErrorKind::Type,
721 format!("cannot convert {} to TOML (key '{key}')", value.type_name()),
722 ));
723 }
724 }
725 }
726
727 Ok(table)
728}
729
730fn vector_to_toml(vec: &Vector) -> Result<toml_edit::Value, RError> {
733 match vec {
734 Vector::Logical(v) => {
735 if v.len() == 1 {
736 match v[0] {
737 Some(b) => Ok(toml_edit::Value::from(b)),
738 None => Err(RError::new(
739 RErrorKind::Type,
740 "TOML does not support NA values".to_string(),
741 )),
742 }
743 } else {
744 let mut arr = toml_edit::Array::new();
745 for item in v.iter() {
746 match item {
747 Some(b) => arr.push_formatted(toml_edit::Value::from(*b)),
748 None => {
749 return Err(RError::new(
750 RErrorKind::Type,
751 "TOML does not support NA values".to_string(),
752 ))
753 }
754 }
755 }
756 Ok(toml_edit::Value::Array(arr))
757 }
758 }
759 Vector::Integer(v) => {
760 if v.len() == 1 {
761 match v.get_opt(0) {
762 Some(i) => Ok(toml_edit::Value::from(i)),
763 None => Err(RError::new(
764 RErrorKind::Type,
765 "TOML does not support NA values".to_string(),
766 )),
767 }
768 } else {
769 let mut arr = toml_edit::Array::new();
770 for item in v.iter_opt() {
771 match item {
772 Some(i) => arr.push_formatted(toml_edit::Value::from(i)),
773 None => {
774 return Err(RError::new(
775 RErrorKind::Type,
776 "TOML does not support NA values".to_string(),
777 ))
778 }
779 }
780 }
781 Ok(toml_edit::Value::Array(arr))
782 }
783 }
784 Vector::Double(v) => {
785 if v.len() == 1 {
786 match v.get_opt(0) {
787 Some(f) => double_to_toml(f),
788 None => Err(RError::new(
789 RErrorKind::Type,
790 "TOML does not support NA values".to_string(),
791 )),
792 }
793 } else {
794 let mut arr = toml_edit::Array::new();
795 for item in v.iter_opt() {
796 match item {
797 Some(f) => arr.push_formatted(double_to_toml(f)?),
798 None => {
799 return Err(RError::new(
800 RErrorKind::Type,
801 "TOML does not support NA values".to_string(),
802 ))
803 }
804 }
805 }
806 Ok(toml_edit::Value::Array(arr))
807 }
808 }
809 Vector::Character(v) => {
810 if v.len() == 1 {
811 match &v[0] {
812 Some(s) => Ok(toml_edit::Value::from(s.as_str())),
813 None => Err(RError::new(
814 RErrorKind::Type,
815 "TOML does not support NA values".to_string(),
816 )),
817 }
818 } else {
819 let mut arr = toml_edit::Array::new();
820 for item in v.iter() {
821 match item {
822 Some(s) => arr.push_formatted(toml_edit::Value::from(s.as_str())),
823 None => {
824 return Err(RError::new(
825 RErrorKind::Type,
826 "TOML does not support NA values".to_string(),
827 ))
828 }
829 }
830 }
831 Ok(toml_edit::Value::Array(arr))
832 }
833 }
834 Vector::Complex(_) => Err(RError::new(
835 RErrorKind::Type,
836 "cannot convert complex numbers to TOML".to_string(),
837 )),
838 Vector::Raw(_) => Err(RError::new(
839 RErrorKind::Type,
840 "cannot convert raw bytes to TOML".to_string(),
841 )),
842 }
843}
844
845fn double_to_toml(f: f64) -> Result<toml_edit::Value, RError> {
847 if f.is_nan() {
848 Ok(toml_edit::Value::from(f64::NAN))
849 } else if f.is_infinite() {
850 if f.is_sign_positive() {
851 Ok(toml_edit::Value::from(f64::INFINITY))
852 } else {
853 Ok(toml_edit::Value::from(f64::NEG_INFINITY))
854 }
855 } else {
856 Ok(toml_edit::Value::from(f))
857 }
858}
859
860