feat(tvix/eval): context-aware replaceStrings

And it also preserve the original context if it exists.

Change-Id: I904f7c13b7f003a267aace6301723780fccaafb7
Reviewed-on: https://cl.tvl.fyi/c/depot/+/10434
Tested-by: BuildkiteCI
Reviewed-by: tazjin <tazjin@tvl.su>
Autosubmit: raitobezarius <tvl@lahfa.xyz>
This commit is contained in:
Ryan Lahfa 2023-12-26 01:15:53 +01:00 committed by clbot
parent cbd22af2b5
commit 96d06031af

View file

@ -919,12 +919,17 @@ mod pure_builtins {
generators::request_force(&co, val.clone()).await; generators::request_force(&co, val.clone()).await;
} }
let string = s.to_str()?; let mut string = s.to_contextful_str()?;
let mut res = String::new(); let mut res = String::new();
let mut i: usize = 0; let mut i: usize = 0;
let mut empty_string_replace = false; let mut empty_string_replace = false;
let mut context = NixContext::new();
if let Some(string_context) = string.context_mut() {
context = context.join(string_context);
}
// This can't be implemented using Rust's string.replace() as // This can't be implemented using Rust's string.replace() as
// well as a map because we need to handle errors with results // well as a map because we need to handle errors with results
@ -935,8 +940,8 @@ mod pure_builtins {
'outer: while i < string.len() { 'outer: while i < string.len() {
// Try a match in all the from strings // Try a match in all the from strings
for elem in std::iter::zip(from.iter(), to.iter()) { for elem in std::iter::zip(from.iter(), to.iter()) {
let from = elem.0.to_str()?; let from = elem.0.to_contextful_str()?;
let to = elem.1.to_str()?; let mut to = elem.1.to_contextful_str()?;
if i + from.len() > string.len() { if i + from.len() > string.len() {
continue; continue;
@ -953,6 +958,9 @@ mod pure_builtins {
if &string[i..i + from.len()] == from.as_str() { if &string[i..i + from.len()] == from.as_str() {
res += &to; res += &to;
i += from.len(); i += from.len();
if let Some(to_ctx) = to.context_mut() {
context = context.join(to_ctx);
}
// remember if we applied the empty from->to // remember if we applied the empty from->to
empty_string_replace = from.as_str().is_empty(); empty_string_replace = from.as_str().is_empty();
@ -973,15 +981,24 @@ mod pure_builtins {
// Special case when the string is empty or at the string's end // Special case when the string is empty or at the string's end
// and one of the from is also empty // and one of the from is also empty
for elem in std::iter::zip(from.iter(), to.iter()) { for elem in std::iter::zip(from.iter(), to.iter()) {
let from = elem.0.to_str()?; let from = elem.0.to_contextful_str()?;
let to = elem.1.to_str()?; // We mutate `to` by consuming its context
// if we perform a successful replacement.
// Therefore, it's fine if `to` was mutate and we reuse it here.
// We don't need to merge again the context, it's already in the right state.
let mut to = elem.1.to_contextful_str()?;
if from.as_str().is_empty() { if from.as_str().is_empty() {
res += &to; res += &to;
if let Some(to_ctx) = to.context_mut() {
context = context.join(to_ctx);
}
break; break;
} }
} }
Ok(Value::String(res.into()))
// FIXME: consume directly the String.
Ok(Value::String(NixString::new_context_from(context, &res)))
} }
#[builtin("seq")] #[builtin("seq")]