json_eval_rs/rlogic/evaluator/
string_ops.rs

1use super::Evaluator;
2use serde_json::Value;
3use super::super::compiled::CompiledLogic;
4use super::helpers;
5
6impl Evaluator {
7    /// Concatenate string values from items - ZERO-COPY
8    #[inline]
9    pub(super) fn concat_strings(&self, items: &[CompiledLogic], user_data: &Value, internal_context: &Value, depth: usize) -> Result<Value, String> {
10        let mut result = String::new();
11        for item in items {
12            let val = self.evaluate_with_context(item, user_data, internal_context, depth + 1)?;
13            result.push_str(&helpers::to_string(&val));
14        }
15        Ok(Value::String(result))
16    }
17
18    /// Extract text from left or right (is_left=true for Left, false for Right) - ZERO-COPY
19    pub(super) fn extract_text_side(&self, text_expr: &CompiledLogic, num_expr: Option<&CompiledLogic>, is_left: bool, user_data: &Value, internal_context: &Value, depth: usize) -> Result<Value, String> {
20        let text_val = self.evaluate_with_context(text_expr, user_data, internal_context, depth + 1)?;
21        let text = helpers::to_string(&text_val);
22        let num_chars = if let Some(n_expr) = num_expr {
23            let n_val = self.evaluate_with_context(n_expr, user_data, internal_context, depth + 1)?;
24            helpers::to_number(&n_val) as usize
25        } else { 1 };
26
27        if is_left {
28            Ok(Value::String(text.chars().take(num_chars).collect()))
29        } else {
30            let chars: Vec<char> = text.chars().collect();
31            let start = chars.len().saturating_sub(num_chars);
32            Ok(Value::String(chars[start..].iter().collect()))
33        }
34    }
35
36    /// Evaluate substring operation - ZERO-COPY
37    pub(super) fn eval_substr(&self, string_expr: &CompiledLogic, start_expr: &CompiledLogic, length_expr: &Option<Box<CompiledLogic>>, user_data: &Value, internal_context: &Value, depth: usize) -> Result<Value, String> {
38        let string_val = self.evaluate_with_context(string_expr, user_data, internal_context, depth + 1)?;
39        let start_val = self.evaluate_with_context(start_expr, user_data, internal_context, depth + 1)?;
40
41        let s = helpers::to_string(&string_val);
42        let start = helpers::to_number(&start_val) as i32;
43
44        let start_idx = if start < 0 {
45            (s.len() as i32 + start).max(0) as usize
46        } else {
47            start.min(s.len() as i32) as usize
48        };
49
50        if let Some(len_expr) = length_expr {
51            let length_val = self.evaluate_with_context(len_expr, user_data, internal_context, depth + 1)?;
52            let length = helpers::to_number(&length_val) as usize;
53            let end_idx = (start_idx + length).min(s.len());
54            Ok(Value::String(s[start_idx..end_idx].to_string()))
55        } else {
56            Ok(Value::String(s[start_idx..].to_string()))
57        }
58    }
59
60    /// Evaluate search operation - ZERO-COPY
61    pub(super) fn eval_search(&self, find_expr: &CompiledLogic, within_expr: &CompiledLogic, start_expr: &Option<Box<CompiledLogic>>, user_data: &Value, internal_context: &Value, depth: usize) -> Result<Value, String> {
62        let find_val = self.evaluate_with_context(find_expr, user_data, internal_context, depth + 1)?;
63        let within_val = self.evaluate_with_context(within_expr, user_data, internal_context, depth + 1)?;
64
65        if let (Value::String(find), Value::String(within)) = (&find_val, &within_val) {
66            let start = if let Some(start_e) = start_expr {
67                let start_val = self.evaluate_with_context(start_e, user_data, internal_context, depth + 1)?;
68                (helpers::to_number(&start_val) as usize).saturating_sub(1)
69            } else {
70                0
71            };
72
73            if let Some(pos) = within.to_lowercase()[start..].find(&find.to_lowercase()) {
74                Ok(self.f64_to_json((pos + start + 1) as f64))
75            } else {
76                Ok(Value::Null)
77            }
78        } else {
79            Ok(Value::Null)
80        }
81    }
82
83    /// Evaluate mid operation (substring from position with length) - ZERO-COPY
84    pub(super) fn eval_mid(&self, text_expr: &CompiledLogic, start_expr: &CompiledLogic, num_expr: &CompiledLogic, user_data: &Value, internal_context: &Value, depth: usize) -> Result<Value, String> {
85        let text_val = self.evaluate_with_context(text_expr, user_data, internal_context, depth + 1)?;
86        let start_val = self.evaluate_with_context(start_expr, user_data, internal_context, depth + 1)?;
87        let num_val = self.evaluate_with_context(num_expr, user_data, internal_context, depth + 1)?;
88
89        let text = helpers::to_string(&text_val);
90        let start = (helpers::to_number(&start_val) as usize).saturating_sub(1);
91        let num_chars = helpers::to_number(&num_val) as usize;
92
93        let chars: Vec<char> = text.chars().collect();
94        let end = (start + num_chars).min(chars.len());
95        Ok(Value::String(chars[start..end].iter().collect()))
96    }
97
98    /// Evaluate split text operation - ZERO-COPY
99    pub(super) fn eval_split_text(&self, value_expr: &CompiledLogic, sep_expr: &CompiledLogic, index_expr: &Option<Box<CompiledLogic>>, user_data: &Value, internal_context: &Value, depth: usize) -> Result<Value, String> {
100        let value_val = self.evaluate_with_context(value_expr, user_data, internal_context, depth + 1)?;
101        let sep_val = self.evaluate_with_context(sep_expr, user_data, internal_context, depth + 1)?;
102
103        let text = helpers::to_string(&value_val);
104        let separator = helpers::to_string(&sep_val);
105        let index = if let Some(idx_expr) = index_expr {
106            let idx_val = self.evaluate_with_context(idx_expr, user_data, internal_context, depth + 1)?;
107            helpers::to_number(&idx_val) as usize
108        } else {
109            0
110        };
111
112        let parts: Vec<&str> = text.split(&separator).collect();
113        Ok(Value::String(parts.get(index).unwrap_or(&"").to_string()))
114    }
115
116    /// Format number with prefix, suffix, decimals, and thousands separator (Excel-like TEXT function)
117    pub(super) fn eval_string_format(
118        &self,
119        value_expr: &CompiledLogic,
120        decimals_expr: &Option<Box<CompiledLogic>>,
121        prefix_expr: &Option<Box<CompiledLogic>>,
122        suffix_expr: &Option<Box<CompiledLogic>>,
123        thousands_sep_expr: &Option<Box<CompiledLogic>>,
124        user_data: &Value,
125        internal_context: &Value,
126        depth: usize,
127    ) -> Result<Value, String> {
128        let value_val = self.evaluate_with_context(value_expr, user_data, internal_context, depth + 1)?;
129        let num = helpers::to_f64(&value_val);
130        
131        // Get decimals (default 0)
132        let decimals = if let Some(dec_expr) = decimals_expr {
133            let dec_val = self.evaluate_with_context(dec_expr, user_data, internal_context, depth + 1)?;
134            helpers::to_number(&dec_val) as usize
135        } else {
136            0
137        };
138        
139        // Get prefix (default empty)
140        let prefix = if let Some(pre_expr) = prefix_expr {
141            let pre_val = self.evaluate_with_context(pre_expr, user_data, internal_context, depth + 1)?;
142            helpers::to_string(&pre_val)
143        } else {
144            String::new()
145        };
146        
147        // Get suffix (default empty)
148        let suffix = if let Some(suf_expr) = suffix_expr {
149            let suf_val = self.evaluate_with_context(suf_expr, user_data, internal_context, depth + 1)?;
150            helpers::to_string(&suf_val)
151        } else {
152            String::new()
153        };
154        
155        // Get thousands separator (default ",")
156        let thousands_sep = if let Some(sep_expr) = thousands_sep_expr {
157            let sep_val = self.evaluate_with_context(sep_expr, user_data, internal_context, depth + 1)?;
158            helpers::to_string(&sep_val)
159        } else {
160            ",".to_string()
161        };
162        
163        // Format the number
164        let formatted = if decimals == 0 {
165            let rounded = num.round() as i64;
166            format_with_thousands(rounded.to_string(), &thousands_sep)
167        } else {
168            let formatted_num = format!("{:.prec$}", num, prec = decimals);
169            // Split at decimal point
170            if let Some(dot_idx) = formatted_num.find('.') {
171                let integer_part = &formatted_num[..dot_idx];
172                let decimal_part = &formatted_num[dot_idx..];
173                format_with_thousands(integer_part.to_string(), &thousands_sep) + decimal_part
174            } else {
175                format_with_thousands(formatted_num, &thousands_sep)
176            }
177        };
178        
179        Ok(Value::String(format!("{}{}{}", prefix, formatted, suffix)))
180    }
181
182    /// Evaluate split value operation - ZERO-COPY
183    pub(super) fn eval_split_value(&self, string_expr: &CompiledLogic, sep_expr: &CompiledLogic, user_data: &Value, internal_context: &Value, depth: usize) -> Result<Value, String> {
184        let string_val = self.evaluate_with_context(string_expr, user_data, internal_context, depth + 1)?;
185        let sep_val = self.evaluate_with_context(sep_expr, user_data, internal_context, depth + 1)?;
186
187        let text = helpers::to_string(&string_val);
188        let separator = helpers::to_string(&sep_val);
189        let parts: Vec<Value> = text.split(&separator)
190            .map(|s| Value::String(s.to_string()))
191            .collect();
192        Ok(Value::Array(parts))
193    }
194
195    /// Evaluate length operation - ZERO-COPY
196    pub(super) fn eval_length(&self, expr: &CompiledLogic, user_data: &Value, internal_context: &Value, depth: usize) -> Result<Value, String> {
197        let val = self.evaluate_with_context(expr, user_data, internal_context, depth + 1)?;
198        let len = match &val {
199            Value::String(s) => s.len(),
200            Value::Array(arr) => arr.len(),
201            Value::Object(obj) => obj.len(),
202            _ => 0,
203        };
204        Ok(self.f64_to_json(len as f64))
205    }
206
207    /// Evaluate len operation (string length) - ZERO-COPY
208    pub(super) fn eval_len(&self, expr: &CompiledLogic, user_data: &Value, internal_context: &Value, depth: usize) -> Result<Value, String> {
209        let val = self.evaluate_with_context(expr, user_data, internal_context, depth + 1)?;
210        let s = helpers::to_string(&val);
211        Ok(self.f64_to_json(s.len() as f64))
212    }
213}
214
215/// Helper function to format a number string with thousands separator
216fn format_with_thousands(num_str: String, separator: &str) -> String {
217    if separator.is_empty() {
218        return num_str;
219    }
220    
221    let chars: Vec<char> = num_str.chars().collect();
222    let mut result = String::new();
223    let len = chars.len();
224    
225    for (i, ch) in chars.iter().enumerate() {
226        if *ch == '-' || *ch == '+' {
227            result.push(*ch);
228            continue;
229        }
230        
231        result.push(*ch);
232        
233        // Add separator every 3 digits from the right
234        let remaining = len - i - 1;
235        if remaining > 0 && remaining % 3 == 0 {
236            result.push_str(separator);
237        }
238    }
239    
240    result
241}