1#![allow(clippy::explicit_counter_loop)]
5#![allow(clippy::approx_constant)]
6
7use crate::value::JValue;
8use indexmap::IndexMap;
9use thiserror::Error;
10
11#[derive(Error, Debug)]
13pub enum FunctionError {
14 #[error("Argument error: {0}")]
15 ArgumentError(String),
16
17 #[error("Type error: {0}")]
18 TypeError(String),
19
20 #[error("Runtime error: {0}")]
21 RuntimeError(String),
22
23 #[cfg(feature = "python")]
30 #[error("Type error: {0}")]
31 PyConversionError(String),
32}
33
34fn js_slice<T: Clone>(arr: &[T], start: i64, end: Option<i64>) -> Vec<T> {
39 let len = arr.len() as i64;
40 let s = if start < 0 {
41 (len + start).max(0) as usize
42 } else {
43 start.min(len) as usize
44 };
45 let e = match end {
46 Some(end) => {
47 if end < 0 {
48 (len + end).max(0) as usize
49 } else {
50 (end.min(len)) as usize
51 }
52 }
53 None => arr.len(),
54 };
55 if s >= e {
56 return Vec::new();
57 }
58 arr[s..e].to_vec()
59}
60
61pub mod string {
62 use super::*;
63 use regex::Regex;
64
65 pub fn extract_regex(value: &JValue) -> Option<(String, String)> {
67 match value {
68 JValue::Regex { pattern, flags } => Some((pattern.to_string(), flags.to_string())),
69 _ => None,
70 }
71 }
72
73 pub fn build_regex(pattern: &str, flags: &str) -> Result<Regex, FunctionError> {
75 let mut regex_pattern = String::new();
77
78 if !flags.is_empty() {
80 regex_pattern.push_str("(?");
81 if flags.contains('i') {
82 regex_pattern.push('i'); }
84 if flags.contains('m') {
85 regex_pattern.push('m'); }
87 if flags.contains('s') {
88 regex_pattern.push('s'); }
90 regex_pattern.push(')');
91 }
92
93 regex_pattern.push_str(pattern);
94
95 Regex::new(®ex_pattern)
96 .map_err(|e| FunctionError::ArgumentError(format!("Invalid regex: {}", e)))
97 }
98
99 pub fn string(value: &JValue, prettify: Option<bool>) -> Result<JValue, FunctionError> {
108 if value.is_undefined() {
110 return Ok(JValue::Undefined);
111 }
112 if value.is_function() {
113 return Ok(JValue::string(""));
114 }
115
116 let result = match value {
117 JValue::String(s) => s.to_string(),
118 JValue::Number(n) => {
119 let f = *n;
120 if !f.is_finite() {
122 return Err(FunctionError::RuntimeError(format!(
123 "D3001: Attempting to invoke string function with non-finite number: {}",
124 f
125 )));
126 }
127
128 if f.fract() == 0.0 && f.abs() < (i64::MAX as f64) {
130 (f as i64).to_string()
131 } else {
132 format_number_with_precision(f)
135 }
136 }
137 JValue::Bool(b) => b.to_string(),
138 JValue::Null => {
139 "null".to_string()
142 }
143 JValue::Array(_) | JValue::Object(_) => {
144 let indent = if prettify.unwrap_or(false) {
147 Some(2)
148 } else {
149 None
150 };
151 stringify_value_custom(value, indent)?
152 }
153 #[cfg(feature = "python")]
154 JValue::LazyPyDict(_) => {
155 let indent = if prettify.unwrap_or(false) {
156 Some(2)
157 } else {
158 None
159 };
160 stringify_value_custom(value, indent)?
161 }
162 _ => String::new(),
163 };
164 Ok(JValue::string(result))
165 }
166
167 fn format_number_with_precision(f: f64) -> String {
175 let formatted = format!("{:.14e}", f);
178
179 if let Ok(parsed) = formatted.parse::<f64>() {
182 if parsed.abs() >= 1e-6 && parsed.abs() < 1e21 {
184 let s = format!("{}", parsed);
186 if s.contains('.') {
188 let parts: Vec<&str> = s.split('.').collect();
189 if parts.len() == 2 {
190 let int_part = parts[0];
191 let frac_part = parts[1];
192 let total_digits = int_part.trim_start_matches('-').len() + frac_part.len();
193
194 if total_digits > 15 {
195 let sig_figs = 15 - int_part.trim_start_matches('-').len();
197 if sig_figs > 0 && sig_figs <= frac_part.len() {
198 let truncated_frac = &frac_part[..sig_figs];
199 let trimmed = truncated_frac.trim_end_matches('0');
201 if trimmed.is_empty() {
202 return int_part.to_string();
203 } else {
204 return format!("{}.{}", int_part, trimmed);
205 }
206 }
207 }
208 }
209 }
210 s
211 } else {
212 let exp_str = format!("{:e}", parsed);
215 if exp_str.contains('e') && !exp_str.contains("e-") && !exp_str.contains("e+") {
217 exp_str.replace('e', "e+")
218 } else {
219 exp_str
220 }
221 }
222 } else {
223 format!("{}", f)
225 }
226 }
227
228 fn stringify_value_custom(
235 value: &JValue,
236 indent: Option<usize>,
237 ) -> Result<String, FunctionError> {
238 let transformed = transform_for_stringify(value);
240
241 let result = if indent.is_some() {
242 serde_json::to_string_pretty(&transformed)
243 .map_err(|e| FunctionError::RuntimeError(format!("JSON stringify error: {}", e)))?
244 } else {
245 serde_json::to_string(&transformed)
246 .map_err(|e| FunctionError::RuntimeError(format!("JSON stringify error: {}", e)))?
247 };
248 Ok(result)
249 }
250
251 fn transform_for_stringify(value: &JValue) -> JValue {
253 match value {
254 JValue::Number(n) => {
255 let f = *n;
256 if f.fract() == 0.0 && f.is_finite() && f.abs() < (1i64 << 53) as f64 {
258 value.clone()
260 } else {
261 let formatted = format_number_with_precision(f);
263 if let Ok(parsed) = formatted.parse::<f64>() {
264 JValue::Number(parsed)
265 } else {
266 value.clone()
267 }
268 }
269 }
270 JValue::Array(arr) => {
271 let transformed: Vec<JValue> = arr
272 .iter()
273 .map(|v| {
274 if v.is_function() {
275 return JValue::string("");
276 }
277 transform_for_stringify(v)
278 })
279 .collect();
280 JValue::array(transformed)
281 }
282 JValue::Object(obj) => {
283 if value.is_function() {
284 return JValue::string("");
285 }
286
287 let transformed: IndexMap<String, JValue> = obj
288 .iter()
289 .map(|(k, v)| {
290 if v.is_function() {
291 return (k.clone(), JValue::string(""));
292 }
293 (k.clone(), transform_for_stringify(v))
294 })
295 .collect();
296 JValue::object(transformed)
297 }
298 #[cfg(feature = "python")]
299 JValue::LazyPyDict(lazy) => match lazy.to_object_ref() {
300 Some(obj) => {
301 let transformed: IndexMap<String, JValue> = obj
302 .iter()
303 .map(|(k, v)| {
304 if v.is_function() {
305 return (k.clone(), JValue::string(""));
306 }
307 (k.clone(), transform_for_stringify(v))
308 })
309 .collect();
310 JValue::object(transformed)
311 }
312 None => JValue::Null,
313 },
314 _ => value.clone(),
315 }
316 }
317
318 pub fn length(s: &str) -> Result<JValue, FunctionError> {
321 Ok(JValue::Number(s.chars().count() as f64))
322 }
323
324 pub fn uppercase(s: &str) -> Result<JValue, FunctionError> {
326 Ok(JValue::string(s.to_uppercase()))
327 }
328
329 pub fn lowercase(s: &str) -> Result<JValue, FunctionError> {
331 Ok(JValue::string(s.to_lowercase()))
332 }
333
334 pub fn substring(s: &str, start: i64, length: Option<i64>) -> Result<JValue, FunctionError> {
340 let chars: Vec<char> = s.chars().collect();
341 let str_len = chars.len() as i64;
342
343 let start = if str_len + start < 0 { 0 } else { start };
346
347 if let Some(len) = length {
348 if len <= 0 {
350 return Ok(JValue::string(""));
351 }
352 let end = if start >= 0 {
355 start + len
356 } else {
357 str_len + start + len
358 };
359 let slice = js_slice(&chars, start, Some(end));
362 Ok(JValue::string(slice.iter().collect::<String>()))
363 } else {
364 let slice = js_slice(&chars, start, None);
367 Ok(JValue::string(slice.iter().collect::<String>()))
368 }
369 }
370
371 pub fn substring_before(s: &str, separator: &str) -> Result<JValue, FunctionError> {
373 if separator.is_empty() {
374 return Ok(JValue::string(""));
375 }
376
377 let result = s.split(separator).next().unwrap_or(s).to_string();
378 Ok(JValue::string(result))
379 }
380
381 pub fn substring_after(s: &str, separator: &str) -> Result<JValue, FunctionError> {
383 if separator.is_empty() {
384 return Ok(JValue::string(s));
385 }
386
387 if let Some(pos) = s.find(separator) {
388 let result = s[pos + separator.len()..].to_string();
389 Ok(JValue::string(result))
390 } else {
391 Ok(JValue::string(s))
393 }
394 }
395
396 pub fn trim(s: &str) -> Result<JValue, FunctionError> {
401 use regex::Regex;
402 use std::sync::OnceLock;
403
404 static WS_REGEX: OnceLock<Regex> = OnceLock::new();
405 let ws_regex = WS_REGEX.get_or_init(|| Regex::new(r"[ \t\n\r]+").unwrap());
406
407 let normalized = ws_regex.replace_all(s, " ");
408 Ok(JValue::string(normalized.trim()))
409 }
410
411 pub fn contains(s: &str, pattern: &JValue) -> Result<JValue, FunctionError> {
413 if let Some((pat, flags)) = extract_regex(pattern) {
415 let re = build_regex(&pat, &flags)?;
416 return Ok(JValue::Bool(re.is_match(s)));
417 }
418
419 let pat = match pattern {
421 JValue::String(s) => &**s,
422 _ => {
423 return Err(FunctionError::TypeError(
424 "contains() requires string arguments".to_string(),
425 ))
426 }
427 };
428
429 Ok(JValue::Bool(s.contains(pat)))
430 }
431
432 pub fn split(
435 s: &str,
436 separator: &JValue,
437 limit: Option<usize>,
438 ) -> Result<JValue, FunctionError> {
439 if let Some((pattern, flags)) = extract_regex(separator) {
441 let re = build_regex(&pattern, &flags)?;
442
443 let parts: Vec<JValue> = re.split(s).map(JValue::string).collect();
444
445 let result = if let Some(lim) = limit {
447 parts.into_iter().take(lim).collect()
448 } else {
449 parts
450 };
451
452 return Ok(JValue::array(result));
453 }
454
455 let sep = match separator {
457 JValue::String(s) => &**s,
458 _ => {
459 return Err(FunctionError::TypeError(
460 "split() requires string arguments".to_string(),
461 ))
462 }
463 };
464
465 if sep.is_empty() {
466 let chars: Vec<JValue> = s.chars().map(|c| JValue::string(c.to_string())).collect();
468 let result = if let Some(lim) = limit {
470 chars.into_iter().take(lim).collect()
471 } else {
472 chars
473 };
474 return Ok(JValue::array(result));
475 }
476
477 let parts: Vec<JValue> = s.split(sep).map(JValue::string).collect();
478
479 let result = if let Some(lim) = limit {
481 parts.into_iter().take(lim).collect()
482 } else {
483 parts
484 };
485
486 Ok(JValue::array(result))
487 }
488
489 pub fn join(arr: &[JValue], separator: Option<&str>) -> Result<JValue, FunctionError> {
491 let sep = separator.unwrap_or("");
492 let parts: Result<Vec<String>, FunctionError> = arr
493 .iter()
494 .map(|v| match v {
495 JValue::String(s) => Ok(s.to_string()),
496 JValue::Number(n) => Ok(format_join_number(*n)),
497 JValue::Bool(b) => Ok(b.to_string()),
498 JValue::Null => Ok(String::new()),
499 _ => Err(FunctionError::TypeError(
500 "Cannot join array containing objects or nested arrays".to_string(),
501 )),
502 })
503 .collect();
504
505 let parts = parts?;
506 Ok(JValue::string(parts.join(sep)))
507 }
508
509 fn format_join_number(n: f64) -> String {
511 if n.fract() == 0.0 && n.abs() < (i64::MAX as f64) {
512 (n as i64).to_string()
513 } else {
514 n.to_string()
515 }
516 }
517
518 fn substitute_capture_groups(
521 replacement: &str,
522 full_match: &str,
523 groups: &[Option<regex::Match>],
524 ) -> String {
525 let mut result = String::new();
526 let mut position = 0;
527 let chars: Vec<char> = replacement.chars().collect();
528
529 while position < chars.len() {
530 if chars[position] == '$' {
531 position += 1;
532
533 if position >= chars.len() {
534 result.push('$');
536 break;
537 }
538
539 let next_ch = chars[position];
540
541 if next_ch == '$' {
542 result.push('$');
544 position += 1;
545 } else if next_ch == '0' {
546 result.push_str(full_match);
548 position += 1;
549 } else if next_ch.is_ascii_digit() {
550 let max_digits = if groups.is_empty() {
553 1
554 } else {
555 ((groups.len() as f64).log10().floor() as usize) + 1
557 };
558
559 let mut digits_end = position;
561 let mut digit_count = 0;
562 while digits_end < chars.len()
563 && chars[digits_end].is_ascii_digit()
564 && digit_count < max_digits
565 {
566 digits_end += 1;
567 digit_count += 1;
568 }
569
570 if digit_count > 0 {
571 let num_str: String = chars[position..digits_end].iter().collect();
573 let mut group_num = num_str.parse::<usize>().unwrap();
574
575 let mut used_digits = digit_count;
578 if max_digits > 1 && group_num > groups.len() && digit_count > 1 {
579 let fallback_str: String =
580 chars[position..digits_end - 1].iter().collect();
581 if let Ok(fallback_num) = fallback_str.parse::<usize>() {
582 group_num = fallback_num;
583 used_digits = digit_count - 1;
584 }
585 }
586
587 if groups.is_empty() {
589 position += used_digits;
592 } else if group_num > 0 && group_num <= groups.len() {
593 if let Some(m) = &groups[group_num - 1] {
595 result.push_str(m.as_str());
596 }
597 position += used_digits;
599 } else {
600 position += used_digits;
603 }
604 } else {
605 result.push('$');
607 }
608 } else {
609 result.push('$');
611 }
613 } else {
614 result.push(chars[position]);
615 position += 1;
616 }
617 }
618
619 result
620 }
621
622 pub fn replace(
624 s: &str,
625 pattern: &JValue,
626 replacement: &str,
627 limit: Option<usize>,
628 ) -> Result<JValue, FunctionError> {
629 if let Some((pat, flags)) = extract_regex(pattern) {
631 let re = build_regex(&pat, &flags)?;
632
633 let mut count = 0;
634 let mut last_match = 0;
635 let mut output = String::new();
636
637 for cap in re.captures_iter(s) {
638 if limit.is_some_and(|lim| count >= lim) {
639 break;
640 }
641
642 let m = cap.get(0).unwrap();
643
644 if m.as_str().is_empty() {
646 return Err(FunctionError::RuntimeError(
647 "D1004: Regular expression matches zero length string".to_string(),
648 ));
649 }
650
651 output.push_str(&s[last_match..m.start()]);
652
653 let groups: Vec<Option<regex::Match>> =
655 (1..cap.len()).map(|i| cap.get(i)).collect();
656
657 let substituted = substitute_capture_groups(replacement, m.as_str(), &groups);
659 output.push_str(&substituted);
660
661 last_match = m.end();
662 count += 1;
663 }
664
665 output.push_str(&s[last_match..]);
666 return Ok(JValue::string(output));
667 }
668
669 let pat = match pattern {
671 JValue::String(s) => &**s,
672 _ => {
673 return Err(FunctionError::TypeError(
674 "replace() requires string arguments".to_string(),
675 ))
676 }
677 };
678
679 if pat.is_empty() {
680 return Err(FunctionError::RuntimeError(
681 "D3010: Pattern cannot be empty".to_string(),
682 ));
683 }
684
685 let result = if let Some(lim) = limit {
686 let mut remaining = s;
687 let mut output = String::new();
688 let mut count = 0;
689
690 while count < lim {
691 if let Some(pos) = remaining.find(pat) {
692 output.push_str(&remaining[..pos]);
693 output.push_str(replacement);
694 remaining = &remaining[pos + pat.len()..];
695 count += 1;
696 } else {
697 output.push_str(remaining);
698 break;
699 }
700 }
701 if count == lim {
702 output.push_str(remaining);
703 }
704 output
705 } else {
706 s.replace(pat, replacement)
707 };
708
709 Ok(JValue::string(result))
710 }
711}
712
713pub mod boolean {
715 use super::*;
716
717 pub fn boolean(value: &JValue) -> Result<JValue, FunctionError> {
728 Ok(JValue::Bool(to_boolean(value)))
729 }
730
731 fn to_boolean(value: &JValue) -> bool {
733 match value {
734 JValue::Null | JValue::Undefined => false,
735 JValue::Bool(b) => *b,
736 JValue::Number(n) => *n != 0.0,
737 JValue::String(s) => !s.is_empty(),
738 JValue::Array(arr) => {
739 if arr.len() == 1 {
740 to_boolean(&arr[0])
741 } else {
742 arr.iter().any(to_boolean)
744 }
745 }
746 JValue::Object(obj) => !obj.is_empty(),
747 JValue::Lambda { .. } | JValue::Builtin { .. } => false,
748 JValue::Regex { .. } => true,
749 #[cfg(feature = "python")]
750 JValue::LazyPyDict(lazy) => !lazy.is_empty(),
751 }
752 }
753}
754
755pub mod numeric {
757 use super::*;
758
759 pub fn number(value: &JValue) -> Result<JValue, FunctionError> {
762 match value {
763 JValue::Number(n) => {
764 let f = *n;
765 if !f.is_finite() {
766 return Err(FunctionError::RuntimeError(
767 "D3030: Cannot convert infinite number".to_string(),
768 ));
769 }
770 Ok(JValue::Number(f))
771 }
772 JValue::String(s) => {
773 let trimmed = s.trim();
774
775 if let Some(stripped) = trimmed
777 .strip_prefix("0x")
778 .or_else(|| trimmed.strip_prefix("0X"))
779 {
780 return i64::from_str_radix(stripped, 16)
782 .map(|n| JValue::Number(n as f64))
783 .map_err(|_| {
784 FunctionError::RuntimeError(format!(
785 "D3030: Cannot convert '{}' to number",
786 s
787 ))
788 });
789 } else if let Some(stripped) = trimmed
790 .strip_prefix("0o")
791 .or_else(|| trimmed.strip_prefix("0O"))
792 {
793 return i64::from_str_radix(stripped, 8)
795 .map(|n| JValue::Number(n as f64))
796 .map_err(|_| {
797 FunctionError::RuntimeError(format!(
798 "D3030: Cannot convert '{}' to number",
799 s
800 ))
801 });
802 } else if let Some(stripped) = trimmed
803 .strip_prefix("0b")
804 .or_else(|| trimmed.strip_prefix("0B"))
805 {
806 return i64::from_str_radix(stripped, 2)
808 .map(|n| JValue::Number(n as f64))
809 .map_err(|_| {
810 FunctionError::RuntimeError(format!(
811 "D3030: Cannot convert '{}' to number",
812 s
813 ))
814 });
815 }
816
817 match trimmed.parse::<f64>() {
819 Ok(n) => {
820 if !n.is_finite() {
822 return Err(FunctionError::RuntimeError(format!(
823 "D3030: Cannot convert '{}' to number",
824 s
825 )));
826 }
827 Ok(JValue::Number(n))
828 }
829 Err(_) => Err(FunctionError::RuntimeError(format!(
830 "D3030: Cannot convert '{}' to number",
831 s
832 ))),
833 }
834 }
835 JValue::Bool(true) => Ok(JValue::Number(1.0)),
836 JValue::Bool(false) => Ok(JValue::Number(0.0)),
837 JValue::Null => Err(FunctionError::RuntimeError(
838 "D3030: Cannot convert null to number".to_string(),
839 )),
840 _ => Err(FunctionError::RuntimeError(
841 "D3030: Cannot convert array or object to number".to_string(),
842 )),
843 }
844 }
845
846 pub fn sum(arr: &[JValue]) -> Result<JValue, FunctionError> {
848 if arr.is_empty() {
849 return Ok(JValue::Number(0.0));
850 }
851
852 let mut total = 0.0;
853 for value in arr {
854 match value {
855 JValue::Number(n) => {
856 total += n;
857 }
858 _ => {
859 return Err(FunctionError::TypeError(format!(
860 "sum() requires all array elements to be numbers, got: {:?}",
861 value
862 )))
863 }
864 }
865 }
866 Ok(JValue::Number(total))
867 }
868
869 pub fn max(arr: &[JValue]) -> Result<JValue, FunctionError> {
871 if arr.is_empty() {
872 return Ok(JValue::Null);
873 }
874
875 let mut max_val = f64::NEG_INFINITY;
876 for value in arr {
877 match value {
878 JValue::Number(n) => {
879 if *n > max_val {
880 max_val = *n;
881 }
882 }
883 _ => {
884 return Err(FunctionError::TypeError(
885 "max() requires all array elements to be numbers".to_string(),
886 ))
887 }
888 }
889 }
890 Ok(JValue::Number(max_val))
891 }
892
893 pub fn min(arr: &[JValue]) -> Result<JValue, FunctionError> {
895 if arr.is_empty() {
896 return Ok(JValue::Null);
897 }
898
899 let mut min_val = f64::INFINITY;
900 for value in arr {
901 match value {
902 JValue::Number(n) => {
903 if *n < min_val {
904 min_val = *n;
905 }
906 }
907 _ => {
908 return Err(FunctionError::TypeError(
909 "min() requires all array elements to be numbers".to_string(),
910 ))
911 }
912 }
913 }
914 Ok(JValue::Number(min_val))
915 }
916
917 pub fn average(arr: &[JValue]) -> Result<JValue, FunctionError> {
919 if arr.is_empty() {
920 return Ok(JValue::Null);
921 }
922
923 let sum_result = sum(arr)?;
924 if let JValue::Number(n) = sum_result {
925 let avg = n / arr.len() as f64;
926 Ok(JValue::Number(avg))
927 } else {
928 Err(FunctionError::RuntimeError("Sum failed".to_string()))
929 }
930 }
931
932 pub fn abs(n: f64) -> Result<JValue, FunctionError> {
934 Ok(JValue::Number(n.abs()))
935 }
936
937 pub fn floor(n: f64) -> Result<JValue, FunctionError> {
939 Ok(JValue::Number(n.floor()))
940 }
941
942 pub fn ceil(n: f64) -> Result<JValue, FunctionError> {
944 Ok(JValue::Number(n.ceil()))
945 }
946
947 pub fn round(n: f64, precision: Option<i32>) -> Result<JValue, FunctionError> {
957 let prec = precision.unwrap_or(0);
958
959 let multiplier = 10_f64.powi(prec);
961 let scaled = n * multiplier;
962
963 let floor_val = scaled.floor();
965 let frac = scaled - floor_val;
966
967 let epsilon = 1e-10;
969 let result = if (frac - 0.5).abs() < epsilon {
970 let floor_int = floor_val as i64;
972 if floor_int % 2 == 0 {
973 floor_val } else {
975 floor_val + 1.0 }
977 } else if frac > 0.5 {
978 floor_val + 1.0 } else {
980 floor_val };
982
983 let final_result = result / multiplier;
985
986 Ok(JValue::Number(final_result))
987 }
988
989 pub fn sqrt(n: f64) -> Result<JValue, FunctionError> {
991 if n < 0.0 {
992 return Err(FunctionError::ArgumentError(
993 "Cannot take square root of negative number".to_string(),
994 ));
995 }
996 Ok(JValue::Number(n.sqrt()))
997 }
998
999 pub fn power(base: f64, exponent: f64) -> Result<JValue, FunctionError> {
1001 let result = base.powf(exponent);
1002 if result.is_nan() || result.is_infinite() {
1003 return Err(FunctionError::RuntimeError(
1004 "Power operation resulted in invalid number".to_string(),
1005 ));
1006 }
1007 Ok(JValue::Number(result))
1008 }
1009
1010 pub fn format_number(
1013 value: f64,
1014 picture: &str,
1015 options: Option<&JValue>,
1016 ) -> Result<JValue, FunctionError> {
1017 let mut decimal_separator = '.';
1019 let mut grouping_separator = ',';
1020 let mut zero_digit = '0';
1021 let mut percent_symbol = "%".to_string();
1022 let mut per_mille_symbol = "\u{2030}".to_string();
1023 let digit_char = '#';
1024 let pattern_separator = ';';
1025
1026 if let Some(JValue::Object(opts)) = options {
1028 if let Some(JValue::String(s)) = opts.get("decimal-separator") {
1029 decimal_separator = s.chars().next().unwrap_or('.');
1030 }
1031 if let Some(JValue::String(s)) = opts.get("grouping-separator") {
1032 grouping_separator = s.chars().next().unwrap_or(',');
1033 }
1034 if let Some(JValue::String(s)) = opts.get("zero-digit") {
1035 zero_digit = s.chars().next().unwrap_or('0');
1036 }
1037 if let Some(JValue::String(s)) = opts.get("percent") {
1038 percent_symbol = s.to_string();
1039 }
1040 if let Some(JValue::String(s)) = opts.get("per-mille") {
1041 per_mille_symbol = s.to_string();
1042 }
1043 }
1044
1045 let sub_pictures: Vec<&str> = picture.split(pattern_separator).collect();
1047 if sub_pictures.len() > 2 {
1048 return Err(FunctionError::ArgumentError(
1049 "D3080: Too many pattern separators in picture string".to_string(),
1050 ));
1051 }
1052
1053 let parts = parse_picture(
1055 sub_pictures[0],
1056 decimal_separator,
1057 grouping_separator,
1058 zero_digit,
1059 digit_char,
1060 &percent_symbol,
1061 &per_mille_symbol,
1062 )?;
1063
1064 let is_negative = value < 0.0;
1066 let mut abs_value = value.abs();
1067
1068 if parts.has_percent {
1070 abs_value *= 100.0;
1071 } else if parts.has_per_mille {
1072 abs_value *= 1000.0;
1073 }
1074
1075 let formatted = apply_number_picture(
1077 abs_value,
1078 &parts,
1079 decimal_separator,
1080 grouping_separator,
1081 zero_digit,
1082 )?;
1083
1084 let result = if is_negative {
1086 if sub_pictures.len() == 2 {
1087 let neg_parts = parse_picture(
1089 sub_pictures[1],
1090 decimal_separator,
1091 grouping_separator,
1092 zero_digit,
1093 digit_char,
1094 &percent_symbol,
1095 &per_mille_symbol,
1096 )?;
1097 let neg_formatted = apply_number_picture(
1098 abs_value,
1099 &neg_parts,
1100 decimal_separator,
1101 grouping_separator,
1102 zero_digit,
1103 )?;
1104 format!("{}{}{}", neg_parts.prefix, neg_formatted, neg_parts.suffix)
1105 } else {
1106 format!("-{}{}{}", parts.prefix, formatted, parts.suffix)
1108 }
1109 } else {
1110 format!("{}{}{}", parts.prefix, formatted, parts.suffix)
1111 };
1112
1113 Ok(JValue::string(result))
1114 }
1115
1116 fn is_digit_in_family(c: char, zero_digit: char) -> bool {
1118 if c.is_ascii_digit() {
1119 return true;
1120 }
1121 let zero_code = zero_digit as u32;
1123 let c_code = c as u32;
1124 c_code >= zero_code && c_code < zero_code + 10
1125 }
1126
1127 fn parse_picture(
1129 picture: &str,
1130 decimal_sep: char,
1131 grouping_sep: char,
1132 zero_digit: char,
1133 digit_char: char,
1134 percent_symbol: &str,
1135 per_mille_symbol: &str,
1136 ) -> Result<PictureParts, FunctionError> {
1137 let chars: Vec<char> = picture.chars().collect();
1139
1140 let prefix_end = chars
1144 .iter()
1145 .position(|&c| {
1146 c == decimal_sep
1147 || c == grouping_sep
1148 || c == digit_char
1149 || is_digit_in_family(c, zero_digit)
1150 })
1151 .unwrap_or(chars.len());
1152 let prefix: String = chars[..prefix_end].iter().collect();
1153
1154 let suffix_start = chars
1156 .iter()
1157 .rposition(|&c| {
1158 c == decimal_sep
1159 || c == grouping_sep
1160 || c == digit_char
1161 || is_digit_in_family(c, zero_digit)
1162 })
1163 .map(|pos| pos + 1)
1164 .unwrap_or(chars.len());
1165 let suffix: String = chars[suffix_start..].iter().collect();
1166
1167 let active: String = chars[prefix_end..suffix_start].iter().collect();
1169
1170 let exponent_pos = active.find('e').or_else(|| active.find('E'));
1172 let (mantissa_part, exponent_part): (String, String) = if let Some(pos) = exponent_pos {
1173 (active[..pos].to_string(), active[pos + 1..].to_string())
1174 } else {
1175 (active.clone(), String::new())
1176 };
1177
1178 let mantissa_chars: Vec<char> = mantissa_part.chars().collect();
1180 let decimal_pos = mantissa_chars.iter().position(|&c| c == decimal_sep);
1181 let (integer_part, fractional_part): (String, String) = if let Some(pos) = decimal_pos {
1182 (
1183 mantissa_chars[..pos].iter().collect(),
1184 mantissa_chars[pos + 1..].iter().collect(),
1185 )
1186 } else {
1187 (mantissa_part.clone(), String::new())
1188 };
1189
1190 if active.matches(decimal_sep).count() > 1 {
1192 return Err(FunctionError::ArgumentError(
1193 "D3081: Multiple decimal separators in picture".to_string(),
1194 ));
1195 }
1196
1197 if let Some(pos) = decimal_pos {
1199 if pos > 0 && active.chars().nth(pos - 1) == Some(grouping_sep) {
1200 return Err(FunctionError::ArgumentError(
1201 "D3087: Grouping separator adjacent to decimal separator".to_string(),
1202 ));
1203 }
1204 if pos + 1 < active.len() && active.chars().nth(pos + 1) == Some(grouping_sep) {
1205 return Err(FunctionError::ArgumentError(
1206 "D3087: Grouping separator adjacent to decimal separator".to_string(),
1207 ));
1208 }
1209 }
1210
1211 let grouping_str = format!("{}{}", grouping_sep, grouping_sep);
1213 if picture.contains(&grouping_str) {
1214 return Err(FunctionError::ArgumentError(
1215 "D3089: Consecutive grouping separators in picture".to_string(),
1216 ));
1217 }
1218
1219 let has_percent = picture.contains(percent_symbol);
1221 let has_per_mille = picture.contains(per_mille_symbol);
1222
1223 if picture.matches(percent_symbol).count() > 1 {
1225 return Err(FunctionError::ArgumentError(
1226 "D3082: Multiple percent signs in picture".to_string(),
1227 ));
1228 }
1229
1230 if picture.matches(per_mille_symbol).count() > 1 {
1232 return Err(FunctionError::ArgumentError(
1233 "D3083: Multiple per-mille signs in picture".to_string(),
1234 ));
1235 }
1236
1237 if has_percent && has_per_mille {
1239 return Err(FunctionError::ArgumentError(
1240 "D3084: Cannot have both percent and per-mille in picture".to_string(),
1241 ));
1242 }
1243
1244 if !integer_part.is_empty() && integer_part.ends_with(grouping_sep) {
1246 return Err(FunctionError::ArgumentError(
1247 "D3088: Integer part ends with grouping separator".to_string(),
1248 ));
1249 }
1250
1251 let has_digit_in_integer = integer_part
1253 .chars()
1254 .any(|c| is_digit_in_family(c, zero_digit) || c == digit_char);
1255 let has_digit_in_fractional = fractional_part
1256 .chars()
1257 .any(|c| is_digit_in_family(c, zero_digit) || c == digit_char);
1258 if !has_digit_in_integer && !has_digit_in_fractional {
1259 return Err(FunctionError::ArgumentError(
1260 "D3085: Picture must contain at least one digit".to_string(),
1261 ));
1262 }
1263
1264 let min_integer_digits = integer_part
1266 .chars()
1267 .filter(|&c| is_digit_in_family(c, zero_digit))
1268 .count();
1269
1270 let min_fractional_digits = fractional_part
1272 .chars()
1273 .filter(|&c| is_digit_in_family(c, zero_digit))
1274 .count();
1275 let mut max_fractional_digits = fractional_part
1276 .chars()
1277 .filter(|&c| is_digit_in_family(c, zero_digit) || c == digit_char)
1278 .count();
1279
1280 if decimal_pos.is_some() && max_fractional_digits == 0 {
1283 max_fractional_digits = 1;
1284 }
1285
1286 let mut grouping_positions = Vec::new();
1288 let int_chars: Vec<char> = integer_part.chars().collect();
1289 for (i, &c) in int_chars.iter().enumerate() {
1290 if c == grouping_sep {
1291 let digits_to_right = int_chars[i + 1..]
1293 .iter()
1294 .filter(|&&ch| is_digit_in_family(ch, zero_digit) || ch == digit_char)
1295 .count();
1296 grouping_positions.push(digits_to_right);
1297 }
1298 }
1299
1300 let regular_grouping = if grouping_positions.is_empty() {
1302 0
1303 } else if grouping_positions.len() == 1 {
1304 grouping_positions[0]
1305 } else {
1306 let first_interval = grouping_positions[0];
1308 if grouping_positions.iter().all(|&p| {
1309 grouping_positions.iter().filter(|&&x| x == p).count()
1310 == grouping_positions.len() / first_interval
1311 || (p % first_interval == 0 && grouping_positions.contains(&first_interval))
1312 }) {
1313 first_interval
1314 } else {
1315 0 }
1317 };
1318
1319 let mut fractional_grouping_positions = Vec::new();
1321 let frac_chars: Vec<char> = fractional_part.chars().collect();
1322 for (i, &c) in frac_chars.iter().enumerate() {
1323 if c == grouping_sep {
1324 let digits_to_left = frac_chars[..i]
1326 .iter()
1327 .filter(|&&ch| is_digit_in_family(ch, zero_digit) || ch == digit_char)
1328 .count();
1329 fractional_grouping_positions.push(digits_to_left);
1330 }
1331 }
1332
1333 let min_exponent_digits = if !exponent_part.is_empty() {
1335 exponent_part
1336 .chars()
1337 .filter(|&c| is_digit_in_family(c, zero_digit))
1338 .count()
1339 } else {
1340 0
1341 };
1342
1343 if !exponent_part.is_empty()
1345 && exponent_part
1346 .chars()
1347 .any(|c| !is_digit_in_family(c, zero_digit))
1348 {
1349 return Err(FunctionError::ArgumentError(
1350 "D3093: Exponent must contain only digit characters".to_string(),
1351 ));
1352 }
1353
1354 if exponent_pos.is_some() && min_exponent_digits == 0 {
1356 return Err(FunctionError::ArgumentError(
1357 "D3093: Exponent cannot be empty".to_string(),
1358 ));
1359 }
1360
1361 if min_exponent_digits > 0 && (has_percent || has_per_mille) {
1363 return Err(FunctionError::ArgumentError(
1364 "D3092: Percent/per-mille not allowed with exponential notation".to_string(),
1365 ));
1366 }
1367
1368 let mut seen_zero_in_integer = false;
1371 for c in integer_part.chars() {
1372 if is_digit_in_family(c, zero_digit) {
1373 seen_zero_in_integer = true;
1374 } else if c == digit_char && seen_zero_in_integer {
1375 return Err(FunctionError::ArgumentError(
1376 "D3090: Optional digit (#) cannot appear after mandatory digit (0) in integer part".to_string()
1377 ));
1378 }
1379 }
1380
1381 let mut seen_hash_in_fractional = false;
1384 for c in fractional_part.chars() {
1385 if c == digit_char {
1386 seen_hash_in_fractional = true;
1387 } else if is_digit_in_family(c, zero_digit) && seen_hash_in_fractional {
1388 return Err(FunctionError::ArgumentError(
1389 "D3091: Mandatory digit (0) cannot appear after optional digit (#) in fractional part".to_string()
1390 ));
1391 }
1392 }
1393
1394 let valid_chars: Vec<char> =
1397 vec![decimal_sep, grouping_sep, zero_digit, digit_char, 'e', 'E'];
1398 for c in mantissa_part.chars() {
1399 if !is_digit_in_family(c, zero_digit) && !valid_chars.contains(&c) {
1400 return Err(FunctionError::ArgumentError(format!(
1401 "D3086: Invalid character in picture: '{}'",
1402 c
1403 )));
1404 }
1405 }
1406
1407 let scaling_factor = min_integer_digits;
1409
1410 Ok(PictureParts {
1411 prefix,
1412 suffix,
1413 min_integer_digits,
1414 min_fractional_digits,
1415 max_fractional_digits,
1416 grouping_positions,
1417 fractional_grouping_positions,
1418 regular_grouping,
1419 has_decimal: decimal_pos.is_some(),
1420 has_integer_part: !integer_part.is_empty(),
1421 has_percent,
1422 has_per_mille,
1423 min_exponent_digits,
1424 scaling_factor,
1425 })
1426 }
1427
1428 fn apply_number_picture(
1430 value: f64,
1431 parts: &PictureParts,
1432 decimal_sep: char,
1433 grouping_sep: char,
1434 zero_digit: char,
1435 ) -> Result<String, FunctionError> {
1436 let (mantissa, exponent) = if parts.min_exponent_digits > 0 {
1438 let max_mantissa = 10_f64.powi(parts.scaling_factor as i32);
1440 let min_mantissa = 10_f64.powi(parts.scaling_factor as i32 - 1);
1441
1442 let mut m = value;
1443 let mut e = 0_i32;
1444
1445 while m < min_mantissa && m != 0.0 {
1447 m *= 10.0;
1448 e -= 1;
1449 }
1450 while m >= max_mantissa {
1451 m /= 10.0;
1452 e += 1;
1453 }
1454
1455 (m, Some(e))
1456 } else {
1457 (value, None)
1458 };
1459
1460 let factor = 10_f64.powi(parts.max_fractional_digits as i32);
1462 let rounded = (mantissa * factor).round() / factor;
1463
1464 let mut num_str = format!("{:.prec$}", rounded, prec = parts.max_fractional_digits);
1466
1467 if decimal_sep != '.' {
1469 num_str = num_str.replace('.', &decimal_sep.to_string());
1470 }
1471
1472 let decimal_pos = num_str.find(decimal_sep).unwrap_or(num_str.len());
1474 let mut integer_str = num_str[..decimal_pos].to_string();
1475 let mut fractional_str = if decimal_pos < num_str.len() {
1476 num_str[decimal_pos + 1..].to_string()
1477 } else {
1478 String::new()
1479 };
1480
1481 while integer_str.len() > 1 && integer_str.starts_with(zero_digit) {
1483 integer_str.remove(0);
1484 }
1485 if integer_str == zero_digit.to_string() && !parts.has_integer_part {
1487 integer_str.clear();
1488 }
1489 if integer_str.is_empty() && parts.has_integer_part {
1491 integer_str.push(zero_digit);
1492 }
1493
1494 while !fractional_str.is_empty() && fractional_str.ends_with(zero_digit) {
1496 fractional_str.pop();
1497 }
1498
1499 while integer_str.len() < parts.min_integer_digits {
1501 integer_str.insert(0, zero_digit);
1502 }
1503
1504 while fractional_str.len() < parts.min_fractional_digits {
1506 fractional_str.push(zero_digit);
1507 }
1508
1509 while fractional_str.len() > parts.min_fractional_digits {
1511 if fractional_str.ends_with(zero_digit) {
1512 fractional_str.pop();
1513 } else {
1514 break;
1515 }
1516 }
1517
1518 if parts.regular_grouping > 0 {
1520 let mut grouped = String::new();
1522 let chars: Vec<char> = integer_str.chars().collect();
1523 for (i, &c) in chars.iter().enumerate() {
1524 grouped.push(c);
1525 let pos_from_right = chars.len() - i - 1;
1526 if pos_from_right > 0 && pos_from_right % parts.regular_grouping == 0 {
1527 grouped.push(grouping_sep);
1528 }
1529 }
1530 integer_str = grouped;
1531 } else if !parts.grouping_positions.is_empty() {
1532 let mut grouped = String::new();
1534 let chars: Vec<char> = integer_str.chars().collect();
1535 for (i, &c) in chars.iter().enumerate() {
1536 grouped.push(c);
1537 let pos_from_right = chars.len() - i - 1;
1538 if parts.grouping_positions.contains(&pos_from_right) {
1539 grouped.push(grouping_sep);
1540 }
1541 }
1542 integer_str = grouped;
1543 }
1544
1545 if !parts.fractional_grouping_positions.is_empty() {
1547 let mut grouped = String::new();
1548 let chars: Vec<char> = fractional_str.chars().collect();
1549 for (i, &c) in chars.iter().enumerate() {
1550 grouped.push(c);
1551 let pos_from_left = i + 1;
1553 if parts.fractional_grouping_positions.contains(&pos_from_left) {
1554 grouped.push(grouping_sep);
1555 }
1556 }
1557 fractional_str = grouped;
1558 }
1559
1560 let mut result = if parts.has_decimal || !fractional_str.is_empty() {
1562 format!("{}{}{}", integer_str, decimal_sep, fractional_str)
1563 } else {
1564 integer_str
1565 };
1566
1567 if zero_digit != '0' {
1569 let zero_code = zero_digit as u32;
1570 result = result
1571 .chars()
1572 .map(|c| {
1573 if c.is_ascii_digit() {
1574 let digit_value = c as u32 - '0' as u32;
1575 char::from_u32(zero_code + digit_value).unwrap_or(c)
1576 } else {
1577 c
1578 }
1579 })
1580 .collect();
1581 }
1582
1583 if let Some(exp) = exponent {
1585 let exp_str = format!("{:0width$}", exp.abs(), width = parts.min_exponent_digits);
1587
1588 let exp_formatted = if zero_digit != '0' {
1590 let zero_code = zero_digit as u32;
1591 exp_str
1592 .chars()
1593 .map(|c| {
1594 if c.is_ascii_digit() {
1595 let digit_value = c as u32 - '0' as u32;
1596 char::from_u32(zero_code + digit_value).unwrap_or(c)
1597 } else {
1598 c
1599 }
1600 })
1601 .collect()
1602 } else {
1603 exp_str
1604 };
1605
1606 result.push('e');
1608 if exp < 0 {
1609 result.push('-');
1610 }
1611 result.push_str(&exp_formatted);
1612 }
1613
1614 Ok(result)
1615 }
1616
1617 #[derive(Debug)]
1619 struct PictureParts {
1620 prefix: String,
1621 suffix: String,
1622 min_integer_digits: usize,
1623 min_fractional_digits: usize,
1624 max_fractional_digits: usize,
1625 grouping_positions: Vec<usize>,
1626 fractional_grouping_positions: Vec<usize>,
1627 regular_grouping: usize,
1628 has_decimal: bool,
1629 has_integer_part: bool,
1630 has_percent: bool,
1631 has_per_mille: bool,
1632 min_exponent_digits: usize,
1633 scaling_factor: usize,
1634 }
1635
1636 pub fn format_base(value: f64, radix: Option<i64>) -> Result<JValue, FunctionError> {
1639 let int_value = value.round() as i64;
1641
1642 let radix = radix.unwrap_or(10);
1644
1645 if !(2..=36).contains(&radix) {
1647 return Err(FunctionError::ArgumentError(format!(
1648 "D3100: Radix must be between 2 and 36, got {}",
1649 radix
1650 )));
1651 }
1652
1653 let is_negative = int_value < 0;
1655 let abs_value = int_value.unsigned_abs();
1656
1657 let digits = "0123456789abcdefghijklmnopqrstuvwxyz";
1659 let mut result = String::new();
1660 let mut val = abs_value;
1661
1662 if val == 0 {
1663 result.push('0');
1664 } else {
1665 while val > 0 {
1666 let digit = (val % radix as u64) as usize;
1667 result.insert(0, digits.chars().nth(digit).unwrap());
1668 val /= radix as u64;
1669 }
1670 }
1671
1672 if is_negative {
1674 result.insert(0, '-');
1675 }
1676
1677 Ok(JValue::string(result))
1678 }
1679}
1680
1681pub mod array {
1683 use super::*;
1684
1685 pub fn count(arr: &[JValue]) -> Result<JValue, FunctionError> {
1687 Ok(JValue::Number(arr.len() as f64))
1688 }
1689
1690 pub fn append(arr1: &[JValue], val: &JValue) -> Result<JValue, FunctionError> {
1692 let mut result = arr1.to_vec();
1693 match val {
1694 JValue::Array(arr2) => result.extend(arr2.iter().cloned()),
1695 other => result.push(other.clone()),
1696 }
1697 Ok(JValue::array(result))
1698 }
1699
1700 pub fn reverse(arr: &[JValue]) -> Result<JValue, FunctionError> {
1702 let mut result = arr.to_vec();
1703 result.reverse();
1704 Ok(JValue::array(result))
1705 }
1706
1707 pub fn sort(arr: &[JValue]) -> Result<JValue, FunctionError> {
1709 let mut result = arr.to_vec();
1710
1711 let all_numbers = result.iter().all(|v| matches!(v, JValue::Number(_)));
1713 let all_strings = result.iter().all(|v| matches!(v, JValue::String(_)));
1714
1715 if all_numbers {
1716 result.sort_by(|a, b| {
1717 let a_num = a.as_f64().unwrap();
1718 let b_num = b.as_f64().unwrap();
1719 a_num
1720 .partial_cmp(&b_num)
1721 .unwrap_or(std::cmp::Ordering::Equal)
1722 });
1723 } else if all_strings {
1724 result.sort_by(|a, b| {
1725 let a_str = a.as_str().unwrap();
1726 let b_str = b.as_str().unwrap();
1727 a_str.cmp(b_str)
1728 });
1729 } else {
1730 return Err(FunctionError::TypeError(
1731 "sort() requires all elements to be of the same comparable type".to_string(),
1732 ));
1733 }
1734
1735 Ok(JValue::array(result))
1736 }
1737
1738 pub fn distinct(arr: &[JValue]) -> Result<JValue, FunctionError> {
1740 let mut result = Vec::new();
1741 let mut seen: Vec<JValue> = Vec::new();
1742
1743 for value in arr {
1744 #[cfg(feature = "python")]
1752 let compare_value = match value {
1753 JValue::LazyPyDict(lazy) => JValue::Object(
1754 lazy.to_object()
1755 .map_err(|e| FunctionError::PyConversionError(e.0))?,
1756 ),
1757 other => other.clone(),
1758 };
1759 #[cfg(not(feature = "python"))]
1760 let compare_value = value.clone();
1761
1762 let mut is_new = true;
1763 for seen_value in &seen {
1764 if values_equal(&compare_value, seen_value) {
1765 is_new = false;
1766 break;
1767 }
1768 }
1769 if is_new {
1770 seen.push(compare_value);
1771 result.push(value.clone());
1772 }
1773 }
1774
1775 Ok(JValue::array(result))
1776 }
1777
1778 pub fn exists(value: &JValue) -> Result<JValue, FunctionError> {
1780 let is_missing = value.is_null() || value.is_undefined();
1781 Ok(JValue::Bool(!is_missing))
1782 }
1783
1784 pub fn values_equal(a: &JValue, b: &JValue) -> bool {
1791 match (a, b) {
1792 (JValue::Null, JValue::Null) => true,
1793 (JValue::Bool(a), JValue::Bool(b)) => a == b,
1794 (JValue::Number(a), JValue::Number(b)) => a == b,
1795 (JValue::String(a), JValue::String(b)) => a == b,
1796 (JValue::Array(a), JValue::Array(b)) => {
1797 a.len() == b.len() && a.iter().zip(b.iter()).all(|(x, y)| values_equal(x, y))
1798 }
1799 (JValue::Object(a), JValue::Object(b)) => {
1800 a.len() == b.len()
1801 && a.iter()
1802 .all(|(k, v)| b.get(k).is_some_and(|v2| values_equal(v, v2)))
1803 }
1804 #[cfg(feature = "python")]
1805 (JValue::LazyPyDict(x), JValue::Object(bm)) => x.to_object_ref().is_some_and(|am| {
1806 am.len() == bm.len()
1807 && am
1808 .iter()
1809 .all(|(k, v)| bm.get(k).is_some_and(|v2| values_equal(v, v2)))
1810 }),
1811 #[cfg(feature = "python")]
1812 (JValue::Object(am), JValue::LazyPyDict(y)) => y.to_object_ref().is_some_and(|bm| {
1813 am.len() == bm.len()
1814 && am
1815 .iter()
1816 .all(|(k, v)| bm.get(k).is_some_and(|v2| values_equal(v, v2)))
1817 }),
1818 #[cfg(feature = "python")]
1819 (JValue::LazyPyDict(x), JValue::LazyPyDict(y)) => {
1820 std::rc::Rc::ptr_eq(x, y)
1824 || x.same_object(y)
1825 || x.to_object_ref().is_some_and(|am| {
1826 y.to_object_ref().is_some_and(|bm| {
1827 am.len() == bm.len()
1828 && am
1829 .iter()
1830 .all(|(k, v)| bm.get(k).is_some_and(|v2| values_equal(v, v2)))
1831 })
1832 })
1833 }
1834 _ => false,
1835 }
1836 }
1837
1838 pub fn shuffle(arr: &[JValue]) -> Result<JValue, FunctionError> {
1841 if arr.len() <= 1 {
1842 return Ok(JValue::array(arr.to_vec()));
1843 }
1844
1845 use rand::seq::SliceRandom;
1846
1847 let mut result = arr.to_vec();
1848 let mut rng = rand::rng();
1849 result.shuffle(&mut rng);
1850
1851 Ok(JValue::array(result))
1852 }
1853}
1854
1855pub mod object {
1857 use super::*;
1858
1859 pub fn keys(obj: &IndexMap<String, JValue>) -> Result<JValue, FunctionError> {
1861 let keys: Vec<JValue> = obj.keys().map(|k| JValue::string(k.as_str())).collect();
1862 Ok(JValue::array(keys))
1863 }
1864
1865 pub fn lookup(obj: &IndexMap<String, JValue>, key: &str) -> Result<JValue, FunctionError> {
1867 Ok(obj.get(key).cloned().unwrap_or(JValue::Null))
1868 }
1869
1870 pub fn spread(obj: &IndexMap<String, JValue>) -> Result<JValue, FunctionError> {
1872 let pairs: Vec<JValue> = obj
1874 .iter()
1875 .map(|(k, v)| {
1876 let mut pair = IndexMap::new();
1877 pair.insert(k.clone(), v.clone());
1878 JValue::object(pair)
1879 })
1880 .collect();
1881 Ok(JValue::array(pairs))
1882 }
1883
1884 pub fn merge(objects: &[JValue]) -> Result<JValue, FunctionError> {
1886 let mut result = IndexMap::new();
1887
1888 for obj in objects {
1889 #[cfg(feature = "python")]
1890 if let JValue::LazyPyDict(lazy) = obj {
1891 match lazy.to_object_ref() {
1892 Some(map) => {
1893 for (k, v) in map.iter() {
1894 result.insert(k.clone(), v.clone());
1895 }
1896 continue;
1897 }
1898 None => {
1899 return Err(FunctionError::TypeError(
1900 "merge() argument could not be converted".to_string(),
1901 ))
1902 }
1903 }
1904 }
1905 match obj {
1906 JValue::Object(map) => {
1907 for (k, v) in map.iter() {
1908 result.insert(k.clone(), v.clone());
1909 }
1910 }
1911 _ => {
1912 return Err(FunctionError::TypeError(
1913 "merge() requires all arguments to be objects".to_string(),
1914 ))
1915 }
1916 }
1917 }
1918
1919 Ok(JValue::object(result))
1920 }
1921}
1922
1923pub mod encoding {
1925 use super::*;
1926 use base64::{engine::general_purpose, Engine as _};
1927
1928 pub fn base64encode(s: &str) -> Result<JValue, FunctionError> {
1930 let encoded = general_purpose::STANDARD.encode(s.as_bytes());
1931 Ok(JValue::string(encoded))
1932 }
1933
1934 pub fn base64decode(s: &str) -> Result<JValue, FunctionError> {
1936 match general_purpose::STANDARD.decode(s.as_bytes()) {
1937 Ok(bytes) => match String::from_utf8(bytes) {
1938 Ok(decoded) => Ok(JValue::string(decoded)),
1939 Err(_) => Err(FunctionError::RuntimeError(
1940 "Invalid UTF-8 in decoded base64".to_string(),
1941 )),
1942 },
1943 Err(_) => Err(FunctionError::RuntimeError(
1944 "Invalid base64 string".to_string(),
1945 )),
1946 }
1947 }
1948
1949 pub fn encode_url_component(s: &str) -> Result<JValue, FunctionError> {
1951 let encoded = percent_encoding::utf8_percent_encode(s, percent_encoding::NON_ALPHANUMERIC)
1952 .to_string();
1953 Ok(JValue::string(encoded))
1954 }
1955
1956 pub fn decode_url_component(s: &str) -> Result<JValue, FunctionError> {
1958 match percent_encoding::percent_decode_str(s).decode_utf8() {
1959 Ok(decoded) => Ok(JValue::string(decoded.to_string())),
1960 Err(_) => Err(FunctionError::RuntimeError(
1961 "Invalid percent-encoded string".to_string(),
1962 )),
1963 }
1964 }
1965
1966 pub fn encode_url(s: &str) -> Result<JValue, FunctionError> {
1969 let encoded =
1971 percent_encoding::utf8_percent_encode(s, percent_encoding::CONTROLS).to_string();
1972 Ok(JValue::string(encoded))
1973 }
1974
1975 pub fn decode_url(s: &str) -> Result<JValue, FunctionError> {
1977 match percent_encoding::percent_decode_str(s).decode_utf8() {
1978 Ok(decoded) => Ok(JValue::string(decoded.to_string())),
1979 Err(_) => Err(FunctionError::RuntimeError(
1980 "Invalid percent-encoded URL".to_string(),
1981 )),
1982 }
1983 }
1984}
1985
1986#[cfg(test)]
1987mod tests {
1988 use super::*;
1989
1990 #[test]
1993 fn test_string_conversion() {
1994 assert_eq!(
1996 string::string(&JValue::string("hello"), None).unwrap(),
1997 JValue::string("hello")
1998 );
1999
2000 assert_eq!(
2002 string::string(&JValue::Number(42.0), None).unwrap(),
2003 JValue::string("42")
2004 );
2005
2006 assert_eq!(
2008 string::string(&JValue::Number(3.14), None).unwrap(),
2009 JValue::string("3.14")
2010 );
2011
2012 assert_eq!(
2014 string::string(&JValue::Bool(true), None).unwrap(),
2015 JValue::string("true")
2016 );
2017
2018 assert_eq!(
2020 string::string(&JValue::Null, None).unwrap(),
2021 JValue::string("null")
2022 );
2023
2024 assert_eq!(
2026 string::string(
2027 &JValue::array(vec![
2028 JValue::from(1i64),
2029 JValue::from(2i64),
2030 JValue::from(3i64)
2031 ]),
2032 None
2033 )
2034 .unwrap(),
2035 JValue::string("[1,2,3]")
2036 );
2037 }
2038
2039 #[test]
2040 fn test_length() {
2041 assert_eq!(string::length("hello").unwrap(), JValue::Number(5.0));
2042 assert_eq!(string::length("").unwrap(), JValue::Number(0.0));
2043 assert_eq!(
2045 string::length("Hello \u{4e16}\u{754c}").unwrap(),
2046 JValue::Number(8.0)
2047 );
2048 assert_eq!(
2049 string::length("\u{1f389}\u{1f38a}").unwrap(),
2050 JValue::Number(2.0)
2051 );
2052 }
2053
2054 #[test]
2055 fn test_uppercase_lowercase() {
2056 assert_eq!(string::uppercase("hello").unwrap(), JValue::string("HELLO"));
2057 assert_eq!(string::lowercase("HELLO").unwrap(), JValue::string("hello"));
2058 assert_eq!(
2059 string::uppercase("Hello World").unwrap(),
2060 JValue::string("HELLO WORLD")
2061 );
2062 }
2063
2064 #[test]
2065 fn test_substring() {
2066 assert_eq!(
2068 string::substring("hello world", 0, Some(5)).unwrap(),
2069 JValue::string("hello")
2070 );
2071
2072 assert_eq!(
2074 string::substring("hello world", 6, None).unwrap(),
2075 JValue::string("world")
2076 );
2077
2078 assert_eq!(
2080 string::substring("hello world", -5, Some(5)).unwrap(),
2081 JValue::string("world")
2082 );
2083
2084 assert_eq!(
2086 string::substring("Hello \u{4e16}\u{754c}", 6, Some(2)).unwrap(),
2087 JValue::string("\u{4e16}\u{754c}")
2088 );
2089
2090 assert_eq!(
2092 string::substring("hello", 0, Some(-1)).unwrap(),
2093 JValue::string("")
2094 );
2095 }
2096
2097 #[test]
2098 fn test_substring_before_after() {
2099 assert_eq!(
2101 string::substring_before("hello world", " ").unwrap(),
2102 JValue::string("hello")
2103 );
2104 assert_eq!(
2105 string::substring_before("hello world", "x").unwrap(),
2106 JValue::string("hello world")
2107 );
2108 assert_eq!(
2109 string::substring_before("hello world", "").unwrap(),
2110 JValue::string("")
2111 );
2112
2113 assert_eq!(
2115 string::substring_after("hello world", " ").unwrap(),
2116 JValue::string("world")
2117 );
2118 assert_eq!(
2120 string::substring_after("hello world", "x").unwrap(),
2121 JValue::string("hello world")
2122 );
2123 assert_eq!(
2124 string::substring_after("hello world", "").unwrap(),
2125 JValue::string("hello world")
2126 );
2127 }
2128
2129 #[test]
2130 fn test_trim() {
2131 assert_eq!(string::trim(" hello ").unwrap(), JValue::string("hello"));
2132 assert_eq!(string::trim("hello").unwrap(), JValue::string("hello"));
2133 assert_eq!(
2134 string::trim("\t\nhello\r\n").unwrap(),
2135 JValue::string("hello")
2136 );
2137 }
2138
2139 #[test]
2140 fn test_contains() {
2141 assert_eq!(
2142 string::contains("hello world", &JValue::string("world")).unwrap(),
2143 JValue::Bool(true)
2144 );
2145 assert_eq!(
2146 string::contains("hello world", &JValue::string("xyz")).unwrap(),
2147 JValue::Bool(false)
2148 );
2149 assert_eq!(
2150 string::contains("hello world", &JValue::string("")).unwrap(),
2151 JValue::Bool(true)
2152 );
2153 }
2154
2155 #[test]
2156 fn test_split() {
2157 assert_eq!(
2159 string::split("a,b,c", &JValue::string(","), None).unwrap(),
2160 JValue::array(vec![
2161 JValue::string("a"),
2162 JValue::string("b"),
2163 JValue::string("c")
2164 ])
2165 );
2166
2167 assert_eq!(
2169 string::split("a,b,c,d", &JValue::string(","), Some(2)).unwrap(),
2170 JValue::array(vec![JValue::string("a"), JValue::string("b")])
2171 );
2172
2173 assert_eq!(
2175 string::split("abc", &JValue::string(""), None).unwrap(),
2176 JValue::array(vec![
2177 JValue::string("a"),
2178 JValue::string("b"),
2179 JValue::string("c")
2180 ])
2181 );
2182 }
2183
2184 #[test]
2185 fn test_join() {
2186 let arr = vec![
2188 JValue::string("a"),
2189 JValue::string("b"),
2190 JValue::string("c"),
2191 ];
2192 assert_eq!(
2193 string::join(&arr, Some(",")).unwrap(),
2194 JValue::string("a,b,c")
2195 );
2196
2197 assert_eq!(string::join(&arr, None).unwrap(), JValue::string("abc"));
2199
2200 let arr = vec![JValue::from(1i64), JValue::from(2i64), JValue::from(3i64)];
2202 assert_eq!(
2203 string::join(&arr, Some("-")).unwrap(),
2204 JValue::string("1-2-3")
2205 );
2206 }
2207
2208 #[test]
2209 fn test_replace() {
2210 assert_eq!(
2212 string::replace("hello hello", &JValue::string("hello"), "hi", None).unwrap(),
2213 JValue::string("hi hi")
2214 );
2215
2216 assert_eq!(
2218 string::replace("hello hello hello", &JValue::string("hello"), "hi", Some(2)).unwrap(),
2219 JValue::string("hi hi hello")
2220 );
2221
2222 assert!(string::replace("hello", &JValue::string(""), "x", None).is_err());
2224 }
2225
2226 #[test]
2229 fn test_number_conversion() {
2230 assert_eq!(
2232 numeric::number(&JValue::Number(42.0)).unwrap(),
2233 JValue::Number(42.0)
2234 );
2235
2236 assert_eq!(
2238 numeric::number(&JValue::string("42")).unwrap(),
2239 JValue::Number(42.0)
2240 );
2241 assert_eq!(
2242 numeric::number(&JValue::string("3.14")).unwrap(),
2243 JValue::Number(3.14)
2244 );
2245 assert_eq!(
2246 numeric::number(&JValue::string(" 123 ")).unwrap(),
2247 JValue::Number(123.0)
2248 );
2249
2250 assert_eq!(
2252 numeric::number(&JValue::Bool(true)).unwrap(),
2253 JValue::Number(1.0)
2254 );
2255 assert_eq!(
2256 numeric::number(&JValue::Bool(false)).unwrap(),
2257 JValue::Number(0.0)
2258 );
2259
2260 assert!(numeric::number(&JValue::Null).is_err());
2262 assert!(numeric::number(&JValue::string("not a number")).is_err());
2263 }
2264
2265 #[test]
2266 fn test_sum() {
2267 let arr = vec![JValue::from(1i64), JValue::from(2i64), JValue::from(3i64)];
2269 assert_eq!(numeric::sum(&arr).unwrap(), JValue::Number(6.0));
2270
2271 assert_eq!(numeric::sum(&[]).unwrap(), JValue::Number(0.0));
2273
2274 let arr = vec![JValue::from(1i64), JValue::string("2")];
2276 assert!(numeric::sum(&arr).is_err());
2277 }
2278
2279 #[test]
2280 fn test_max_min() {
2281 let arr = vec![
2282 JValue::from(3i64),
2283 JValue::from(1i64),
2284 JValue::from(4i64),
2285 JValue::from(2i64),
2286 ];
2287
2288 assert_eq!(numeric::max(&arr).unwrap(), JValue::Number(4.0));
2289 assert_eq!(numeric::min(&arr).unwrap(), JValue::Number(1.0));
2290
2291 assert_eq!(numeric::max(&[]).unwrap(), JValue::Null);
2293 assert_eq!(numeric::min(&[]).unwrap(), JValue::Null);
2294 }
2295
2296 #[test]
2297 fn test_average() {
2298 let arr = vec![
2299 JValue::from(1i64),
2300 JValue::from(2i64),
2301 JValue::from(3i64),
2302 JValue::from(4i64),
2303 ];
2304 assert_eq!(numeric::average(&arr).unwrap(), JValue::Number(2.5));
2305
2306 assert_eq!(numeric::average(&[]).unwrap(), JValue::Null);
2308 }
2309
2310 #[test]
2311 fn test_math_functions() {
2312 assert_eq!(numeric::abs(-5.5).unwrap(), JValue::Number(5.5));
2314 assert_eq!(numeric::abs(5.5).unwrap(), JValue::Number(5.5));
2315
2316 assert_eq!(numeric::floor(3.7).unwrap(), JValue::Number(3.0));
2318 assert_eq!(numeric::floor(-3.7).unwrap(), JValue::Number(-4.0));
2319
2320 assert_eq!(numeric::ceil(3.2).unwrap(), JValue::Number(4.0));
2322 assert_eq!(numeric::ceil(-3.2).unwrap(), JValue::Number(-3.0));
2323
2324 assert_eq!(
2326 numeric::round(3.14159, Some(2)).unwrap(),
2327 JValue::Number(3.14)
2328 );
2329 assert_eq!(numeric::round(3.14159, None).unwrap(), JValue::Number(3.0));
2330 assert_eq!(numeric::round(3.14, Some(-1)).unwrap(), JValue::Number(0.0));
2332
2333 assert_eq!(numeric::sqrt(16.0).unwrap(), JValue::Number(4.0));
2335 assert!(numeric::sqrt(-1.0).is_err());
2336
2337 assert_eq!(numeric::power(2.0, 3.0).unwrap(), JValue::Number(8.0));
2339 assert_eq!(numeric::power(9.0, 0.5).unwrap(), JValue::Number(3.0));
2340 }
2341
2342 #[test]
2345 fn test_count() {
2346 let arr = vec![JValue::from(1i64), JValue::from(2i64), JValue::from(3i64)];
2347 assert_eq!(array::count(&arr).unwrap(), JValue::Number(3.0));
2348 assert_eq!(array::count(&[]).unwrap(), JValue::Number(0.0));
2349 }
2350
2351 #[test]
2352 fn test_append() {
2353 let arr1 = vec![JValue::from(1i64), JValue::from(2i64)];
2354
2355 let result = array::append(&arr1, &JValue::from(3i64)).unwrap();
2357 assert_eq!(
2358 result,
2359 JValue::array(vec![
2360 JValue::from(1i64),
2361 JValue::from(2i64),
2362 JValue::from(3i64)
2363 ])
2364 );
2365
2366 let arr2 = JValue::array(vec![JValue::from(3i64), JValue::from(4i64)]);
2368 let result = array::append(&arr1, &arr2).unwrap();
2369 assert_eq!(
2370 result,
2371 JValue::array(vec![
2372 JValue::from(1i64),
2373 JValue::from(2i64),
2374 JValue::from(3i64),
2375 JValue::from(4i64)
2376 ])
2377 );
2378 }
2379
2380 #[test]
2381 fn test_reverse() {
2382 let arr = vec![JValue::from(1i64), JValue::from(2i64), JValue::from(3i64)];
2383 assert_eq!(
2384 array::reverse(&arr).unwrap(),
2385 JValue::array(vec![
2386 JValue::from(3i64),
2387 JValue::from(2i64),
2388 JValue::from(1i64)
2389 ])
2390 );
2391 }
2392
2393 #[test]
2394 fn test_sort() {
2395 let arr = vec![
2397 JValue::from(3i64),
2398 JValue::from(1i64),
2399 JValue::from(4i64),
2400 JValue::from(2i64),
2401 ];
2402 assert_eq!(
2403 array::sort(&arr).unwrap(),
2404 JValue::array(vec![
2405 JValue::from(1i64),
2406 JValue::from(2i64),
2407 JValue::from(3i64),
2408 JValue::from(4i64)
2409 ])
2410 );
2411
2412 let arr = vec![
2414 JValue::string("charlie"),
2415 JValue::string("alice"),
2416 JValue::string("bob"),
2417 ];
2418 assert_eq!(
2419 array::sort(&arr).unwrap(),
2420 JValue::array(vec![
2421 JValue::string("alice"),
2422 JValue::string("bob"),
2423 JValue::string("charlie")
2424 ])
2425 );
2426
2427 let arr = vec![JValue::from(1i64), JValue::string("a")];
2429 assert!(array::sort(&arr).is_err());
2430 }
2431
2432 #[test]
2433 fn test_distinct() {
2434 let arr = vec![
2435 JValue::from(1i64),
2436 JValue::from(2i64),
2437 JValue::from(1i64),
2438 JValue::from(3i64),
2439 JValue::from(2i64),
2440 ];
2441 assert_eq!(
2442 array::distinct(&arr).unwrap(),
2443 JValue::array(vec![
2444 JValue::from(1i64),
2445 JValue::from(2i64),
2446 JValue::from(3i64)
2447 ])
2448 );
2449
2450 let arr = vec![
2452 JValue::string("a"),
2453 JValue::string("b"),
2454 JValue::string("a"),
2455 ];
2456 assert_eq!(
2457 array::distinct(&arr).unwrap(),
2458 JValue::array(vec![JValue::string("a"), JValue::string("b")])
2459 );
2460 }
2461
2462 #[test]
2463 fn test_exists() {
2464 assert_eq!(
2465 array::exists(&JValue::Number(42.0)).unwrap(),
2466 JValue::Bool(true)
2467 );
2468 assert_eq!(
2469 array::exists(&JValue::string("hello")).unwrap(),
2470 JValue::Bool(true)
2471 );
2472 assert_eq!(array::exists(&JValue::Null).unwrap(), JValue::Bool(false));
2473 }
2474
2475 #[test]
2478 fn test_keys() {
2479 let mut obj = IndexMap::new();
2480 obj.insert("name".to_string(), JValue::string("Alice"));
2481 obj.insert("age".to_string(), JValue::Number(30.0));
2482
2483 let result = object::keys(&obj).unwrap();
2484 if let JValue::Array(keys) = result {
2485 assert_eq!(keys.len(), 2);
2486 assert!(keys.contains(&JValue::string("name")));
2487 assert!(keys.contains(&JValue::string("age")));
2488 } else {
2489 panic!("Expected array of keys");
2490 }
2491 }
2492
2493 #[test]
2494 fn test_lookup() {
2495 let mut obj = IndexMap::new();
2496 obj.insert("name".to_string(), JValue::string("Alice"));
2497 obj.insert("age".to_string(), JValue::Number(30.0));
2498
2499 assert_eq!(
2500 object::lookup(&obj, "name").unwrap(),
2501 JValue::string("Alice")
2502 );
2503 assert_eq!(object::lookup(&obj, "age").unwrap(), JValue::Number(30.0));
2504 assert_eq!(object::lookup(&obj, "missing").unwrap(), JValue::Null);
2505 }
2506
2507 #[test]
2508 fn test_spread() {
2509 let mut obj = IndexMap::new();
2510 obj.insert("a".to_string(), JValue::from(1i64));
2511 obj.insert("b".to_string(), JValue::from(2i64));
2512
2513 let result = object::spread(&obj).unwrap();
2514 if let JValue::Array(pairs) = result {
2515 assert_eq!(pairs.len(), 2);
2516 for pair in pairs.iter() {
2518 if let JValue::Object(p) = pair {
2519 assert_eq!(
2520 p.len(),
2521 1,
2522 "Each spread element should be a single-key object"
2523 );
2524 } else {
2525 panic!("Expected Object in spread result");
2526 }
2527 }
2528 let all_keys: Vec<String> = pairs
2530 .iter()
2531 .filter_map(|p| {
2532 if let JValue::Object(m) = p {
2533 m.keys().next().cloned()
2534 } else {
2535 None
2536 }
2537 })
2538 .collect();
2539 assert!(all_keys.contains(&"a".to_string()));
2540 assert!(all_keys.contains(&"b".to_string()));
2541 } else {
2542 panic!("Expected array of key-value pairs");
2543 }
2544 }
2545
2546 #[test]
2547 fn test_merge() {
2548 let mut obj1 = IndexMap::new();
2549 obj1.insert("a".to_string(), JValue::from(1i64));
2550 obj1.insert("b".to_string(), JValue::from(2i64));
2551
2552 let mut obj2 = IndexMap::new();
2553 obj2.insert("b".to_string(), JValue::from(3i64));
2554 obj2.insert("c".to_string(), JValue::from(4i64));
2555
2556 let arr = vec![JValue::object(obj1), JValue::object(obj2)];
2557 let result = object::merge(&arr).unwrap();
2558
2559 if let JValue::Object(merged) = result {
2560 assert_eq!(merged.get("a"), Some(&JValue::from(1i64)));
2561 assert_eq!(merged.get("b"), Some(&JValue::from(3i64))); assert_eq!(merged.get("c"), Some(&JValue::from(4i64)));
2563 } else {
2564 panic!("Expected merged object");
2565 }
2566 }
2567}