diff options
Diffstat (limited to 'tvix/eval/src/value/string.rs')
-rw-r--r-- | tvix/eval/src/value/string.rs | 873 |
1 files changed, 0 insertions, 873 deletions
diff --git a/tvix/eval/src/value/string.rs b/tvix/eval/src/value/string.rs deleted file mode 100644 index ceb43f1ea5ed..000000000000 --- a/tvix/eval/src/value/string.rs +++ /dev/null @@ -1,873 +0,0 @@ -//! This module implements Nix language strings. -//! -//! Nix language strings never need to be modified on the language -//! level, allowing us to shave off some memory overhead and only -//! paying the cost when creating new strings. -use bstr::{BStr, BString, ByteSlice, Chars}; -use rnix::ast; -use std::alloc::{alloc, dealloc, handle_alloc_error, Layout}; -use std::borrow::{Borrow, Cow}; -use std::collections::HashSet; -use std::ffi::c_void; -use std::fmt::{self, Debug, Display}; -use std::hash::Hash; -use std::ops::Deref; -use std::ptr::{self, NonNull}; -use std::slice; - -use serde::de::{Deserializer, Visitor}; -use serde::{Deserialize, Serialize}; - -#[derive(Clone, Debug, Serialize, Hash, PartialEq, Eq)] -pub enum NixContextElement { - /// A plain store path (e.g. source files copied to the store) - Plain(String), - - /// Single output of a derivation, represented by its name and its derivation path. - Single { name: String, derivation: String }, - - /// A reference to a complete derivation - /// including its source and its binary closure. - /// It is used for the `drvPath` attribute context. - /// The referred string is the store path to - /// the derivation path. - Derivation(String), -} - -/// Nix context strings representation in Tvix. This tracks a set of different kinds of string -/// dependencies that we can come across during manipulation of our language primitives, mostly -/// strings. There's some simple algebra of context strings and how they propagate w.r.t. primitive -/// operations, e.g. concatenation, interpolation and other string operations. -#[repr(transparent)] -#[derive(Clone, Debug, Serialize, Default)] -pub struct NixContext(HashSet<NixContextElement>); - -impl From<NixContextElement> for NixContext { - fn from(value: NixContextElement) -> Self { - Self([value].into()) - } -} - -impl From<HashSet<NixContextElement>> for NixContext { - fn from(value: HashSet<NixContextElement>) -> Self { - Self(value) - } -} - -impl NixContext { - /// Creates an empty context that can be populated - /// and passed to form a contextful [NixString], albeit - /// if the context is concretly empty, the resulting [NixString] - /// will be contextless. - pub fn new() -> Self { - Self::default() - } - - /// For internal consumers, we let people observe - /// if the [NixContext] is actually empty or not - /// to decide whether they want to skip the allocation - /// of a full blown [HashSet]. - pub(crate) fn is_empty(&self) -> bool { - self.0.is_empty() - } - - /// Consumes a new [NixContextElement] and add it if not already - /// present in this context. - pub fn append(mut self, other: NixContextElement) -> Self { - self.0.insert(other); - self - } - - /// Consumes both ends of the join into a new NixContent - /// containing the union of elements of both ends. - pub fn join(mut self, other: &mut NixContext) -> Self { - let other_set = std::mem::take(&mut other.0); - let mut set: HashSet<NixContextElement> = std::mem::take(&mut self.0); - set.extend(other_set); - Self(set) - } - - /// Copies from another [NixString] its context strings - /// in this context. - pub fn mimic(&mut self, other: &NixString) { - if let Some(context) = other.context() { - self.0.extend(context.iter().cloned()); - } - } - - /// Iterates over "plain" context elements, e.g. sources imported - /// in the store without more information, i.e. `toFile` or coerced imported paths. - /// It yields paths to the store. - pub fn iter_plain(&self) -> impl Iterator<Item = &str> { - self.iter().filter_map(|elt| { - if let NixContextElement::Plain(s) = elt { - Some(s.as_str()) - } else { - None - } - }) - } - - /// Iterates over "full derivations" context elements, e.g. something - /// referring to their `drvPath`, i.e. their full sources and binary closure. - /// It yields derivation paths. - pub fn iter_derivation(&self) -> impl Iterator<Item = &str> { - self.iter().filter_map(|elt| { - if let NixContextElement::Derivation(s) = elt { - Some(s.as_str()) - } else { - None - } - }) - } - - /// Iterates over "single" context elements, e.g. single derived paths, - /// or also known as the single output of a given derivation. - /// The first element of the tuple is the output name - /// and the second element is the derivation path. - pub fn iter_single_outputs(&self) -> impl Iterator<Item = (&str, &str)> { - self.iter().filter_map(|elt| { - if let NixContextElement::Single { name, derivation } = elt { - Some((name.as_str(), derivation.as_str())) - } else { - None - } - }) - } - - /// Iterates over any element of the context. - pub fn iter(&self) -> impl Iterator<Item = &NixContextElement> { - self.0.iter() - } - - /// Produces a list of owned references to this current context, - /// no matter its type. - pub fn to_owned_references(self) -> Vec<String> { - self.0 - .into_iter() - .map(|ctx| match ctx { - NixContextElement::Derivation(drv_path) => drv_path, - NixContextElement::Plain(store_path) => store_path, - NixContextElement::Single { derivation, .. } => derivation, - }) - .collect() - } -} - -/// This type is never instantiated, but serves to document the memory layout of the actual heap -/// allocation for Nix strings. -#[allow(dead_code)] -struct NixStringInner { - /// The string context, if any. Note that this is boxed to take advantage of the null pointer - /// niche, otherwise this field ends up being very large: - /// - /// ```notrust - /// >> std::mem::size_of::<Option<HashSet<String>>>() - /// 48 - /// - /// >> std::mem::size_of::<Option<Box<HashSet<String>>>>() - /// 8 - /// ``` - context: Option<Box<NixContext>>, - /// The length of the data, stored *inline in the allocation* - length: usize, - /// The actual data for the string itself. Will always be `length` bytes long - data: [u8], -} - -#[allow(clippy::zst_offset)] -impl NixStringInner { - /// Construct a [`Layout`] for a nix string allocation with the given length. - /// - /// Returns a tuple of: - /// 1. The layout itself. - /// 2. The offset of [`Self::length`] within the allocation, assuming the allocation starts at 0 - /// 3. The offset of the data array within the allocation, assuming the allocation starts at 0 - fn layout(len: usize) -> (Layout, usize, usize) { - let layout = Layout::new::<Option<Box<NixContext>>>(); - let (layout, len_offset) = layout.extend(Layout::new::<usize>()).unwrap(); - let (layout, data_offset) = layout.extend(Layout::array::<u8>(len).unwrap()).unwrap(); - (layout, len_offset, data_offset) - } - - /// Returns the [`Layout`] for an *already-allocated* nix string, loading the length from the - /// pointer. - /// - /// Returns a tuple of: - /// 1. The layout itself. - /// 2. The offset of [`Self::length`] within the allocation, assuming the allocation starts at 0 - /// 3. The offset of the data array within the allocation, assuming the allocation starts at 0 - /// - /// # Safety - /// - /// This function must only be called on a pointer that has been properly initialized with - /// [`Self::alloc`]. The data buffer may not necessarily be initialized - unsafe fn layout_of(this: NonNull<c_void>) -> (Layout, usize, usize) { - let layout = Layout::new::<Option<Box<NixContext>>>(); - let (_, len_offset) = layout.extend(Layout::new::<usize>()).unwrap(); - // SAFETY: Layouts are linear, so even though we haven't involved data at all yet, we know - // the len_offset is a valid offset into the second field of the allocation - let len = *(this.as_ptr().add(len_offset) as *const usize); - Self::layout(len) - } - - /// Allocate an *uninitialized* nix string with the given length. Writes the length to the - /// length value in the pointer, but leaves both context and data uninitialized - /// - /// This function is safe to call (as constructing pointers of any sort of validity is always - /// safe in Rust) but it is unsafe to use the resulting pointer to do anything other than - /// - /// 1. Read the length - /// 2. Write the context - /// 3. Write the data - /// - /// until the string is fully initialized - fn alloc(len: usize) -> NonNull<c_void> { - let (layout, len_offset, _data_offset) = Self::layout(len); - debug_assert_ne!(layout.size(), 0); - unsafe { - // SAFETY: Layout has non-zero size, since the layout of the context and the - // layout of the len both have non-zero size - let ptr = alloc(layout); - - if let Some(this) = NonNull::new(ptr as *mut _) { - // SAFETY: We've allocated with a layout that causes the len_offset to be in-bounds - // and writeable, and if the allocation succeeded it won't wrap - ((this.as_ptr() as *mut u8).add(len_offset) as *mut usize).write(len); - debug_assert_eq!(Self::len(this), len); - this - } else { - handle_alloc_error(layout); - } - } - } - - /// Deallocate the Nix string at the given pointer - /// - /// # Safety - /// - /// This function must only be called with a pointer that has been properly initialized with - /// [`Self::alloc`] - unsafe fn dealloc(this: NonNull<c_void>) { - let (layout, _, _) = Self::layout_of(this); - // SAFETY: okay because of the safety guarantees of this method - dealloc(this.as_ptr() as *mut u8, layout) - } - - /// Return the length of the Nix string at the given pointer - /// - /// # Safety - /// - /// This function must only be called with a pointer that has been properly initialized with - /// [`Self::alloc`] - unsafe fn len(this: NonNull<c_void>) -> usize { - let (_, len_offset, _) = Self::layout_of(this); - // SAFETY: As long as the safety guarantees of this method are upheld, we've allocated with - // a layout that causes the len_offset to be in-bounds and writeable, and if the allocation - // succeeded it won't wrap - *(this.as_ptr().add(len_offset) as *const usize) - } - - /// Return a pointer to the context value within the given Nix string pointer - /// - /// # Safety - /// - /// This function must only be called with a pointer that has been properly initialized with - /// [`Self::alloc`] - unsafe fn context_ptr(this: NonNull<c_void>) -> *mut Option<Box<NixContext>> { - // SAFETY: The context is the first field in the layout of the allocation - this.as_ptr() as *mut Option<Box<NixContext>> - } - - /// Construct a shared reference to the context value within the given Nix string pointer - /// - /// # Safety - /// - /// This function must only be called with a pointer that has been properly initialized with - /// [`Self::alloc`], and where the context has been properly initialized (by writing to the - /// pointer returned from [`Self::context_ptr`]). - /// - /// Also, all the normal Rust rules about pointer-to-reference conversion apply. See - /// [`NonNull::as_ref`] for more. - unsafe fn context_ref<'a>(this: NonNull<c_void>) -> &'a Option<Box<NixContext>> { - Self::context_ptr(this).as_ref().unwrap() - } - - /// Construct a mutable reference to the context value within the given Nix string pointer - /// - /// # Safety - /// - /// This function must only be called with a pointer that has been properly initialized with - /// [`Self::alloc`], and where the context has been properly initialized (by writing to the - /// pointer returned from [`Self::context_ptr`]). - /// - /// Also, all the normal Rust rules about pointer-to-reference conversion apply. See - /// [`NonNull::as_mut`] for more. - unsafe fn context_mut<'a>(this: NonNull<c_void>) -> &'a mut Option<Box<NixContext>> { - Self::context_ptr(this).as_mut().unwrap() - } - - /// Return a pointer to the data array within the given Nix string pointer - /// - /// # Safety - /// - /// This function must only be called with a pointer that has been properly initialized with - /// [`Self::alloc`] - unsafe fn data_ptr(this: NonNull<c_void>) -> *mut u8 { - let (_, _, data_offset) = Self::layout_of(this); - // SAFETY: data is the third field in the layout of the allocation - this.as_ptr().add(data_offset) as *mut u8 - } - - /// Construct a shared reference to the data slice within the given Nix string pointer - /// - /// # Safety - /// - /// This function must only be called with a pointer that has been properly initialized with - /// [`Self::alloc`], and where the data array has been properly initialized (by writing to the - /// pointer returned from [`Self::data_ptr`]). - /// - /// Also, all the normal Rust rules about pointer-to-reference conversion apply. See - /// [`slice::from_raw_parts`] for more. - unsafe fn data_slice<'a>(this: NonNull<c_void>) -> &'a [u8] { - let len = Self::len(this); - let data = Self::data_ptr(this); - slice::from_raw_parts(data, len) - } - - /// Construct a mutable reference to the data slice within the given Nix string pointer - /// - /// # Safety - /// - /// This function must only be called with a pointer that has been properly initialized with - /// [`Self::alloc`], and where the data array has been properly initialized (by writing to the - /// pointer returned from [`Self::data_ptr`]). - /// - /// Also, all the normal Rust rules about pointer-to-reference conversion apply. See - /// [`slice::from_raw_parts_mut`] for more. - #[allow(dead_code)] - unsafe fn data_slice_mut<'a>(this: NonNull<c_void>) -> &'a mut [u8] { - let len = Self::len(this); - let data = Self::data_ptr(this); - slice::from_raw_parts_mut(data, len) - } - - /// Clone the Nix string pointed to by this pointer, and return a pointer to a new Nix string - /// containing the same data and context. - /// - /// # Safety - /// - /// This function must only be called with a pointer that has been properly initialized with - /// [`Self::alloc`], and where the context has been properly initialized (by writing to the - /// pointer returned from [`Self::context_ptr`]), and the data array has been properly - /// initialized (by writing to the pointer returned from [`Self::data_ptr`]). - unsafe fn clone(this: NonNull<c_void>) -> NonNull<c_void> { - let (layout, _, _) = Self::layout_of(this); - let ptr = alloc(layout); - if let Some(new) = NonNull::new(ptr as *mut _) { - ptr::copy_nonoverlapping(this.as_ptr(), new.as_ptr(), layout.size()); - Self::context_ptr(new).write(Self::context_ref(this).clone()); - new - } else { - handle_alloc_error(layout); - } - } -} - -/// Nix string values -/// -/// # Internals -/// -/// For performance reasons (to keep allocations small, and to avoid indirections), [`NixString`] is -/// represented as a single *thin* pointer to a packed data structure containing the -/// [context][NixContext] and the string data itself (which is a raw byte array, to match the Nix -/// string semantics that allow any array of bytes to be represented by a string). - -/// This memory representation is documented in [`NixStringInner`], but since Rust prefers to deal -/// with slices via *fat pointers* (pointers that include the length in the *pointer*, not in the -/// heap allocation), we have to do mostly manual layout management and allocation for this -/// representation. See the documentation for the methods of [`NixStringInner`] for more information -pub struct NixString(NonNull<c_void>); - -unsafe impl Send for NixString {} -unsafe impl Sync for NixString {} - -impl Drop for NixString { - fn drop(&mut self) { - // SAFETY: There's no way to construct a NixString that doesn't leave the allocation correct - // according to the rules of dealloc - unsafe { - NixStringInner::dealloc(self.0); - } - } -} - -impl Clone for NixString { - fn clone(&self) -> Self { - // SAFETY: There's no way to construct a NixString that doesn't leave the allocation correct - // according to the rules of clone - unsafe { Self(NixStringInner::clone(self.0)) } - } -} - -impl Debug for NixString { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if let Some(ctx) = self.context() { - f.debug_struct("NixString") - .field("context", ctx) - .field("data", &self.as_bstr()) - .finish() - } else { - write!(f, "{:?}", self.as_bstr()) - } - } -} - -impl PartialEq for NixString { - fn eq(&self, other: &Self) -> bool { - self.as_bstr() == other.as_bstr() - } -} - -impl Eq for NixString {} - -impl PartialEq<&[u8]> for NixString { - fn eq(&self, other: &&[u8]) -> bool { - **self == **other - } -} - -impl PartialEq<&str> for NixString { - fn eq(&self, other: &&str) -> bool { - **self == other.as_bytes() - } -} - -impl PartialOrd for NixString { - fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { - Some(self.cmp(other)) - } -} - -impl Ord for NixString { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.as_bstr().cmp(other.as_bstr()) - } -} - -impl From<Box<BStr>> for NixString { - fn from(value: Box<BStr>) -> Self { - Self::new(&value, None) - } -} - -impl From<BString> for NixString { - fn from(value: BString) -> Self { - Self::new(&value, None) - } -} - -impl From<&BStr> for NixString { - fn from(value: &BStr) -> Self { - value.to_owned().into() - } -} - -impl From<&[u8]> for NixString { - fn from(value: &[u8]) -> Self { - Self::from(value.to_owned()) - } -} - -impl From<Vec<u8>> for NixString { - fn from(value: Vec<u8>) -> Self { - value.into_boxed_slice().into() - } -} - -impl From<Box<[u8]>> for NixString { - fn from(value: Box<[u8]>) -> Self { - Self::new(&value, None) - } -} - -impl From<&str> for NixString { - fn from(s: &str) -> Self { - s.as_bytes().into() - } -} - -impl From<String> for NixString { - fn from(s: String) -> Self { - s.into_bytes().into() - } -} - -impl<T> From<(T, Option<Box<NixContext>>)> for NixString -where - NixString: From<T>, -{ - fn from((s, ctx): (T, Option<Box<NixContext>>)) -> Self { - Self::new(NixString::from(s).as_ref(), ctx) - } -} - -impl From<Box<str>> for NixString { - fn from(s: Box<str>) -> Self { - s.into_boxed_bytes().into() - } -} - -impl From<ast::Ident> for NixString { - fn from(ident: ast::Ident) -> Self { - ident.ident_token().unwrap().text().into() - } -} - -impl<'a> From<&'a NixString> for &'a BStr { - fn from(s: &'a NixString) -> Self { - s.as_bstr() - } -} - -// No impl From<NixString> for String, that one quotes. - -impl From<NixString> for BString { - fn from(s: NixString) -> Self { - s.as_bstr().to_owned() - } -} - -impl AsRef<[u8]> for NixString { - fn as_ref(&self) -> &[u8] { - self.as_bytes() - } -} - -impl Borrow<BStr> for NixString { - fn borrow(&self) -> &BStr { - self.as_bstr() - } -} - -impl Hash for NixString { - fn hash<H: std::hash::Hasher>(&self, state: &mut H) { - self.as_bstr().hash(state) - } -} - -impl<'de> Deserialize<'de> for NixString { - fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> - where - D: Deserializer<'de>, - { - struct StringVisitor; - - impl<'de> Visitor<'de> for StringVisitor { - type Value = NixString; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a valid Nix string") - } - - fn visit_string<E>(self, v: String) -> Result<Self::Value, E> - where - E: serde::de::Error, - { - Ok(v.into()) - } - - fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> - where - E: serde::de::Error, - { - Ok(v.into()) - } - } - - deserializer.deserialize_string(StringVisitor) - } -} - -impl Deref for NixString { - type Target = BStr; - - fn deref(&self) -> &Self::Target { - self.as_bstr() - } -} - -#[cfg(feature = "arbitrary")] -mod arbitrary { - use super::*; - use proptest::prelude::{any_with, Arbitrary}; - use proptest::strategy::{BoxedStrategy, Strategy}; - - impl Arbitrary for NixString { - type Parameters = <String as Arbitrary>::Parameters; - - type Strategy = BoxedStrategy<Self>; - - fn arbitrary_with(args: Self::Parameters) -> Self::Strategy { - any_with::<String>(args).prop_map(Self::from).boxed() - } - } -} - -impl NixString { - fn new(contents: &[u8], context: Option<Box<NixContext>>) -> Self { - // SAFETY: We're always fully initializing a NixString here: - // - // 1. NixStringInner::alloc sets up the len for us - // 2. We set the context, using ptr::write to make sure that the uninitialized memory isn't - // read or dropped - // 3. We set the data, using copy_from_nonoverlapping to make sure that the uninitialized - // memory isn't read or dropped - // - // Only *then* can we construct a NixString - unsafe { - let inner = NixStringInner::alloc(contents.len()); - NixStringInner::context_ptr(inner).write(context); - NixStringInner::data_ptr(inner) - .copy_from_nonoverlapping(contents.as_ptr(), contents.len()); - Self(inner) - } - } - - pub fn new_inherit_context_from<T>(other: &NixString, new_contents: T) -> Self - where - NixString: From<T>, - { - Self::new( - Self::from(new_contents).as_ref(), - other.context().map(|c| Box::new(c.clone())), - ) - } - - pub fn new_context_from<T>(context: NixContext, contents: T) -> Self - where - NixString: From<T>, - { - Self::new( - Self::from(contents).as_ref(), - if context.is_empty() { - None - } else { - Some(Box::new(context)) - }, - ) - } - - pub fn as_bstr(&self) -> &BStr { - BStr::new(self.as_bytes()) - } - - pub fn as_bytes(&self) -> &[u8] { - // SAFETY: There's no way to construct an uninitialized NixString (see the SAFETY comment in - // `new`) - unsafe { NixStringInner::data_slice(self.0) } - } - - pub fn into_bstring(self) -> BString { - self.as_bstr().to_owned() - } - - /// Return a displayable representation of the string as an - /// identifier. - /// - /// This is used when printing out strings used as e.g. attribute - /// set keys, as those are only escaped in the presence of special - /// characters. - pub fn ident_str(&self) -> Cow<str> { - let escaped = match self.to_str_lossy() { - Cow::Borrowed(s) => nix_escape_string(s), - Cow::Owned(s) => nix_escape_string(&s).into_owned().into(), - }; - match escaped { - // A borrowed string is unchanged and can be returned as - // is. - Cow::Borrowed(_) => { - if is_valid_nix_identifier(&escaped) && !is_keyword(&escaped) { - escaped - } else { - Cow::Owned(format!("\"{}\"", escaped)) - } - } - - // An owned string has escapes, and needs the outer quotes - // for display. - Cow::Owned(s) => Cow::Owned(format!("\"{}\"", s)), - } - } - - pub fn concat(&self, other: &Self) -> Self { - let mut s = self.to_vec(); - s.extend(&(***other)); - - let context = [self.context(), other.context()] - .into_iter() - .flatten() - .fold(NixContext::new(), |acc_ctx, new_ctx| { - acc_ctx.join(&mut new_ctx.clone()) - }); - Self::new_context_from(context, s) - } - - pub(crate) fn context(&self) -> Option<&NixContext> { - // SAFETY: There's no way to construct an uninitialized or invalid NixString (see the SAFETY - // comment in `new`). - // - // Also, we're using the same lifetime and mutability as self, to fit the - // pointer-to-reference conversion rules - unsafe { NixStringInner::context_ref(self.0).as_deref() } - } - - pub(crate) fn context_mut(&mut self) -> Option<&mut NixContext> { - // SAFETY: There's no way to construct an uninitialized or invalid NixString (see the SAFETY - // comment in `new`). - // - // Also, we're using the same lifetime and mutability as self, to fit the - // pointer-to-reference conversion rules - unsafe { NixStringInner::context_mut(self.0).as_deref_mut() } - } - - pub fn iter_context(&self) -> impl Iterator<Item = &NixContext> { - self.context().into_iter() - } - - pub fn iter_plain(&self) -> impl Iterator<Item = &str> { - self.iter_context().flat_map(|context| context.iter_plain()) - } - - pub fn iter_derivation(&self) -> impl Iterator<Item = &str> { - return self - .iter_context() - .flat_map(|context| context.iter_derivation()); - } - - pub fn iter_single_outputs(&self) -> impl Iterator<Item = (&str, &str)> { - return self - .iter_context() - .flat_map(|context| context.iter_single_outputs()); - } - - /// Returns whether this Nix string possess a context or not. - pub fn has_context(&self) -> bool { - self.context().is_some() - } - - /// This clears the context of that string, losing - /// all dependency tracking information. - pub fn clear_context(&mut self) { - // SAFETY: There's no way to construct an uninitialized or invalid NixString (see the SAFETY - // comment in `new`). - *unsafe { NixStringInner::context_mut(self.0) } = None; - } - - pub fn chars(&self) -> Chars<'_> { - self.as_bstr().chars() - } -} - -fn nix_escape_char(ch: char, next: Option<&char>) -> Option<&'static str> { - match (ch, next) { - ('\\', _) => Some("\\\\"), - ('"', _) => Some("\\\""), - ('\n', _) => Some("\\n"), - ('\t', _) => Some("\\t"), - ('\r', _) => Some("\\r"), - ('$', Some('{')) => Some("\\$"), - _ => None, - } -} - -/// Return true if this string is a keyword -- character strings -/// which lexically match the "identifier" production but are not -/// parsed as identifiers. See also cppnix commit -/// b72bc4a972fe568744d98b89d63adcd504cb586c. -fn is_keyword(s: &str) -> bool { - matches!( - s, - "if" | "then" | "else" | "assert" | "with" | "let" | "in" | "rec" | "inherit" - ) -} - -/// Return true if this string can be used as an identifier in Nix. -fn is_valid_nix_identifier(s: &str) -> bool { - // adapted from rnix-parser's tokenizer.rs - let mut chars = s.chars(); - match chars.next() { - Some('a'..='z' | 'A'..='Z' | '_') => (), - _ => return false, - } - for c in chars { - match c { - 'a'..='z' | 'A'..='Z' | '0'..='9' | '_' | '-' | '\'' => (), - _ => return false, - } - } - true -} - -/// Escape a Nix string for display, as most user-visible representation -/// are escaped strings. -/// -/// Note that this does not add the outer pair of surrounding quotes. -fn nix_escape_string(input: &str) -> Cow<str> { - let mut iter = input.char_indices().peekable(); - - while let Some((i, c)) = iter.next() { - if let Some(esc) = nix_escape_char(c, iter.peek().map(|(_, c)| c)) { - let mut escaped = String::with_capacity(input.len()); - escaped.push_str(&input[..i]); - escaped.push_str(esc); - - // In theory we calculate how many bytes it takes to represent `esc` - // in UTF-8 and use that for the offset. It is, however, safe to - // assume that to be 1, as all characters that can be escaped in a - // Nix string are ASCII. - let mut inner_iter = input[i + 1..].chars().peekable(); - while let Some(c) = inner_iter.next() { - match nix_escape_char(c, inner_iter.peek()) { - Some(esc) => escaped.push_str(esc), - None => escaped.push(c), - } - } - - return Cow::Owned(escaped); - } - } - - Cow::Borrowed(input) -} - -impl Display for NixString { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str("\"")?; - f.write_str(&nix_escape_string(&self.to_str_lossy()))?; - f.write_str("\"") - } -} - -#[cfg(test)] -mod tests { - use test_strategy::proptest; - - use super::*; - - use crate::properties::{eq_laws, hash_laws, ord_laws}; - - #[test] - fn size() { - assert_eq!(std::mem::size_of::<NixString>(), 8); - } - - #[proptest] - fn clone_strings(s: NixString) { - drop(s.clone()) - } - - eq_laws!(NixString); - hash_laws!(NixString); - ord_laws!(NixString); -} |