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; use num_enum::TryFromPrimitive; macro_rules! define_builtins { ($(($name:literal, $variant:ident, $arity:expr)),* $(,)?) => { const BUILTINS: &[(&str, u8)] = &[ $(($name, $arity),)* ]; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, TryFromPrimitive, Collect)] #[repr(u8)] #[collect(require_static)] pub enum BuiltinId { $($variant,)* } impl BuiltinId { pub const ALL: [Self; BUILTINS.len()] = [$(Self::$variant,)*]; } }; } define_builtins! { ("abort", Abort, 1), ("__add", Add, 2), ("__addErrorContext", AddErrorContext, 2), ("__all", All, 2), ("__any", Any, 2), ("__appendContext", AppendContext, 2), ("__attrNames", AttrNames, 1), ("__attrValues", AttrValues, 1), ("baseNameOf", BaseNameOf, 1), ("__bitAnd", BitAnd, 2), ("__bitOr", BitOr, 2), ("__bitXor", BitXor, 2), ("break", Break, 1), ("__catAttrs", CatAttrs, 2), ("__ceil", Ceil, 1), ("__compareVersions", CompareVersions, 2), ("__concatLists", ConcatLists, 1), ("__concatMap", ConcatMap, 2), ("__concatStringsSep", ConcatStringsSep, 2), ("__convertHash", ConvertHash, 1), ("__deepSeq", DeepSeq, 2), ("derivation", Derivation, 1), ("derivationStrict", DerivationStrict, 1), ("dirOf", DirOf, 1), ("__div", Div, 2), ("__elem", Elem, 2), ("__elemAt", ElemAt, 2), ("fetchGit", FetchGit, 1), ("fetchMercurial", FetchMercurial, 1), ("fetchTarball", FetchTarball, 1), ("fetchTree", FetchTree, 1), ("__fetchurl", FetchUrl, 1), ("__filter", Filter, 2), ("__filterSource", FilterSource, 2), ("__findFile", FindFile, 2), ("__floor", Floor, 1), ("__foldl'", FoldlStrict, 3), ("__fromJSON", FromJSON, 1), ("fromTOML", FromTOML, 1), ("__functionArgs", FunctionArgs, 1), ("__genList", GenList, 2), ("__genericClosure", GenericClosure, 1), ("__getAttr", GetAttr, 2), ("__getContext", GetContext, 1), ("__getEnv", GetEnv, 1), ("__groupBy", GroupBy, 2), ("__hasAttr", HasAttr, 2), ("__hasContext", HasContext, 1), ("__hashFile", HashFile, 2), ("__hashString", HashString, 2), ("__head", Head, 1), ("import", Import, 1), ("__intersectAttrs", IntersectAttrs, 2), ("__isAttrs", IsAttrs, 1), ("__isBool", IsBool, 1), ("__isFloat", IsFloat, 1), ("__isFunction", IsFunction, 1), ("__isInt", IsInt, 1), ("__isList", IsList, 1), ("isNull", IsNull, 1), ("__isPath", IsPath, 1), ("__isString", IsString, 1), ("__length", Length, 1), ("__lessThan", LessThan, 2), ("__listToAttrs", ListToAttrs, 1), ("map", Map, 2), ("__mapAttrs", MapAttrs, 2), ("__match", Match, 2), ("__mul", Mul, 2), ("__parseDrvName", ParseDrvName, 1), ("__partition", Partition, 2), ("__path", Path, 1), ("__pathExists", PathExists, 1), ("placeholder", Placeholder, 1), ("__readDir", ReadDir, 1), ("__readFile", ReadFile, 1), ("__readFileType", ReadFileType, 1), ("removeAttrs", RemoveAttrs, 2), ("__replaceStrings", ReplaceStrings, 3), ("scopedImport", ScopedImport, 2), ("__seq", Seq, 2), ("__sort", Sort, 2), ("__split", Split, 2), ("__splitVersion", SplitVersion, 1), ("__storePath", StorePath, 1), ("__stringLength", StringLength, 1), ("__sub", Sub, 2), ("__substring", Substring, 3), ("__tail", Tail, 1), ("throw", Throw, 1), ("__toFile", ToFile, 2), ("__toJSON", ToJSON, 1), ("__toPath", ToPath, 1), ("toString", ToString, 1), ("__toXML", ToXML, 1), ("__trace", Trace, 2), ("__tryEval", TryEval, 1), ("__typeOf", TypeOf, 1), ("__unsafeDiscardStringContext", UnsafeDiscardStringContext, 1), ("__unsafeDiscardOutputDependency", UnsafeDiscardOutputDependency, 1), ("__unsafeGetAttrPos", UnsafeGetAttrPos, 2), ("__warn", Warn, 2), ("__zipAttrsWith", ZipAttrsWith, 2), } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct BuiltinInfo { pub name: &'static str, pub global_name: &'static str, pub global: bool, pub arity: u8, } impl BuiltinId { pub const TOTAL: usize = BUILTINS.len(); #[expect( clippy::indexing_slicing, reason = "a `BuiltinId` discriminant is always a valid index into the `BUILTINS` table" )] #[inline(always)] pub fn info(self) -> BuiltinInfo { let (global_name, arity) = BUILTINS[self as usize]; let (name, global) = global_name .strip_prefix("__") .map_or((global_name, true), |name| (name, false)); BuiltinInfo { name, global_name, global, arity, } } } #[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; #[expect( clippy::cast_sign_loss, reason = "this branch runs only when exp < precision, so precision-1-exp and precision-1 are non-negative" )] 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((head, tail)) = formatted.split_once('e') { let trimmed = head.trim_end_matches('0').trim_end_matches('.'); write!(f, "{trimmed}e{tail}") } 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»"), } } }