1use bstr::{BString, ByteVec};
4use codemap::Span;
5use lexical_core::format::CXX_LITERAL;
6use serde::Deserialize;
7use std::cmp::Ordering;
8use std::fmt::Display;
9use std::io::Write;
10use std::num::{NonZeroI32, NonZeroUsize};
11use std::path::PathBuf;
12use std::rc::Rc;
13use std::sync::LazyLock;
14
15#[cfg(feature = "arbitrary")]
16mod arbitrary;
17mod attrs;
18mod builtin;
19mod function;
20mod json;
21mod list;
22mod path;
23mod string;
24mod thunk;
25
26use crate::AddContext;
27use crate::errors::{CatchableErrorKind, ErrorKind};
28use crate::opcode::StackIdx;
29use crate::vm::generators::{self, GenCo};
30pub use attrs::NixAttrs;
31pub use builtin::{Builtin, BuiltinResult};
32pub(crate) use function::Formals;
33pub use function::{Closure, Lambda};
34pub use list::NixList;
35pub use path::canon_path;
36pub use string::{NixContext, NixContextElement, NixString};
37pub use thunk::Thunk;
38
39pub use self::thunk::ThunkSet;
40
41#[warn(variant_size_differences)]
42#[derive(Clone, Debug, Deserialize)]
43#[serde(untagged)]
44pub enum Value {
45 Null,
46 Bool(bool),
47 Integer(i64),
48 Float(f64),
49 String(NixString),
50
51 #[serde(skip)]
52 Path(Box<PathBuf>),
53 Attrs(NixAttrs),
54 List(NixList),
55
56 #[serde(skip)]
57 Closure(Rc<Closure>), #[serde(skip)]
60 Builtin(Builtin),
61
62 #[serde(skip_deserializing)]
65 Thunk(Thunk),
66
67 #[serde(skip)]
69 AttrNotFound,
70
71 #[serde(skip)]
73 Blueprint(Rc<Lambda>),
74
75 #[serde(skip)]
76 DeferredUpvalue(StackIdx),
77 #[serde(skip)]
78 UnresolvedPath(Box<PathBuf>),
79
80 #[serde(skip)]
81 FinaliseRequest(bool),
82
83 #[serde(skip)]
84 Catchable(Box<CatchableErrorKind>),
85}
86
87impl From<CatchableErrorKind> for Value {
88 #[inline]
89 fn from(c: CatchableErrorKind) -> Value {
90 Value::Catchable(Box::new(c))
91 }
92}
93
94impl<V> From<Result<V, CatchableErrorKind>> for Value
95where
96 Value: From<V>,
97{
98 #[inline]
99 fn from(v: Result<V, CatchableErrorKind>) -> Value {
100 match v {
101 Ok(v) => v.into(),
102 Err(e) => Value::Catchable(Box::new(e)),
103 }
104 }
105}
106
107static WRITE_FLOAT_OPTIONS: LazyLock<lexical_core::WriteFloatOptions> = LazyLock::new(|| {
108 lexical_core::WriteFloatOptionsBuilder::new()
109 .trim_floats(true)
110 .round_mode(lexical_core::write_float_options::RoundMode::Round)
111 .positive_exponent_break(Some(NonZeroI32::new(5).unwrap()))
112 .max_significant_digits(Some(NonZeroUsize::new(6).unwrap()))
113 .build()
114 .unwrap()
115});
116
117macro_rules! gen_cast {
128 ( $name:ident, $type:ty, $expected:expr, $variant:pat, $result:expr ) => {
129 pub fn $name(&self) -> Result<$type, ErrorKind> {
130 match self {
131 $variant => Ok($result),
132 Value::Thunk(thunk) => Self::$name(&thunk.value()),
133 other => Err(type_error($expected, &other)),
134 }
135 }
136 };
137}
138
139macro_rules! gen_cast_mut {
142 ( $name:ident, $type:ty, $expected:expr, $variant:ident) => {
143 pub fn $name(&mut self) -> Result<&mut $type, ErrorKind> {
144 match self {
145 Value::$variant(x) => Ok(x),
146 other => Err(type_error($expected, &other)),
147 }
148 }
149 };
150}
151
152macro_rules! gen_is {
154 ( $name:ident, $variant:pat ) => {
155 pub fn $name(&self) -> bool {
156 match self {
157 $variant => true,
158 Value::Thunk(thunk) => Self::$name(&thunk.value()),
159 _ => false,
160 }
161 }
162 };
163}
164
165#[derive(Clone, Copy, PartialEq, Eq, Debug)]
167pub struct CoercionKind {
168 pub strong: bool,
178
179 pub import_paths: bool,
183}
184
185impl From<CoercionKind> for u8 {
186 fn from(k: CoercionKind) -> u8 {
187 k.strong as u8 | ((k.import_paths as u8) << 1)
188 }
189}
190
191impl From<u8> for CoercionKind {
192 fn from(byte: u8) -> Self {
193 CoercionKind {
194 strong: byte & 0x01 != 0,
195 import_paths: byte & 0x02 != 0,
196 }
197 }
198}
199
200impl<T> From<T> for Value
201where
202 T: Into<NixString>,
203{
204 fn from(t: T) -> Self {
205 Self::String(t.into())
206 }
207}
208
209#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
213pub enum PointerEquality {
214 ForbidAll,
216
217 AllowNested,
219
220 AllowAll,
222}
223
224impl Value {
225 pub fn attrs(attrs: NixAttrs) -> Self {
227 Self::Attrs(attrs)
228 }
229
230 pub(super) async fn deep_force(self, co: GenCo, span: Span) -> Result<Value, ErrorKind> {
235 if let Some(v) = Self::deep_force_(self.clone(), co, span).await? {
236 Ok(v)
237 } else {
238 Ok(self)
239 }
240 }
241
242 async fn deep_force_(myself: Value, co: GenCo, span: Span) -> Result<Option<Value>, ErrorKind> {
244 let mut vals = vec![myself];
246
247 let mut thunk_set: ThunkSet = Default::default();
248
249 loop {
250 let v = if let Some(v) = vals.pop() {
251 v
252 } else {
253 return Ok(None);
254 };
255
256 let value = if let Value::Thunk(t) = &v {
259 if !thunk_set.insert(t) {
260 continue;
261 }
262 Thunk::force_(t.clone(), &co, span).await?
263 } else {
264 v
265 };
266
267 match value {
268 Value::Null
270 | Value::Bool(_)
271 | Value::Integer(_)
272 | Value::Float(_)
273 | Value::String(_)
274 | Value::Path(_)
275 | Value::Closure(_)
276 | Value::Builtin(_) => continue,
277
278 Value::List(list) => {
279 for val in list.into_iter().rev() {
280 vals.push(val);
281 }
282 continue;
283 }
284
285 Value::Attrs(attrs) => {
286 for (_, val) in attrs.into_iter_sorted().rev() {
287 vals.push(val);
288 }
289 continue;
290 }
291
292 Value::Thunk(_) => panic!("Snix bug: force_value() returned a thunk"),
293
294 Value::Catchable(_) => return Ok(Some(value)),
295
296 Value::AttrNotFound
297 | Value::Blueprint(_)
298 | Value::DeferredUpvalue(_)
299 | Value::UnresolvedPath(_)
300 | Value::FinaliseRequest(_) => panic!(
301 "Snix bug: internal value left on stack: {}",
302 value.type_of()
303 ),
304 }
305 }
306 }
307
308 pub async fn coerce_to_string(
309 self,
310 co: GenCo,
311 kind: CoercionKind,
312 span: Span,
313 ) -> Result<Value, ErrorKind> {
314 self.coerce_to_string_(&co, kind, span).await
315 }
316
317 pub async fn coerce_to_string_(
320 self,
321 co: &GenCo,
322 kind: CoercionKind,
323 span: Span,
324 ) -> Result<Value, ErrorKind> {
325 enum Item {
326 Value(Value),
327 WithSeparator(Value),
328 Space,
329 }
330
331 let mut out = BString::default();
332 let mut out_context: NixContext = NixContext::new();
333
334 let mut worklist = vec![Item::Value(self)];
335 while let Some(item) = worklist.pop() {
336 let value = match item {
337 Item::Space => {
338 out.push(b' ');
339 continue;
340 }
341 Item::Value(value) => value.force(co, span).await?,
342 Item::WithSeparator(value) => {
343 let value = value.force(co, span).await?;
344
345 if !matches!(&value, Value::List(list) if list.is_empty()) {
349 worklist.push(Item::Space);
350 }
351 value
352 }
353 };
354
355 match (value, kind) {
356 (Value::String(mut s), _) => {
358 if let Some(ctx) = s.take_context() {
359 out_context.extend(*ctx);
360 }
361
362 out.push_str(s);
363 }
364
365 (
371 Value::Path(p),
372 CoercionKind {
373 import_paths: true, ..
374 },
375 ) => {
376 let imported = generators::request_path_import(co, *p).await;
377 out_context = out_context.append(NixContextElement::Plain(
380 imported.to_string_lossy().to_string(),
381 ));
382 out.push_str(imported.as_os_str().as_encoded_bytes());
383 }
384 (
385 Value::Path(p),
386 CoercionKind {
387 import_paths: false,
388 ..
389 },
390 ) => {
391 out.push_str(p.as_os_str().as_encoded_bytes());
392 }
393
394 (Value::Attrs(attrs), kind) => {
399 if let Some(to_string) = attrs.select("__toString") {
400 let callable = to_string.clone().force(co, span).await?;
401
402 generators::request_stack_push(co, Value::Attrs(attrs.clone())).await;
405
406 let result = generators::request_call(co, callable).await;
408
409 worklist.push(Item::Value(result));
413 } else if let Some(out_path) = attrs.select("outPath") {
414 worklist.push(Item::Value(out_path.clone()));
415 } else {
416 return Err(ErrorKind::NotCoercibleToString { from: "set", kind });
417 }
418 }
419
420 (Value::Null, CoercionKind { strong: true, .. })
422 | (Value::Bool(false), CoercionKind { strong: true, .. }) => {
423 }
425 (Value::Bool(true), CoercionKind { strong: true, .. }) => {
426 out.push_str("1");
427 }
428 (Value::Integer(i), CoercionKind { strong: true, .. }) => {
429 write!(&mut out, "{i}").unwrap();
430 }
431 (Value::Float(f), CoercionKind { strong: true, .. }) => {
432 write!(&mut out, "{f:.6}").unwrap();
435 }
436
437 (Value::List(list), CoercionKind { strong: true, .. }) => {
439 let len = list.len();
440 for (i, value) in list.into_iter().enumerate().rev() {
441 if i < len - 1 {
442 worklist.push(Item::WithSeparator(value))
443 } else {
444 worklist.push(Item::Value(value))
445 }
446 }
447 }
448
449 (Value::Thunk(_), _) => panic!("Snix bug: force returned unforced thunk"),
450
451 val @ (Value::Closure(_), _)
452 | val @ (Value::Builtin(_), _)
453 | val @ (Value::Null, _)
454 | val @ (Value::Bool(_), _)
455 | val @ (Value::Integer(_), _)
456 | val @ (Value::Float(_), _)
457 | val @ (Value::List(_), _) => {
458 return Err(ErrorKind::NotCoercibleToString {
459 from: val.0.type_of(),
460 kind,
461 });
462 }
463
464 (c @ Value::Catchable(_), _) => return Ok(c),
465
466 (Value::AttrNotFound, _)
467 | (Value::Blueprint(_), _)
468 | (Value::DeferredUpvalue(_), _)
469 | (Value::UnresolvedPath(_), _)
470 | (Value::FinaliseRequest(_), _) => {
471 panic!("Snix bug: .coerce_to_string() called on internal value")
472 }
473 }
474 }
475 Ok(Value::String(NixString::new_context_from(out_context, out)))
476 }
477
478 pub(crate) async fn nix_eq_owned_genco(
479 self,
480 other: Value,
481 co: GenCo,
482 ptr_eq: PointerEquality,
483 span: Span,
484 ) -> Result<Value, ErrorKind> {
485 self.nix_eq(other, &co, ptr_eq, span).await
486 }
487
488 pub(crate) async fn nix_eq(
499 self,
500 other: Value,
501 co: &GenCo,
502 ptr_eq: PointerEquality,
503 span: Span,
504 ) -> Result<Value, ErrorKind> {
505 let mut vals = vec![((self, other), ptr_eq)];
509
510 loop {
511 let ((a, b), ptr_eq) = if let Some(abp) = vals.pop() {
512 abp
513 } else {
514 return Ok(Value::Bool(true));
516 };
517 let a = match a {
518 Value::Thunk(thunk) => {
519 if ptr_eq == PointerEquality::AllowAll
522 && let Value::Thunk(t1) = &b
523 && t1.ptr_eq(&thunk)
524 {
525 continue;
526 };
527
528 Thunk::force_(thunk, co, span).await?
529 }
530
531 _ => a,
532 };
533
534 let b = b.force(co, span).await?;
535
536 debug_assert!(!matches!(a, Value::Thunk(_)));
537 debug_assert!(!matches!(b, Value::Thunk(_)));
538
539 let result = match (a, b) {
540 (c @ Value::Catchable(_), _) => return Ok(c),
542 (_, c @ Value::Catchable(_)) => return Ok(c),
543 (Value::Null, Value::Null) => true,
544 (Value::Bool(b1), Value::Bool(b2)) => b1 == b2,
545 (Value::String(s1), Value::String(s2)) => s1 == s2,
546 (Value::Path(p1), Value::Path(p2)) => p1 == p2,
547
548 (Value::Integer(i1), Value::Integer(i2)) => i1 == i2,
550 (Value::Integer(i), Value::Float(f)) => i as f64 == f,
551 (Value::Float(f1), Value::Float(f2)) => f1 == f2,
552 (Value::Float(f), Value::Integer(i)) => i as f64 == f,
553
554 (Value::List(l1), Value::List(l2)) => {
556 if ptr_eq >= PointerEquality::AllowNested && l1.ptr_eq(&l2) {
557 continue;
558 }
559
560 if l1.len() != l2.len() {
561 return Ok(Value::Bool(false));
562 }
563
564 vals.extend(l1.into_iter().rev().zip(l2.into_iter().rev()).zip(
565 std::iter::repeat(std::cmp::max(ptr_eq, PointerEquality::AllowNested)),
566 ));
567 continue;
568 }
569
570 (_, Value::List(_)) | (Value::List(_), _) => return Ok(Value::Bool(false)),
571
572 (Value::Attrs(a1), Value::Attrs(a2)) => {
574 if ptr_eq >= PointerEquality::AllowNested && a1.ptr_eq(&a2) {
575 continue;
576 }
577
578 #[allow(clippy::single_match)] match (a1.select("type"), a2.select("type")) {
582 (Some(v1), Some(v2)) => {
583 let s1 = v1.clone().force(co, span).await?;
584 if s1.is_catchable() {
585 return Ok(s1);
586 }
587 let s2 = v2.clone().force(co, span).await?;
588 if s2.is_catchable() {
589 return Ok(s2);
590 }
591 let s1 = s1.to_str();
592 let s2 = s2.to_str();
593
594 if let (Ok(s1), Ok(s2)) = (s1, s2)
595 && s1 == "derivation"
596 && s2 == "derivation"
597 {
598 let out1 = a1
601 .select_required("outPath")
602 .context("comparing derivations")?
603 .clone();
604
605 let out2 = a2
606 .select_required("outPath")
607 .context("comparing derivations")?
608 .clone();
609
610 let out1 = out1.clone().force(co, span).await?;
611 let out2 = out2.clone().force(co, span).await?;
612
613 if out1.is_catchable() {
614 return Ok(out1);
615 }
616
617 if out2.is_catchable() {
618 return Ok(out2);
619 }
620
621 let result =
622 out1.to_contextful_str()? == out2.to_contextful_str()?;
623 if !result {
624 return Ok(Value::Bool(false));
625 } else {
626 continue;
627 }
628 }
629 }
630 _ => {}
631 };
632
633 if a1.len() != a2.len() {
634 return Ok(Value::Bool(false));
635 }
636
637 let iter1 = a1.into_iter_sorted().rev();
641 let iter2 = a2.into_iter_sorted().rev();
642 for ((k1, v1), (k2, v2)) in iter1.zip(iter2) {
643 vals.push((
644 (v1, v2),
645 std::cmp::max(ptr_eq, PointerEquality::AllowNested),
646 ));
647 vals.push((
648 (k1.into(), k2.into()),
649 std::cmp::max(ptr_eq, PointerEquality::AllowNested),
650 ));
651 }
652 continue;
653 }
654
655 (Value::Attrs(_), _) | (_, Value::Attrs(_)) => return Ok(Value::Bool(false)),
656
657 (Value::Closure(c1), Value::Closure(c2))
658 if ptr_eq >= PointerEquality::AllowNested && Rc::ptr_eq(&c1, &c2) =>
659 {
660 continue;
661 }
662
663 _ => return Ok(Value::Bool(false)),
666 };
667 if !result {
668 return Ok(Value::Bool(false));
669 }
670 }
671 }
672
673 pub fn type_of(&self) -> &'static str {
674 match self {
675 Value::Null => "null",
676 Value::Bool(_) => "bool",
677 Value::Integer(_) => "int",
678 Value::Float(_) => "float",
679 Value::String(_) => "string",
680 Value::Path(_) => "path",
681 Value::Attrs(_) => "set",
682 Value::List(_) => "list",
683 Value::Closure(_) | Value::Builtin(_) => "lambda",
684
685 Value::Thunk(_) => "internal[thunk]",
689 Value::AttrNotFound => "internal[attr_not_found]",
690 Value::Blueprint(_) => "internal[blueprint]",
691 Value::DeferredUpvalue(_) => "internal[deferred_upvalue]",
692 Value::UnresolvedPath(_) => "internal[unresolved_path]",
693 Value::FinaliseRequest(_) => "internal[finaliser_sentinel]",
694 Value::Catchable(_) => "internal[catchable]",
695 }
696 }
697
698 gen_cast!(as_bool, bool, "bool", Value::Bool(b), *b);
699 gen_cast!(as_int, i64, "int", Value::Integer(x), *x);
700 gen_cast!(as_float, f64, "float", Value::Float(x), *x);
701
702 pub fn to_str(&self) -> Result<NixString, ErrorKind> {
708 match self {
709 Value::String(s) if !s.has_context() => Ok((*s).clone()),
710 Value::Thunk(thunk) => Self::to_str(&thunk.value()),
711 other => Err(type_error("contextless strings", other)),
712 }
713 }
714
715 gen_cast!(
716 to_contextful_str,
717 NixString,
718 "contextful string",
719 Value::String(s),
720 (*s).clone()
721 );
722 gen_cast!(to_path, Box<PathBuf>, "path", Value::Path(p), p.clone());
723 gen_cast!(to_attrs, NixAttrs, "set", Value::Attrs(a), a.clone());
724 gen_cast!(to_list, NixList, "list", Value::List(l), l.clone());
725 gen_cast!(
726 as_closure,
727 Rc<Closure>,
728 "lambda",
729 Value::Closure(c),
730 c.clone()
731 );
732
733 gen_cast_mut!(as_list_mut, NixList, "list", List);
734
735 gen_is!(is_path, Value::Path(_));
736 gen_is!(is_number, Value::Integer(_) | Value::Float(_));
737 gen_is!(is_bool, Value::Bool(_));
738 gen_is!(is_attrs, Value::Attrs(_));
739 gen_is!(is_catchable, Value::Catchable(_));
740
741 pub fn is_thunk(&self) -> bool {
745 matches!(self, Self::Thunk(..))
746 }
747
748 pub async fn nix_cmp_ordering(
753 self,
754 other: Self,
755 co: GenCo,
756 span: Span,
757 ) -> Result<Result<Ordering, CatchableErrorKind>, ErrorKind> {
758 Self::nix_cmp_ordering_(self, other, co, span).await
759 }
760
761 async fn nix_cmp_ordering_(
762 myself: Self,
763 other: Self,
764 co: GenCo,
765 span: Span,
766 ) -> Result<Result<Ordering, CatchableErrorKind>, ErrorKind> {
767 let mut vals = vec![((myself, other), PointerEquality::ForbidAll)];
771
772 loop {
773 let ((mut a, mut b), ptr_eq) = if let Some(abp) = vals.pop() {
774 abp
775 } else {
776 return Ok(Ok(Ordering::Equal));
778 };
779 if ptr_eq == PointerEquality::AllowAll {
780 if a.clone()
781 .nix_eq(b.clone(), &co, PointerEquality::AllowAll, span)
782 .await?
783 .as_bool()?
784 {
785 continue;
786 }
787 a = a.force(&co, span).await?;
788 b = b.force(&co, span).await?;
789 }
790 let result = match (a, b) {
791 (Value::Catchable(c), _) => return Ok(Err(*c)),
792 (_, Value::Catchable(c)) => return Ok(Err(*c)),
793 (Value::Integer(i1), Value::Integer(i2)) => i1.cmp(&i2),
795 (Value::Float(f1), Value::Float(f2)) => f1.total_cmp(&f2),
796 (Value::String(s1), Value::String(s2)) => s1.cmp(&s2),
797 (Value::List(l1), Value::List(l2)) => {
798 let max = l1.len().max(l2.len());
799 for j in 0..max {
800 let i = max - 1 - j;
801 if i >= l2.len() {
802 vals.push(((1.into(), 0.into()), PointerEquality::ForbidAll));
803 } else if i >= l1.len() {
804 vals.push(((0.into(), 1.into()), PointerEquality::ForbidAll));
805 } else {
806 vals.push(((l1[i].clone(), l2[i].clone()), PointerEquality::AllowAll));
807 }
808 }
809 continue;
810 }
811
812 (Value::Integer(i1), Value::Float(f2)) => (i1 as f64).total_cmp(&f2),
814 (Value::Float(f1), Value::Integer(i2)) => f1.total_cmp(&(i2 as f64)),
815
816 (lhs, rhs) => {
818 return Err(ErrorKind::Incomparable {
819 lhs: lhs.type_of(),
820 rhs: rhs.type_of(),
821 });
822 }
823 };
824 if result != Ordering::Equal {
825 return Ok(Ok(result));
826 }
827 }
828 }
829
830 pub async fn force(self, co: &GenCo, span: Span) -> Result<Value, ErrorKind> {
832 if let Value::Thunk(thunk) = self {
833 return Thunk::force_(thunk, co, span).await;
835 }
836 Ok(self)
837 }
838
839 pub async fn force_owned_genco(self, co: GenCo, span: Span) -> Result<Value, ErrorKind> {
841 if let Value::Thunk(thunk) = self {
842 return Thunk::force_(thunk, &co, span).await;
844 }
845 Ok(self)
846 }
847
848 pub fn explain(&self) -> String {
851 match self {
852 Value::Null => "the 'null' value".into(),
853 Value::Bool(b) => format!("the boolean value '{b}'"),
854 Value::Integer(i) => format!("the integer '{i}'"),
855 Value::Float(f) => format!("the float '{f}'"),
856 Value::String(s) if s.has_context() => format!("the contextful string '{s}'"),
857 Value::String(s) => format!("the contextless string '{s}'"),
858 Value::Path(p) => format!("the path '{}'", p.to_string_lossy()),
859 Value::Attrs(attrs) => format!("a {}-item attribute set", attrs.len()),
860 Value::List(list) => format!("a {}-item list", list.len()),
861
862 Value::Closure(f) => {
863 if let Some(name) = &f.lambda.name {
864 format!("the user-defined Nix function '{name}'")
865 } else {
866 "a user-defined Nix function".to_string()
867 }
868 }
869
870 Value::Builtin(b) => {
871 let mut out = format!("the builtin function '{}'", b.name());
872 if let Some(docs) = b.documentation() {
873 out.push_str("\n\n");
874 out.push_str(docs);
875 }
876 out
877 }
878
879 Value::Thunk(t) => t.value().explain(),
881
882 Value::Catchable(_) => "a catchable failure".into(),
883
884 Value::AttrNotFound
885 | Value::Blueprint(_)
886 | Value::DeferredUpvalue(_)
887 | Value::UnresolvedPath(_)
888 | Value::FinaliseRequest(_) => "an internal Snix evaluator value".into(),
889 }
890 }
891
892 pub fn suspended_native_thunk(native: Box<dyn Fn() -> Result<Value, ErrorKind>>) -> Self {
895 Value::Thunk(Thunk::new_suspended_native(native))
896 }
897}
898
899trait TotalDisplay {
900 fn total_fmt(&self, f: &mut std::fmt::Formatter<'_>, set: &mut ThunkSet) -> std::fmt::Result;
901}
902
903impl Display for Value {
904 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
905 self.total_fmt(f, &mut Default::default())
906 }
907}
908
909fn total_fmt_float<F: std::fmt::Write>(num: f64, mut f: F) -> std::fmt::Result {
912 let mut buf = [b'0'; lexical_core::BUFFER_SIZE];
913 let mut s = lexical_core::write_with_options::<f64, { CXX_LITERAL }>(
914 num,
915 &mut buf,
916 &WRITE_FLOAT_OPTIONS,
917 );
918
919 let mut new_s = Vec::with_capacity(s.len());
923
924 if s.contains(&b'e') {
925 for (i, c) in s.iter().enumerate() {
926 if c == &b'e' {
928 if s.len() > i && s[i + 1].is_ascii_digit() {
930 new_s.extend_from_slice(&s[0..=i]);
932 new_s.push(b'+');
934 if s.len() == i + 2 {
937 new_s.push(b'0');
938 }
939 new_s.extend_from_slice(&s[i + 1..]);
940 break;
941 }
942 }
943 }
944
945 if !new_s.is_empty() {
947 s = &mut new_s
948 }
949 } else if s.contains(&b'.') {
950 for (i, c) in s.iter().enumerate() {
954 if c == &b'.' {
956 let frac = String::from_utf8_lossy(&s[i + 1..]);
958 let frac_no_trailing_zeroes = frac.trim_end_matches('0');
959
960 if frac.len() != frac_no_trailing_zeroes.len() {
961 if frac_no_trailing_zeroes.is_empty() {
963 new_s.extend_from_slice(&s[0..=i - 1]);
965 } else {
966 new_s.extend_from_slice(&s[0..=i]);
968 new_s.extend_from_slice(frac_no_trailing_zeroes.as_bytes());
969 }
970
971 s = &mut new_s;
973 break;
974 }
975 }
976 }
977 }
978
979 write!(f, "{}", String::from_utf8_lossy(s))
980}
981
982impl TotalDisplay for Value {
983 fn total_fmt(&self, f: &mut std::fmt::Formatter<'_>, set: &mut ThunkSet) -> std::fmt::Result {
984 match self {
985 Value::Null => f.write_str("null"),
986 Value::Bool(true) => f.write_str("true"),
987 Value::Bool(false) => f.write_str("false"),
988 Value::Integer(num) => write!(f, "{num}"),
989 Value::String(s) => s.fmt(f),
990 Value::Path(p) => p.display().fmt(f),
991 Value::Attrs(attrs) => attrs.total_fmt(f, set),
992 Value::List(list) => list.total_fmt(f, set),
993 Value::Closure(_) => f.write_str("<LAMBDA>"),
995 Value::Builtin(builtin) => builtin.fmt(f),
996
997 Value::Float(num) => total_fmt_float(*num, f),
1001
1002 Value::AttrNotFound => f.write_str("internal[not found]"),
1004 Value::Blueprint(_) => f.write_str("internal[blueprint]"),
1005 Value::DeferredUpvalue(_) => f.write_str("internal[deferred_upvalue]"),
1006 Value::UnresolvedPath(_) => f.write_str("internal[unresolved_path]"),
1007 Value::FinaliseRequest(_) => f.write_str("internal[finaliser_sentinel]"),
1008
1009 Value::Thunk(t) => t.total_fmt(f, set),
1012 Value::Catchable(_) => panic!("total_fmt() called on a CatchableErrorKind"),
1013 }
1014 }
1015}
1016
1017impl From<bool> for Value {
1018 fn from(b: bool) -> Self {
1019 Value::Bool(b)
1020 }
1021}
1022
1023impl From<i64> for Value {
1024 fn from(i: i64) -> Self {
1025 Self::Integer(i)
1026 }
1027}
1028
1029impl From<f64> for Value {
1030 fn from(i: f64) -> Self {
1031 Self::Float(i)
1032 }
1033}
1034
1035impl From<PathBuf> for Value {
1036 fn from(path: PathBuf) -> Self {
1037 Self::Path(Box::new(path))
1038 }
1039}
1040
1041fn type_error(expected: &'static str, actual: &Value) -> ErrorKind {
1042 ErrorKind::TypeError {
1043 expected,
1044 actual: actual.type_of(),
1045 }
1046}
1047
1048#[cfg(test)]
1049mod tests {
1050 use super::*;
1051 use std::mem::size_of;
1052
1053 #[test]
1054 fn size() {
1055 assert_eq!(size_of::<Value>(), 16);
1056 }
1057
1058 mod floats {
1059 use crate::value::total_fmt_float;
1060
1061 #[test]
1062 fn format_float() {
1063 let ff = [
1064 (0f64, "0"),
1065 (1.0f64, "1"),
1066 (-0.01, "-0.01"),
1067 (5e+22, "5e+22"),
1068 (1e6, "1e+06"),
1069 (-2E-2, "-0.02"),
1070 (6.626e-34, "6.626e-34"),
1071 (9_224_617.445_991_227, "9.22462e+06"),
1072 ];
1073 for (n, expected) in ff.iter() {
1074 let mut buf = String::new();
1075 let res = total_fmt_float(*n, &mut buf);
1076 assert!(res.is_ok());
1077 assert_eq!(
1078 expected, &buf,
1079 "{} should be formatted as {}, but got {}",
1080 n, expected, buf
1081 );
1082 }
1083 }
1084 }
1085}