use std::borrow::Cow; use std::collections::BTreeMap; use std::fmt::{Debug, Display, Formatter, Result as FmtResult}; use std::ops::{Deref, DerefMut}; use gc_arena::Collect; #[repr(transparent)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Collect)] #[collect(require_static)] pub struct StringId(pub string_interner::symbol::SymbolU32); /// Represents a Nix symbol, which is used as a key in attribute sets. #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct Symbol<'a>(Cow<'a, str>); pub type StaticSymbol = Symbol<'static>; impl From for Symbol<'_> { fn from(value: String) -> Self { Symbol(Cow::Owned(value)) } } impl<'a> From<&'a str> for Symbol<'a> { fn from(value: &'a str) -> Self { Symbol(Cow::Borrowed(value)) } } /// Formats a string slice as a Nix symbol, quoting it if necessary. pub fn format_symbol<'a>(sym: impl Into>) -> Cow<'a, str> { let sym = sym.into(); if Symbol::NORMAL_REGEX.test(&sym) { sym } else { Cow::Owned(escape_quote_string(&sym)) } } impl Display for Symbol<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { if self.normal() { write!(f, "{}", self.0) } else { write!(f, "{}", escape_quote_string(&self.0)) } } } impl Symbol<'_> { const NORMAL_REGEX: ere::Regex<1> = ere::compile_regex!("^[a-zA-Z_][a-zA-Z0-9_'-]*$"); /// Checks if the symbol is a "normal" identifier that doesn't require quotes. fn normal(&self) -> bool { Self::NORMAL_REGEX.test(self) } } impl Deref for Symbol<'_> { type Target = str; fn deref(&self) -> &Self::Target { &self.0 } } fn escape_quote_string(s: &str) -> String { let mut ret = String::with_capacity(s.len() + 2); ret.push('"'); let mut iter = s.chars().peekable(); while let Some(c) = iter.next() { match c { '\\' => ret.push_str("\\\\"), '"' => ret.push_str("\\\""), '\n' => ret.push_str("\\n"), '\r' => ret.push_str("\\r"), '\t' => ret.push_str("\\t"), '$' if iter.peek() == Some(&'{') => ret.push_str("\\$"), c => ret.push(c), } } ret.push('"'); ret } /// Represents a Nix attribute set, which is a map from symbols to values. #[derive(Default, Clone, PartialEq)] pub struct AttrSet { data: BTreeMap, } impl AttrSet { pub fn new(data: BTreeMap) -> Self { Self { data } } /// Gets a value by key (string or Symbol). pub fn get<'a, 'sym: 'a>(&'a self, key: impl Into>) -> Option<&'a Value> { self.data.get(&key.into()) } /// Checks if a key exists in the attribute set. pub fn contains_key<'a, 'sym: 'a>(&'a self, key: impl Into>) -> bool { self.data.contains_key(&key.into()) } } impl Deref for AttrSet { type Target = BTreeMap; fn deref(&self) -> &Self::Target { &self.data } } impl DerefMut for AttrSet { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.data } } impl Debug for AttrSet { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { use Value::*; write!(f, "{{")?; for (k, v) in self.data.iter() { write!(f, " {k:?} = ")?; match v { List(_) => write!(f, "[ ... ];")?, AttrSet(_) => write!(f, "{{ ... }};")?, v => write!(f, "{v:?};")?, } } write!(f, " }}") } } impl Display for AttrSet { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { use Value::*; if self.data.len() > 1 { writeln!(f, "{{")?; for (k, v) in self.data.iter() { write!(f, " {k} = ")?; match v { List(_) => writeln!(f, "[ ... ];")?, AttrSet(_) => writeln!(f, "{{ ... }};")?, v => writeln!(f, "{v};")?, } } write!(f, "}}") } else { write!(f, "{{")?; for (k, v) in self.data.iter() { write!(f, " {k} = ")?; match v { List(_) => write!(f, "[ ... ];")?, AttrSet(_) => write!(f, "{{ ... }};")?, v => write!(f, "{v};")?, } } write!(f, " }}") } } } impl AttrSet { pub fn display_compat(&self) -> AttrSetCompatDisplay<'_> { AttrSetCompatDisplay(self) } } pub struct AttrSetCompatDisplay<'a>(&'a AttrSet); impl Display for AttrSetCompatDisplay<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { write!(f, "{{")?; for (k, v) in self.0.data.iter() { write!(f, " {k} = {};", v.display_compat())?; } write!(f, " }}") } } /// Represents a Nix list, which is a vector of values. #[derive(Default, Clone, Debug, PartialEq)] pub struct List { data: Vec, } impl List { pub fn new(data: Vec) -> Self { Self { data } } } impl Deref for List { type Target = Vec; fn deref(&self) -> &Self::Target { &self.data } } impl DerefMut for List { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.data } } impl Display for List { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { use Value::*; if self.data.len() > 1 { writeln!(f, "[")?; for v in self.data.iter() { match v { List(_) => writeln!(f, " [ ... ]")?, AttrSet(_) => writeln!(f, " {{ ... }}")?, v => writeln!(f, " {v}")?, } } write!(f, "]") } else { write!(f, "[ ")?; for v in self.data.iter() { match v { List(_) => write!(f, "[ ... ] ")?, AttrSet(_) => write!(f, "{{ ... }} ")?, v => write!(f, "{v} ")?, } } write!(f, "]") } } } impl List { pub fn display_compat(&self) -> ListCompatDisplay<'_> { ListCompatDisplay(self) } } pub struct ListCompatDisplay<'a>(&'a List); impl Display for ListCompatDisplay<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { write!(f, "[ ")?; for v in self.0.data.iter() { write!(f, "{} ", v.display_compat())?; } write!(f, "]") } } /// Represents any possible Nix value that can be returned from an evaluation. #[derive(Clone, Debug, PartialEq)] pub enum Value { /// An integer value. Int(i64), /// An floating-point value. Float(f64), /// An boolean value. Bool(bool), /// An null value. Null, /// A string value. String(String), /// A path value (absolute path string). Path(String), /// An attribute set. AttrSet(AttrSet), /// A list. List(List), /// A thunk, representing a delayed computation. Thunk, /// A function (lambda). Func, /// A primitive (built-in) operation. PrimOp(&'static str), /// A partially applied primitive operation. PrimOpApp(&'static str), /// A marker for a value that has been seen before during serialization, to break cycles. /// This is used to prevent infinite recursion when printing or serializing cyclic data structures. Repeated, } /// Wrapper to format a float in Nix style (C printf `%g` with precision 6). pub struct NixFloat(pub f64); impl Display for NixFloat { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { fmt_nix_float(f, self.0) } } /// Format a float matching C's `printf("%g", x)` with default precision 6. fn fmt_nix_float(f: &mut Formatter<'_>, x: f64) -> FmtResult { if !x.is_finite() { return write!(f, "{x}"); } if x == 0.0 { return if x.is_sign_negative() { write!(f, "-0") } else { write!(f, "0") }; } let precision: i32 = 6; let exp = x.abs().log10().floor() as i32; let formatted = if exp >= -4 && exp < precision { let decimal_places = (precision - 1 - exp) as usize; format!("{x:.decimal_places$}") } else { let sig_digits = (precision - 1) as usize; let s = format!("{x:.sig_digits$e}"); let (mantissa, exp_part) = s .split_once('e') .expect("scientific notation must contain 'e'"); let (sign, digits) = if let Some(d) = exp_part.strip_prefix('-') { ("-", d) } else if let Some(d) = exp_part.strip_prefix('+') { ("+", d) } else { ("+", exp_part) }; if digits.len() < 2 { format!("{mantissa}e{sign}0{digits}") } else { format!("{mantissa}e{sign}{digits}") } }; if formatted.contains('.') { if let Some(e_pos) = formatted.find('e') { let trimmed = formatted[..e_pos] .trim_end_matches('0') .trim_end_matches('.'); write!(f, "{}{}", trimmed, &formatted[e_pos..]) } else { let trimmed = formatted.trim_end_matches('0').trim_end_matches('.'); write!(f, "{trimmed}") } } else { write!(f, "{formatted}") } } impl Display for Value { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { use Value::*; match self { &Int(x) => write!(f, "{x}"), &Float(x) => fmt_nix_float(f, x), &Bool(x) => write!(f, "{x}"), Null => write!(f, "null"), String(x) => write!(f, "{}", escape_quote_string(x)), Path(x) => write!(f, "{x}"), AttrSet(x) => write!(f, "{x}"), List(x) => write!(f, "{x}"), Thunk => write!(f, "«code»"), Func => write!(f, "«lambda»"), PrimOp(name) => write!(f, "«primop {name}»"), PrimOpApp(name) => write!(f, "«partially applied primop {name}»"), Repeated => write!(f, "«repeated»"), } } } impl Value { pub fn display_compat(&self) -> ValueCompatDisplay<'_> { ValueCompatDisplay(self) } } pub struct ValueCompatDisplay<'a>(&'a Value); impl Display for ValueCompatDisplay<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { use Value::*; match self.0 { &Int(x) => write!(f, "{x}"), &Float(x) => fmt_nix_float(f, x), &Bool(x) => write!(f, "{x}"), Null => write!(f, "null"), String(x) => write!(f, "{}", escape_quote_string(x)), Path(x) => write!(f, "{x}"), AttrSet(x) => write!(f, "{}", x.display_compat()), List(x) => write!(f, "{}", x.display_compat()), Thunk => write!(f, "«thunk»"), Func => write!(f, ""), PrimOp(_) => write!(f, ""), PrimOpApp(_) => write!(f, ""), Repeated => write!(f, "«repeated»"), } } }