about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--tvix/eval/src/value/string.rs56
1 files changed, 46 insertions, 10 deletions
diff --git a/tvix/eval/src/value/string.rs b/tvix/eval/src/value/string.rs
index 1937a35870d3..0b665a0a5ec6 100644
--- a/tvix/eval/src/value/string.rs
+++ b/tvix/eval/src/value/string.rs
@@ -1,4 +1,4 @@
-use std::fmt::Display;
+use std::{borrow::Cow, fmt::Display};
 
 /// This module implements Nix language strings and their different
 /// backing implementations.
@@ -9,15 +9,6 @@ pub enum NixString {
     Heap(String),
 }
 
-impl Display for NixString {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        match self {
-            NixString::Static(s) => f.write_str(s),
-            NixString::Heap(s) => f.write_str(s),
-        }
-    }
-}
-
 impl PartialEq for NixString {
     fn eq(&self, other: &Self) -> bool {
         self.as_str() == other.as_str()
@@ -53,3 +44,48 @@ impl NixString {
         }
     }
 }
+
+fn nix_escape_char(ch: char) -> Option<&'static str> {
+    match ch {
+        '\\' => Some("\\"),
+        '"' => Some("\\"),
+        '\n' => Some("\\n"),
+        _ => None,
+    }
+}
+
+// Escape a Nix string for display, as the user-visible representation
+// is always an escaped string (except for traces).
+//
+// Note that this does not add the outer pair of surrounding quotes.
+fn escape_string(input: &str) -> Cow<str> {
+    for (i, c) in input.chars().enumerate() {
+        if let Some(esc) = nix_escape_char(c) {
+            let mut escaped = String::with_capacity(input.len());
+            escaped.push_str(&input[..i]);
+            escaped.push_str(esc);
+
+            for c in input[i + 1..].chars() {
+                match nix_escape_char(c) {
+                    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("\"")?;
+        match self {
+            NixString::Static(s) => f.write_str(&escape_string(s))?,
+            NixString::Heap(s) => f.write_str(&escape_string(s))?,
+        };
+        f.write_str("\"")
+    }
+}