dsq_functions/builtin/
replace.rs

1use dsq_shared::value::Value;
2use dsq_shared::Result;
3use inventory;
4
5pub fn builtin_replace(args: &[Value]) -> Result<Value> {
6    if args.len() != 3 {
7        return Err(dsq_shared::error::operation_error(
8            "replace() expects 3 arguments",
9        ));
10    }
11
12    match (&args[0], &args[1], &args[2]) {
13        (Value::String(s), Value::String(from), Value::String(to)) => {
14            Ok(Value::String(s.replace(from, to)))
15        }
16        _ => Err(dsq_shared::error::operation_error(
17            "replace() requires string arguments",
18        )),
19    }
20}
21
22inventory::submit! {
23    crate::FunctionRegistration {
24        name: "replace",
25        func: builtin_replace,
26    }
27}
28
29#[cfg(test)]
30mod tests {
31    use super::*;
32    use dsq_shared::value::Value;
33
34    #[test]
35    fn test_replace_basic() {
36        let result = builtin_replace(&[
37            Value::String("hello world".to_string()),
38            Value::String("world".to_string()),
39            Value::String("universe".to_string()),
40        ])
41        .unwrap();
42        assert_eq!(result, Value::String("hello universe".to_string()));
43    }
44
45    #[test]
46    fn test_replace_no_match() {
47        let result = builtin_replace(&[
48            Value::String("hello world".to_string()),
49            Value::String("foo".to_string()),
50            Value::String("bar".to_string()),
51        ])
52        .unwrap();
53        assert_eq!(result, Value::String("hello world".to_string()));
54    }
55
56    #[test]
57    fn test_replace_multiple_occurrences() {
58        let result = builtin_replace(&[
59            Value::String("foo foo foo".to_string()),
60            Value::String("foo".to_string()),
61            Value::String("bar".to_string()),
62        ])
63        .unwrap();
64        assert_eq!(result, Value::String("bar bar bar".to_string()));
65    }
66
67    #[test]
68    fn test_replace_empty_from() {
69        let result = builtin_replace(&[
70            Value::String("ab".to_string()),
71            Value::String("".to_string()),
72            Value::String("x".to_string()),
73        ])
74        .unwrap();
75        // In Rust, s.replace("", "x") inserts x between every character
76        assert_eq!(result, Value::String("xaxbx".to_string()));
77    }
78
79    #[test]
80    fn test_replace_non_string_args() {
81        let result = builtin_replace(&[
82            Value::Int(123),
83            Value::String("2".to_string()),
84            Value::String("3".to_string()),
85        ]);
86        assert!(result.is_err());
87        assert!(result
88            .unwrap_err()
89            .to_string()
90            .contains("requires string arguments"));
91    }
92
93    #[test]
94    fn test_replace_wrong_number_of_args() {
95        let result = builtin_replace(&[Value::String("test".to_string())]);
96        assert!(result.is_err());
97        assert!(result
98            .unwrap_err()
99            .to_string()
100            .contains("expects 3 arguments"));
101
102        let result = builtin_replace(&[
103            Value::String("test".to_string()),
104            Value::String("t".to_string()),
105            Value::String("r".to_string()),
106            Value::String("extra".to_string()),
107        ]);
108        assert!(result.is_err());
109        assert!(result
110            .unwrap_err()
111            .to_string()
112            .contains("expects 3 arguments"));
113    }
114
115    #[test]
116    fn test_replace_unicode() {
117        let result = builtin_replace(&[
118            Value::String("héllo wörld".to_string()),
119            Value::String("wörld".to_string()),
120            Value::String("universe".to_string()),
121        ])
122        .unwrap();
123        assert_eq!(result, Value::String("héllo universe".to_string()));
124    }
125
126    #[test]
127    fn test_replace_overlapping() {
128        let result = builtin_replace(&[
129            Value::String("aaa".to_string()),
130            Value::String("aa".to_string()),
131            Value::String("b".to_string()),
132        ])
133        .unwrap();
134        // Rust's replace doesn't handle overlapping, it replaces non-overlapping
135        assert_eq!(result, Value::String("ba".to_string()));
136    }
137}