--- a/crates/jrsonnet-evaluator/src/evaluate/operator.rs +++ b/crates/jrsonnet-evaluator/src/evaluate/operator.rs @@ -24,13 +24,9 @@ }) } -/// Arbitrary threshold - pub fn evaluate_add_op(a: &Val, b: &Val) -> Result { use Val::*; Ok(match (a, b) { - (Str(a), Str(b)) if a.is_empty() => Val::Str(b.clone()), - (Str(a), Str(b)) if b.is_empty() => Val::Str(a.clone()), (Str(v1), Str(v2)) => Str(StrValue::concat(v1.clone(), v2.clone())), // Can't use generic json serialization way, because it depends on number to string concatenation (std.jsonnet:890) --- a/crates/jrsonnet-evaluator/src/val.rs +++ b/crates/jrsonnet-evaluator/src/val.rs @@ -199,34 +199,40 @@ } impl StrValue { pub fn concat(a: StrValue, b: StrValue) -> Self { + // TODO: benchmark for an optimal value, currently just a arbitrary choice + const STRING_EXTEND_THRESHOLD: usize = 100; + if a.is_empty() { b } else if b.is_empty() { a + } else if a.len() + b.len() < STRING_EXTEND_THRESHOLD { + Self::Flat(format!("{a}{b}").into()) } else { let len = a.len() + b.len(); Self::Tree(Rc::new((a, b, len))) } } pub fn into_flat(self) -> IStr { + #[cold] + fn write_buf(s: &StrValue, out: &mut String) { + match s { + StrValue::Flat(f) => out.push_str(f), + StrValue::Tree(t) => { + write_buf(&t.0, out); + write_buf(&t.1, out); + } + } + } match self { StrValue::Flat(f) => f, StrValue::Tree(_) => { - let mut buf = String::new(); - self.into_flat_buf(&mut buf); + let mut buf = String::with_capacity(self.len()); + write_buf(&self, &mut buf); buf.into() } } } - fn into_flat_buf(&self, out: &mut String) { - match self { - StrValue::Flat(f) => out.push_str(f), - StrValue::Tree(t) => { - t.0.into_flat_buf(out); - t.1.into_flat_buf(out); - } - } - } pub fn len(&self) -> usize { match self { StrValue::Flat(v) => v.len(), @@ -236,7 +242,8 @@ pub fn is_empty(&self) -> bool { match self { Self::Flat(v) => v.is_empty(), - _ => false, + // Can't create non-flat empty string + Self::Tree(_) => false, } } }