1use runmat_builtins::{
4 BuiltinCompletionPolicy, BuiltinDescriptor, BuiltinErrorDescriptor, BuiltinOutputMode,
5 BuiltinParamArity, BuiltinParamDescriptor, BuiltinParamType, BuiltinSignatureDescriptor,
6};
7use runmat_builtins::{BuiltinIntegerAuditDescriptor, BuiltinIntegerAuditKind};
8use runmat_macros::runtime_builtin;
9use runmat_value::{CellArray, CharArray, StringArray, Value};
10
11use crate::builtins::common::map_control_flow_with_builtin;
12use crate::builtins::common::spec::{
13 BroadcastSemantics, BuiltinFusionSpec, BuiltinGpuSpec, ConstantStrategy, GpuOpKind,
14 ReductionNaN, ResidencyPolicy, ShapeRequirements,
15};
16use crate::builtins::strings::common::{
17 char_row_to_string_slice, contains_resident_text_input, is_missing_string,
18};
19use crate::builtins::strings::type_resolvers::text_preserve_type;
20use crate::{
21 build_runtime_error, gather_if_needed_async, make_cell_with_shape, BuiltinResult, RuntimeError,
22};
23
24#[runmat_macros::register_gpu_spec(builtin_path = "crate::builtins::strings::transform::strrep")]
25pub const GPU_SPEC: BuiltinGpuSpec = BuiltinGpuSpec {
26 name: "strrep",
27 op_kind: GpuOpKind::Custom("string-transform"),
28 supported_precisions: &[],
29 broadcast: BroadcastSemantics::None,
30 provider_hooks: &[],
31 constant_strategy: ConstantStrategy::InlineLiteral,
32 residency: ResidencyPolicy::GatherImmediately,
33 nan_mode: ReductionNaN::Include,
34 two_pass_threshold: None,
35 workgroup_size: None,
36 accepts_nan_mode: false,
37 notes: "Executes on the CPU; GPU-resident inputs are gathered before replacements are applied.",
38};
39
40#[runmat_macros::register_fusion_spec(builtin_path = "crate::builtins::strings::transform::strrep")]
41pub const FUSION_SPEC: BuiltinFusionSpec = BuiltinFusionSpec {
42 name: "strrep",
43 shape: ShapeRequirements::Any,
44 constant_strategy: ConstantStrategy::InlineLiteral,
45 elementwise: None,
46 reduction: None,
47 emits_nan: false,
48 notes: "String transformation builtin; marked as a sink so fusion skips GPU residency.",
49};
50
51const BUILTIN_NAME: &str = "strrep";
52
53const STRREP_OUTPUT: [BuiltinParamDescriptor; 1] = [BuiltinParamDescriptor {
54 name: "newStr",
55 ty: BuiltinParamType::Any,
56 arity: BuiltinParamArity::Required,
57 default: None,
58 description: "Text with pattern occurrences replaced, preserving input container kind.",
59}];
60
61const STRREP_INPUTS: [BuiltinParamDescriptor; 3] = [
62 BuiltinParamDescriptor {
63 name: "str",
64 ty: BuiltinParamType::Any,
65 arity: BuiltinParamArity::Required,
66 default: None,
67 description: "Input text (string/char/cell).",
68 },
69 BuiltinParamDescriptor {
70 name: "old",
71 ty: BuiltinParamType::Any,
72 arity: BuiltinParamArity::Required,
73 default: None,
74 description: "Pattern text scalar (string or char row).",
75 },
76 BuiltinParamDescriptor {
77 name: "new",
78 ty: BuiltinParamType::Any,
79 arity: BuiltinParamArity::Required,
80 default: None,
81 description: "Replacement text scalar matching old's data type family.",
82 },
83];
84
85const STRREP_SIGNATURES: [BuiltinSignatureDescriptor; 1] = [BuiltinSignatureDescriptor {
86 label: "newStr = strrep(str, old, new)",
87 inputs: &STRREP_INPUTS,
88 outputs: &STRREP_OUTPUT,
89}];
90
91const STRREP_ERROR_INVALID_INPUT: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
92 code: "RM.STRREP.INVALID_INPUT",
93 identifier: Some("RunMat:strrep:InvalidInput"),
94 when: "First argument is not a string array, char array, or cell array of text scalars.",
95 message:
96 "strrep: first argument must be a string array, character array, or cell array of character vectors",
97};
98
99const STRREP_ERROR_PATTERN_TYPE: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
100 code: "RM.STRREP.PATTERN_TYPE",
101 identifier: Some("RunMat:strrep:PatternType"),
102 when: "old/new arguments are not string scalars or character vectors.",
103 message: "strrep: old and new must be string scalars or character vectors",
104};
105
106const STRREP_ERROR_PATTERN_MISMATCH: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
107 code: "RM.STRREP.PATTERN_MISMATCH",
108 identifier: Some("RunMat:strrep:PatternMismatch"),
109 when: "old and new are different text data families (string vs char).",
110 message: "strrep: old and new must be the same data type",
111};
112
113const STRREP_ERROR_CELL_ELEMENT: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
114 code: "RM.STRREP.CELL_ELEMENT",
115 identifier: Some("RunMat:strrep:CellElement"),
116 when: "Cell input contains non-text elements or non-row char arrays.",
117 message: "strrep: cell array elements must be string scalars or character vectors",
118};
119
120const STRREP_ERROR_INTERNAL: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
121 code: "RM.STRREP.INTERNAL",
122 identifier: Some("RunMat:strrep:InternalError"),
123 when: "Internal output container construction failed.",
124 message: "strrep: internal error",
125};
126
127const STRREP_ERRORS: [BuiltinErrorDescriptor; 5] = [
128 STRREP_ERROR_INVALID_INPUT,
129 STRREP_ERROR_PATTERN_TYPE,
130 STRREP_ERROR_PATTERN_MISMATCH,
131 STRREP_ERROR_CELL_ELEMENT,
132 STRREP_ERROR_INTERNAL,
133];
134
135pub const STRREP_DESCRIPTOR: BuiltinDescriptor = BuiltinDescriptor {
136 signatures: &STRREP_SIGNATURES,
137 output_mode: BuiltinOutputMode::Fixed,
138 completion_policy: BuiltinCompletionPolicy::Public,
139 errors: &STRREP_ERRORS,
140};
141
142pub const STRREP_INTEGER_AUDIT: BuiltinIntegerAuditDescriptor = BuiltinIntegerAuditDescriptor {
143 kind: BuiltinIntegerAuditKind::NotApplicable,
144 canonical_builtin: None,
145 notes: "strrep accepts text in the subject, old-pattern, and replacement roles. Numeric, integer, and provider-resident values reject before provider access and are never interpreted as character codes.",
146};
147
148#[derive(Clone, Copy, PartialEq, Eq)]
149enum PatternKind {
150 String,
151 Char,
152}
153
154fn map_flow(err: RuntimeError) -> RuntimeError {
155 map_control_flow_with_builtin(err, BUILTIN_NAME)
156}
157
158fn strrep_error_with_message(
159 message: impl Into<String>,
160 error: &'static BuiltinErrorDescriptor,
161) -> RuntimeError {
162 let mut builder = build_runtime_error(message).with_builtin(BUILTIN_NAME);
163 if let Some(identifier) = error.identifier {
164 builder = builder.with_identifier(identifier);
165 }
166 builder.build()
167}
168
169fn strrep_error(error: &'static BuiltinErrorDescriptor) -> RuntimeError {
170 strrep_error_with_message(error.message, error)
171}
172
173#[runtime_builtin(
174 name = "strrep",
175 category = "strings/transform",
176 summary = "Replace non-overlapping substring occurrences in text inputs.",
177 keywords = "strrep,replace,strings,character array,text",
178 accel = "sink",
179 type_resolver(text_preserve_type),
180 descriptor(crate::builtins::strings::transform::strrep::STRREP_DESCRIPTOR),
181 integer_audit(crate::builtins::strings::transform::strrep::STRREP_INTEGER_AUDIT),
182 builtin_path = "crate::builtins::strings::transform::strrep"
183)]
184async fn strrep_builtin(
185 str_value: Value,
186 old_value: Value,
187 new_value: Value,
188) -> BuiltinResult<Value> {
189 if contains_resident_text_input(&str_value)
190 || contains_resident_text_input(&old_value)
191 || contains_resident_text_input(&new_value)
192 {
193 return Err(strrep_error(&STRREP_ERROR_INVALID_INPUT));
194 }
195 let gathered_str = gather_if_needed_async(&str_value).await.map_err(map_flow)?;
196 let gathered_old = gather_if_needed_async(&old_value).await.map_err(map_flow)?;
197 let gathered_new = gather_if_needed_async(&new_value).await.map_err(map_flow)?;
198
199 let (old_text, old_kind) = parse_pattern(gathered_old)?;
200 let (new_text, new_kind) = parse_pattern(gathered_new)?;
201 if old_kind != new_kind {
202 return Err(strrep_error(&STRREP_ERROR_PATTERN_MISMATCH));
203 }
204
205 match gathered_str {
206 Value::String(text) => Ok(Value::String(strrep_string_value(
207 text, &old_text, &new_text,
208 ))),
209 Value::StringArray(array) => strrep_string_array(array, &old_text, &new_text),
210 Value::CharArray(array) => strrep_char_array(array, &old_text, &new_text),
211 Value::Cell(cell) => strrep_cell_array(cell, &old_text, &new_text),
212 _ => Err(strrep_error(&STRREP_ERROR_INVALID_INPUT)),
213 }
214}
215
216fn parse_pattern(value: Value) -> BuiltinResult<(String, PatternKind)> {
217 match value {
218 Value::String(text) => Ok((text, PatternKind::String)),
219 Value::StringArray(array) => {
220 if array.data.len() == 1 {
221 Ok((array.data[0].clone(), PatternKind::String))
222 } else {
223 Err(strrep_error(&STRREP_ERROR_PATTERN_TYPE))
224 }
225 }
226 Value::CharArray(array) => {
227 if array.rows <= 1 {
228 let text = if array.rows == 0 {
229 String::new()
230 } else {
231 char_row_to_string_slice(&array.data, array.cols, 0)
232 };
233 Ok((text, PatternKind::Char))
234 } else {
235 Err(strrep_error(&STRREP_ERROR_PATTERN_TYPE))
236 }
237 }
238 _ => Err(strrep_error(&STRREP_ERROR_PATTERN_TYPE)),
239 }
240}
241
242fn strrep_string_value(text: String, old: &str, new: &str) -> String {
243 if is_missing_string(&text) {
244 text
245 } else {
246 text.replace(old, new)
247 }
248}
249
250fn strrep_string_array(array: StringArray, old: &str, new: &str) -> BuiltinResult<Value> {
251 let StringArray { data, shape, .. } = array;
252 let replaced = data
253 .into_iter()
254 .map(|text| strrep_string_value(text, old, new))
255 .collect::<Vec<_>>();
256 let rebuilt = StringArray::new(replaced, shape).map_err(|e| {
257 strrep_error_with_message(format!("{BUILTIN_NAME}: {e}"), &STRREP_ERROR_INTERNAL)
258 })?;
259 Ok(Value::StringArray(rebuilt))
260}
261
262fn strrep_char_array(array: CharArray, old: &str, new: &str) -> BuiltinResult<Value> {
263 let CharArray {
264 data,
265 shape,
266 rows,
267 cols,
268 } = array;
269 if rows == 0 || cols == 0 {
270 return Ok(Value::CharArray(CharArray {
271 data,
272 shape,
273 rows,
274 cols,
275 }));
276 }
277
278 let mut replaced_rows = Vec::with_capacity(rows);
279 let mut target_cols = 0usize;
280 for row in 0..rows {
281 let text = char_row_to_string_slice(&data, cols, row);
282 let replaced = text.replace(old, new);
283 target_cols = target_cols.max(replaced.chars().count());
284 replaced_rows.push(replaced);
285 }
286
287 let mut new_data = Vec::with_capacity(rows * target_cols);
288 for row_text in replaced_rows {
289 let mut chars: Vec<char> = row_text.chars().collect();
290 if chars.len() < target_cols {
291 chars.resize(target_cols, ' ');
292 }
293 new_data.extend(chars);
294 }
295
296 CharArray::new(new_data, rows, target_cols)
297 .map(Value::CharArray)
298 .map_err(|e| {
299 strrep_error_with_message(format!("{BUILTIN_NAME}: {e}"), &STRREP_ERROR_INTERNAL)
300 })
301}
302
303fn strrep_cell_array(cell: CellArray, old: &str, new: &str) -> BuiltinResult<Value> {
304 let CellArray { data, shape, .. } = cell;
305 let mut replaced = Vec::with_capacity(data.len());
306 for ptr in &data {
307 replaced.push(strrep_cell_element(ptr, old, new)?);
308 }
309 make_cell_with_shape(replaced, shape).map_err(|e| {
310 strrep_error_with_message(format!("{BUILTIN_NAME}: {e}"), &STRREP_ERROR_INTERNAL)
311 })
312}
313
314fn strrep_cell_element(value: &Value, old: &str, new: &str) -> BuiltinResult<Value> {
315 match value {
316 Value::String(text) => Ok(Value::String(strrep_string_value(text.clone(), old, new))),
317 Value::StringArray(array) => strrep_string_array(array.clone(), old, new),
318 Value::CharArray(array) => strrep_char_array(array.clone(), old, new),
319 _ => Err(strrep_error(&STRREP_ERROR_CELL_ELEMENT)),
320 }
321}
322
323#[cfg(test)]
324pub(crate) mod tests {
325 use super::*;
326 use runmat_builtins::{ResolveContext, Type};
327
328 fn run_strrep(str_value: Value, old_value: Value, new_value: Value) -> BuiltinResult<Value> {
329 futures::executor::block_on(strrep_builtin(str_value, old_value, new_value))
330 }
331
332 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
333 #[test]
334 fn strrep_string_scalar_basic() {
335 let result = run_strrep(
336 Value::String("RunMat Ignite".into()),
337 Value::String("Ignite".into()),
338 Value::String("Interpreter".into()),
339 )
340 .expect("strrep");
341 assert_eq!(result, Value::String("RunMat Interpreter".into()));
342 }
343
344 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
345 #[test]
346 fn strrep_string_array_preserves_missing() {
347 let array = StringArray::new(
348 vec![
349 String::from("gpu"),
350 String::from("<missing>"),
351 String::from("planner"),
352 ],
353 vec![3, 1],
354 )
355 .unwrap();
356 let result = run_strrep(
357 Value::StringArray(array),
358 Value::String("gpu".into()),
359 Value::String("GPU".into()),
360 )
361 .expect("strrep");
362 match result {
363 Value::StringArray(sa) => {
364 assert_eq!(sa.shape, vec![3, 1]);
365 assert_eq!(
366 sa.data,
367 vec![
368 String::from("GPU"),
369 String::from("<missing>"),
370 String::from("planner")
371 ]
372 );
373 }
374 other => panic!("expected string array, got {other:?}"),
375 }
376 }
377
378 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
379 #[test]
380 fn strrep_string_array_with_char_pattern() {
381 let array = StringArray::new(
382 vec![String::from("alpha"), String::from("beta")],
383 vec![2, 1],
384 )
385 .unwrap();
386 let result = run_strrep(
387 Value::StringArray(array),
388 Value::CharArray(CharArray::new_row("a")),
389 Value::CharArray(CharArray::new_row("A")),
390 )
391 .expect("strrep");
392 match result {
393 Value::StringArray(sa) => {
394 assert_eq!(sa.shape, vec![2, 1]);
395 assert_eq!(sa.data, vec![String::from("AlphA"), String::from("betA")]);
396 }
397 other => panic!("expected string array, got {other:?}"),
398 }
399 }
400
401 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
402 #[test]
403 fn strrep_char_array_padding() {
404 let chars = CharArray::new(vec!['R', 'u', 'n', ' ', 'M', 'a', 't'], 1, 7).unwrap();
405 let result = run_strrep(
406 Value::CharArray(chars),
407 Value::String(" ".into()),
408 Value::String("_".into()),
409 )
410 .expect("strrep");
411 match result {
412 Value::CharArray(out) => {
413 assert_eq!(out.rows, 1);
414 assert_eq!(out.cols, 7);
415 let expected: Vec<char> = "Run_Mat".chars().collect();
416 assert_eq!(out.data, expected);
417 }
418 other => panic!("expected char array, got {other:?}"),
419 }
420 }
421
422 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
423 #[test]
424 fn strrep_char_array_shrinks_rows_pad_with_spaces() {
425 let mut data: Vec<char> = "alpha".chars().collect();
426 data.extend("beta ".chars());
427 let array = CharArray::new(data, 2, 5).unwrap();
428 let result = run_strrep(
429 Value::CharArray(array),
430 Value::String("a".into()),
431 Value::String("".into()),
432 )
433 .expect("strrep");
434 match result {
435 Value::CharArray(out) => {
436 assert_eq!(out.rows, 2);
437 assert_eq!(out.cols, 4);
438 let expected: Vec<char> = vec!['l', 'p', 'h', ' ', 'b', 'e', 't', ' '];
439 assert_eq!(out.data, expected);
440 }
441 other => panic!("expected char array, got {other:?}"),
442 }
443 }
444
445 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
446 #[test]
447 fn strrep_cell_array_char_vectors() {
448 let cell = CellArray::new(
449 vec![
450 Value::CharArray(CharArray::new_row("Kernel Fusion")),
451 Value::CharArray(CharArray::new_row("GPU Planner")),
452 ],
453 1,
454 2,
455 )
456 .unwrap();
457 let result = run_strrep(
458 Value::Cell(cell),
459 Value::String(" ".into()),
460 Value::String("_".into()),
461 )
462 .expect("strrep");
463 match result {
464 Value::Cell(out) => {
465 assert_eq!(out.rows, 1);
466 assert_eq!(out.cols, 2);
467 assert_eq!(
468 out.get(0, 0).unwrap(),
469 Value::CharArray(CharArray::new_row("Kernel_Fusion"))
470 );
471 assert_eq!(
472 out.get(0, 1).unwrap(),
473 Value::CharArray(CharArray::new_row("GPU_Planner"))
474 );
475 }
476 other => panic!("expected cell array, got {other:?}"),
477 }
478 }
479
480 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
481 #[test]
482 fn strrep_cell_array_string_scalars() {
483 let cell = CellArray::new(
484 vec![
485 Value::String("Planner".into()),
486 Value::String("Profiler".into()),
487 ],
488 1,
489 2,
490 )
491 .unwrap();
492 let result = run_strrep(
493 Value::Cell(cell),
494 Value::String("er".into()),
495 Value::String("ER".into()),
496 )
497 .expect("strrep");
498 match result {
499 Value::Cell(out) => {
500 assert_eq!(out.rows, 1);
501 assert_eq!(out.cols, 2);
502 assert_eq!(out.get(0, 0).unwrap(), Value::String("PlannER".into()));
503 assert_eq!(out.get(0, 1).unwrap(), Value::String("ProfilER".into()));
504 }
505 other => panic!("expected cell array, got {other:?}"),
506 }
507 }
508
509 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
510 #[test]
511 fn strrep_cell_array_invalid_element_error() {
512 let cell = CellArray::new(vec![Value::Num(1.0)], 1, 1).unwrap();
513 let err = run_strrep(
514 Value::Cell(cell),
515 Value::String("1".into()),
516 Value::String("one".into()),
517 )
518 .expect_err("expected cell element error");
519 assert!(err.to_string().contains("cell array elements"));
520 }
521
522 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
523 #[test]
524 fn strrep_cell_array_char_matrix_element() {
525 let mut chars: Vec<char> = "alpha".chars().collect();
526 chars.extend("beta ".chars());
527 let element = CharArray::new(chars, 2, 5).unwrap();
528 let cell = CellArray::new(vec![Value::CharArray(element)], 1, 1).unwrap();
529 let result = run_strrep(
530 Value::Cell(cell),
531 Value::String("a".into()),
532 Value::String("A".into()),
533 )
534 .expect("strrep");
535 match result {
536 Value::Cell(out) => {
537 let nested = out.get(0, 0).unwrap();
538 match nested {
539 Value::CharArray(ca) => {
540 assert_eq!(ca.rows, 2);
541 assert_eq!(ca.cols, 5);
542 let expected: Vec<char> =
543 vec!['A', 'l', 'p', 'h', 'A', 'b', 'e', 't', 'A', ' '];
544 assert_eq!(ca.data, expected);
545 }
546 other => panic!("expected char array element, got {other:?}"),
547 }
548 }
549 other => panic!("expected cell array, got {other:?}"),
550 }
551 }
552
553 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
554 #[test]
555 fn strrep_cell_array_string_arrays() {
556 let element = StringArray::new(vec!["alpha".into(), "beta".into()], vec![1, 2]).unwrap();
557 let cell = CellArray::new(vec![Value::StringArray(element)], 1, 1).unwrap();
558 let result = run_strrep(
559 Value::Cell(cell),
560 Value::String("a".into()),
561 Value::String("A".into()),
562 )
563 .expect("strrep");
564 match result {
565 Value::Cell(out) => {
566 let nested = out.get(0, 0).unwrap();
567 match nested {
568 Value::StringArray(sa) => {
569 assert_eq!(sa.shape, vec![1, 2]);
570 assert_eq!(sa.data, vec![String::from("AlphA"), String::from("betA")]);
571 }
572 other => panic!("expected string array element, got {other:?}"),
573 }
574 }
575 other => panic!("expected cell array, got {other:?}"),
576 }
577 }
578
579 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
580 #[test]
581 fn strrep_empty_pattern_inserts_replacement() {
582 let result = run_strrep(
583 Value::String("abc".into()),
584 Value::String("".into()),
585 Value::String("-".into()),
586 )
587 .expect("strrep");
588 assert_eq!(result, Value::String("-a-b-c-".into()));
589 }
590
591 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
592 #[test]
593 fn strrep_type_mismatch_errors() {
594 let err = run_strrep(
595 Value::String("abc".into()),
596 Value::String("a".into()),
597 Value::CharArray(CharArray::new_row("x")),
598 )
599 .expect_err("expected type mismatch");
600 assert!(err.to_string().contains("same data type"));
601 }
602
603 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
604 #[test]
605 fn strrep_invalid_pattern_type_errors() {
606 let err = run_strrep(
607 Value::String("abc".into()),
608 Value::Num(1.0),
609 Value::String("x".into()),
610 )
611 .expect_err("expected pattern error");
612 assert!(err
613 .to_string()
614 .contains("string scalars or character vectors"));
615 }
616
617 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
618 #[test]
619 fn strrep_first_argument_type_error() {
620 let err = run_strrep(
621 Value::Num(42.0),
622 Value::String("a".into()),
623 Value::String("b".into()),
624 )
625 .expect_err("expected argument type error");
626 assert!(err.to_string().contains("first argument"));
627 }
628
629 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
630 #[test]
631 #[cfg(feature = "wgpu")]
632 fn strrep_wgpu_provider_fallback() {
633 if runmat_accelerate::backend::wgpu::provider::register_wgpu_provider(
634 runmat_accelerate::backend::wgpu::provider::WgpuProviderOptions::default(),
635 )
636 .is_err()
637 {
638 return;
640 }
641 let result = run_strrep(
642 Value::String("Native Engine".into()),
643 Value::String("Engine".into()),
644 Value::String("JIT".into()),
645 )
646 .expect("strrep");
647 assert_eq!(result, Value::String("Native JIT".into()));
648 }
649
650 #[test]
651 fn strrep_type_preserves_text() {
652 assert_eq!(
653 text_preserve_type(&[Type::String], &ResolveContext::new(Vec::new())),
654 Type::String
655 );
656 }
657}