funcscript/
native.rs

1//! Built-in/native functions for the Rust core runtime.
2//!
3//! These are registered in the VM global scope (e.g. `Range`, `Len`, `First`, `And`/`Or`/`In`).
4
5use crate::value::Value;
6use crate::value::FsError;
7use std::rc::Rc;
8use crate::obj::Obj;
9use num_bigint::BigInt;
10use num_traits::{Signed, ToPrimitive, Zero};
11use base64::{engine::general_purpose, Engine as _};
12use uuid::Uuid;
13use std::cell::RefCell;
14use std::collections::{HashMap, HashSet};
15use crate::obj::KvcObject;
16use regex::Regex;
17use crate::host;
18
19pub fn define_natives(globals: &mut std::collections::HashMap<String, Value>) {
20    let mut insert = |name: &str, v: Value| {
21        globals.insert(name.to_ascii_lowercase(), v);
22    };
23
24    insert("Range", Value::Obj(Rc::new(Obj::NativeFn(fs_range))));
25    insert("true", Value::Bool(true));
26    insert("false", Value::Bool(false));
27    
28    insert("Abs", Value::Obj(Rc::new(Obj::NativeFn(math_abs))));
29    insert("Max", Value::Obj(Rc::new(Obj::NativeFn(math_max))));
30    insert("Min", Value::Obj(Rc::new(Obj::NativeFn(math_min))));
31    insert("Sqrt", Value::Obj(Rc::new(Obj::NativeFn(math_sqrt))));
32
33    insert("Len", Value::Obj(Rc::new(Obj::NativeFn(fs_len))));
34    insert("First", Value::Obj(Rc::new(Obj::NativeFn(fs_first))));
35
36    insert("And", Value::Obj(Rc::new(Obj::NativeFn(fs_and))));
37    insert("Or", Value::Obj(Rc::new(Obj::NativeFn(fs_or))));
38    insert("In", Value::Obj(Rc::new(Obj::NativeFn(fs_in))));
39
40    insert("TemplateMerge", Value::Obj(Rc::new(Obj::NativeFn(fs_template_merge))));
41
42    insert("Sum", Value::Obj(Rc::new(Obj::NativeFn(fs_sum))));
43    insert("SumApprox", Value::Obj(Rc::new(Obj::NativeFn(fs_sum_approx))));
44
45    insert("Date", Value::Obj(Rc::new(Obj::NativeFn(fs_date))));
46    insert("TicksToDate", Value::Obj(Rc::new(Obj::NativeFn(fs_ticks_to_date))));
47    insert("guid", Value::Obj(Rc::new(Obj::NativeFn(fs_guid))));
48    insert("ChangeType", Value::Obj(Rc::new(Obj::NativeFn(fs_change_type))));
49
50    insert("lower", Value::Obj(Rc::new(Obj::NativeFn(text_lower))));
51    insert("upper", Value::Obj(Rc::new(Obj::NativeFn(text_upper))));
52    insert("endswith", Value::Obj(Rc::new(Obj::NativeFn(text_endswith))));
53    insert("substring", Value::Obj(Rc::new(Obj::NativeFn(text_substring))));
54    insert("find", Value::Obj(Rc::new(Obj::NativeFn(text_find))));
55    insert("isBlank", Value::Obj(Rc::new(Obj::NativeFn(text_is_blank))));
56    insert("join", Value::Obj(Rc::new(Obj::NativeFn(text_join))));
57
58    insert("Take", Value::Obj(Rc::new(Obj::NativeFn(list_take))));
59    insert("Skip", Value::Obj(Rc::new(Obj::NativeFn(list_skip))));
60    insert("Reverse", Value::Obj(Rc::new(Obj::NativeFn(list_reverse))));
61    insert("Distinct", Value::Obj(Rc::new(Obj::NativeFn(list_distinct))));
62    insert("Contains", Value::Obj(Rc::new(Obj::NativeFn(list_contains))));
63
64    // Provider collections (C# parity): `math.*`, `text.*`, `float.*`
65    insert("math", build_math_provider());
66    insert("text", build_text_provider());
67    insert("float", build_float_provider());
68
69    // Common math globals (C# function names)
70    insert("Pow", Value::Obj(Rc::new(Obj::NativeFn(math_pow))));
71    insert("Sin", Value::Obj(Rc::new(Obj::NativeFn(math_sin))));
72    insert("Cos", Value::Obj(Rc::new(Obj::NativeFn(math_cos))));
73    insert("Tan", Value::Obj(Rc::new(Obj::NativeFn(math_tan))));
74    insert("Asin", Value::Obj(Rc::new(Obj::NativeFn(math_asin))));
75    insert("Acos", Value::Obj(Rc::new(Obj::NativeFn(math_acos))));
76    insert("Atan", Value::Obj(Rc::new(Obj::NativeFn(math_atan))));
77    insert("Atan2", Value::Obj(Rc::new(Obj::NativeFn(math_atan2))));
78    insert("Exp", Value::Obj(Rc::new(Obj::NativeFn(math_exp))));
79    insert("Ln", Value::Obj(Rc::new(Obj::NativeFn(math_ln))));
80    insert("Log10", Value::Obj(Rc::new(Obj::NativeFn(math_log10))));
81    insert("Log2", Value::Obj(Rc::new(Obj::NativeFn(math_log2))));
82    insert("Ceiling", Value::Obj(Rc::new(Obj::NativeFn(math_ceil))));
83    insert("Floor", Value::Obj(Rc::new(Obj::NativeFn(math_floor))));
84    insert("Round", Value::Obj(Rc::new(Obj::NativeFn(math_round))));
85    insert("Trunc", Value::Obj(Rc::new(Obj::NativeFn(math_trunc))));
86    insert("Sign", Value::Obj(Rc::new(Obj::NativeFn(math_sign))));
87    insert("Clamp", Value::Obj(Rc::new(Obj::NativeFn(math_clamp))));
88    insert("Random", Value::Obj(Rc::new(Obj::NativeFn(math_random))));
89    insert("Cbrt", Value::Obj(Rc::new(Obj::NativeFn(math_cbrt))));
90    insert("DegToRad", Value::Obj(Rc::new(Obj::NativeFn(math_deg_to_rad))));
91    insert("RadToDeg", Value::Obj(Rc::new(Obj::NativeFn(math_rad_to_deg))));
92
93    insert("IsNaN", Value::Obj(Rc::new(Obj::NativeFn(float_is_nan))));
94    insert("IsInfinity", Value::Obj(Rc::new(Obj::NativeFn(float_is_infinity))));
95    insert("IsNormal", Value::Obj(Rc::new(Obj::NativeFn(float_is_normal))));
96
97    // Remaining C# built-ins
98    insert("regex", Value::Obj(Rc::new(Obj::NativeFn(text_regex))));
99    insert("parse", Value::Obj(Rc::new(Obj::NativeFn(text_parse))));
100    insert("format", Value::Obj(Rc::new(Obj::NativeFn(text_format))));
101    insert("_templatemerge", Value::Obj(Rc::new(Obj::NativeFn(text_templatemerge))));
102    insert("HEncode", Value::Obj(Rc::new(Obj::NativeFn(html_encode))));
103
104    insert("error", Value::Obj(Rc::new(Obj::NativeFn(misc_error))));
105    insert("log", Value::Obj(Rc::new(Obj::NativeFn(misc_log))));
106
107    insert("file", Value::Obj(Rc::new(Obj::NativeFn(os_file_text))));
108    insert("fileexists", Value::Obj(Rc::new(Obj::NativeFn(os_file_exists))));
109    insert("isfile", Value::Obj(Rc::new(Obj::NativeFn(os_is_file))));
110    insert("dirlist", Value::Obj(Rc::new(Obj::NativeFn(os_dir_list))));
111}
112
113fn kvc_from_cache(display_names_in_order: Vec<(&str, Value)>) -> Value {
114    let mut cache: HashMap<String, Value> = HashMap::new();
115    let mut order: Vec<String> = Vec::with_capacity(display_names_in_order.len());
116    let mut display_names: HashMap<String, String> = HashMap::new();
117    for (display, v) in display_names_in_order {
118        let key_l = display.to_lowercase();
119        cache.insert(key_l.clone(), v);
120        order.push(key_l.clone());
121        display_names.insert(key_l, display.to_string());
122    }
123    let kvc = KvcObject {
124        entries: HashMap::new(),
125        cache,
126        evaluating: HashSet::new(),
127        parent: None,
128        order,
129        display_names,
130    };
131    Value::Obj(Rc::new(Obj::Kvc(Rc::new(RefCell::new(kvc)))))
132}
133
134fn build_math_provider() -> Value {
135    kvc_from_cache(vec![
136        ("Pi", Value::Number(std::f64::consts::PI)),
137        ("E", Value::Number(std::f64::consts::E)),
138
139        ("Abs", Value::Obj(Rc::new(Obj::NativeFn(math_abs)))),
140        ("Min", Value::Obj(Rc::new(Obj::NativeFn(math_min)))),
141        ("Max", Value::Obj(Rc::new(Obj::NativeFn(math_max)))),
142        ("Sqrt", Value::Obj(Rc::new(Obj::NativeFn(math_sqrt)))),
143        ("Pow", Value::Obj(Rc::new(Obj::NativeFn(math_pow)))),
144
145        ("Sin", Value::Obj(Rc::new(Obj::NativeFn(math_sin)))),
146        ("Cos", Value::Obj(Rc::new(Obj::NativeFn(math_cos)))),
147        ("Tan", Value::Obj(Rc::new(Obj::NativeFn(math_tan)))),
148        ("Asin", Value::Obj(Rc::new(Obj::NativeFn(math_asin)))),
149        ("Acos", Value::Obj(Rc::new(Obj::NativeFn(math_acos)))),
150        ("Atan", Value::Obj(Rc::new(Obj::NativeFn(math_atan)))),
151        ("Atan2", Value::Obj(Rc::new(Obj::NativeFn(math_atan2)))),
152
153        ("Exp", Value::Obj(Rc::new(Obj::NativeFn(math_exp)))),
154        ("Ln", Value::Obj(Rc::new(Obj::NativeFn(math_ln)))),
155        ("Log10", Value::Obj(Rc::new(Obj::NativeFn(math_log10)))),
156        ("Log2", Value::Obj(Rc::new(Obj::NativeFn(math_log2)))),
157
158        ("Ceiling", Value::Obj(Rc::new(Obj::NativeFn(math_ceil)))),
159        ("Floor", Value::Obj(Rc::new(Obj::NativeFn(math_floor)))),
160        ("Round", Value::Obj(Rc::new(Obj::NativeFn(math_round)))),
161        ("Trunc", Value::Obj(Rc::new(Obj::NativeFn(math_trunc)))),
162        ("Sign", Value::Obj(Rc::new(Obj::NativeFn(math_sign)))),
163        ("Clamp", Value::Obj(Rc::new(Obj::NativeFn(math_clamp)))),
164
165        ("Random", Value::Obj(Rc::new(Obj::NativeFn(math_random)))),
166        ("Cbrt", Value::Obj(Rc::new(Obj::NativeFn(math_cbrt)))),
167        ("DegToRad", Value::Obj(Rc::new(Obj::NativeFn(math_deg_to_rad)))),
168        ("RadToDeg", Value::Obj(Rc::new(Obj::NativeFn(math_rad_to_deg)))),
169    ])
170}
171
172fn build_text_provider() -> Value {
173    kvc_from_cache(vec![
174        ("lower", Value::Obj(Rc::new(Obj::NativeFn(text_lower)))),
175        ("upper", Value::Obj(Rc::new(Obj::NativeFn(text_upper)))),
176        ("endswith", Value::Obj(Rc::new(Obj::NativeFn(text_endswith)))),
177        ("substring", Value::Obj(Rc::new(Obj::NativeFn(text_substring)))),
178        ("find", Value::Obj(Rc::new(Obj::NativeFn(text_find)))),
179        ("isBlank", Value::Obj(Rc::new(Obj::NativeFn(text_is_blank)))),
180        ("join", Value::Obj(Rc::new(Obj::NativeFn(text_join)))),
181        ("regex", Value::Obj(Rc::new(Obj::NativeFn(text_regex)))),
182        ("parse", Value::Obj(Rc::new(Obj::NativeFn(text_parse)))),
183        ("format", Value::Obj(Rc::new(Obj::NativeFn(text_format)))),
184        ("_templatemerge", Value::Obj(Rc::new(Obj::NativeFn(text_templatemerge)))),
185    ])
186}
187
188fn build_float_provider() -> Value {
189    kvc_from_cache(vec![
190        ("IsNormal", Value::Obj(Rc::new(Obj::NativeFn(float_is_normal)))),
191        ("IsNaN", Value::Obj(Rc::new(Obj::NativeFn(float_is_nan)))),
192        ("IsInfinity", Value::Obj(Rc::new(Obj::NativeFn(float_is_infinity)))),
193    ])
194}
195
196fn math_abs(args: &[Value]) -> Value {
197    if args.len() != 1 { return Value::Nil; }
198    match &args[0] {
199        Value::Number(n) => Value::Number(n.abs()),
200        Value::Int(n) => match n.checked_abs() {
201            Some(v) => Value::Int(v),
202            None => Value::BigInt(BigInt::from(*n).abs()),
203        },
204        Value::BigInt(n) => Value::BigInt(n.abs()),
205        _ => Value::Nil,
206    }
207}
208
209fn math_max(args: &[Value]) -> Value {
210    if args.len() != 2 { return Value::Nil; }
211    match (&args[0], &args[1]) {
212        (Value::Number(a), Value::Number(b)) => Value::Number(a.max(*b)),
213        (a, b) => {
214            let ai = match a {
215                Value::Int(n) => Some(BigInt::from(*n)),
216                Value::BigInt(n) => Some(n.clone()),
217                _ => None,
218            };
219            let bi = match b {
220                Value::Int(n) => Some(BigInt::from(*n)),
221                Value::BigInt(n) => Some(n.clone()),
222                _ => None,
223            };
224            if let (Some(ai), Some(bi)) = (ai, bi) {
225                let m = if ai >= bi { ai } else { bi };
226                if let Some(v) = m.to_i64() { Value::Int(v) } else { Value::BigInt(m) }
227            } else {
228                Value::Nil
229            }
230        }
231    }
232}
233
234fn math_min(args: &[Value]) -> Value {
235    if args.len() != 2 { return Value::Nil; }
236    match (&args[0], &args[1]) {
237        (Value::Number(a), Value::Number(b)) => Value::Number(a.min(*b)),
238        (a, b) => {
239            let ai = match a {
240                Value::Int(n) => Some(BigInt::from(*n)),
241                Value::BigInt(n) => Some(n.clone()),
242                _ => None,
243            };
244            let bi = match b {
245                Value::Int(n) => Some(BigInt::from(*n)),
246                Value::BigInt(n) => Some(n.clone()),
247                _ => None,
248            };
249            if let (Some(ai), Some(bi)) = (ai, bi) {
250                let m = if ai <= bi { ai } else { bi };
251                if let Some(v) = m.to_i64() { Value::Int(v) } else { Value::BigInt(m) }
252            } else {
253                Value::Nil
254            }
255        }
256    }
257}
258
259fn math_sqrt(args: &[Value]) -> Value {
260    if args.len() != 1 { return Value::Nil; }
261    match &args[0] {
262         Value::Number(n) => Value::Number(n.sqrt()),
263         Value::Int(n) => Value::Number((*n as f64).sqrt()),
264         Value::BigInt(n) => match n.to_f64() {
265             Some(x) => Value::Number(x.sqrt()),
266             None => Value::Nil,
267         },
268         _ => Value::Nil,
269    }
270}
271
272fn math_num1(args: &[Value], name: &str) -> Result<f64, Value> {
273    if args.len() != 1 {
274        return Err(Value::Error(FsError { code: 1, message: format!("{name}: number expected"), line: -1, column: -1 }));
275    }
276    match &args[0] {
277        Value::Error(e) => Err(Value::Error(e.clone())),
278        Value::Int(n) => Ok(*n as f64),
279        Value::BigInt(n) => n.to_f64().ok_or_else(|| Value::Error(FsError { code: 1, message: format!("{name}: number out of range"), line: -1, column: -1 })),
280        Value::Number(n) if n.is_finite() => Ok(*n),
281        _ => Err(Value::Error(FsError { code: 2, message: format!("{name}: number expected"), line: -1, column: -1 })),
282    }
283}
284
285fn math_num2(args: &[Value], name: &str) -> Result<(f64, f64), Value> {
286    if args.len() != 2 {
287        return Err(Value::Error(FsError { code: 1, message: format!("{name}: Expected 2 parameters"), line: -1, column: -1 }));
288    }
289    let a = math_num1(&args[0..1], name)?;
290    let b = math_num1(&args[1..2], name)?;
291    Ok((a, b))
292}
293
294fn math_pow(args: &[Value]) -> Value {
295    match math_num2(args, "Pow") {
296        Ok((a, b)) => Value::Number(a.powf(b)),
297        Err(e) => e,
298    }
299}
300
301fn math_sin(args: &[Value]) -> Value { math_num1(args, "Sin").map(|n| Value::Number(n.sin())).unwrap_or_else(|e| e) }
302fn math_cos(args: &[Value]) -> Value { math_num1(args, "Cos").map(|n| Value::Number(n.cos())).unwrap_or_else(|e| e) }
303fn math_tan(args: &[Value]) -> Value { math_num1(args, "Tan").map(|n| Value::Number(n.tan())).unwrap_or_else(|e| e) }
304fn math_asin(args: &[Value]) -> Value { math_num1(args, "Asin").map(|n| Value::Number(n.asin())).unwrap_or_else(|e| e) }
305fn math_acos(args: &[Value]) -> Value { math_num1(args, "Acos").map(|n| Value::Number(n.acos())).unwrap_or_else(|e| e) }
306fn math_atan(args: &[Value]) -> Value { math_num1(args, "Atan").map(|n| Value::Number(n.atan())).unwrap_or_else(|e| e) }
307fn math_atan2(args: &[Value]) -> Value {
308    match math_num2(args, "Atan2") {
309        Ok((y, x)) => Value::Number(y.atan2(x)),
310        Err(e) => e,
311    }
312}
313
314fn math_exp(args: &[Value]) -> Value { math_num1(args, "Exp").map(|n| Value::Number(n.exp())).unwrap_or_else(|e| e) }
315
316fn math_ln(args: &[Value]) -> Value {
317    if args.is_empty() || args.len() > 2 {
318        return Value::Error(FsError { code: 1, message: "Ln: Expecting 1 or 2 parameters".to_string(), line: -1, column: -1 });
319    }
320    let v = match math_num1(&args[0..1], "Ln") {
321        Ok(v) => v,
322        Err(e) => return e,
323    };
324    if v <= 0.0 {
325        return Value::Error(FsError { code: 2, message: "Ln: value must be greater than 0.".to_string(), line: -1, column: -1 });
326    }
327    if args.len() == 1 {
328        return Value::Number(v.ln());
329    }
330    let base = match math_num1(&args[1..2], "Ln") {
331        Ok(v) => v,
332        Err(e) => return e,
333    };
334    if base <= 0.0 || (base - 1.0).abs() < f64::EPSILON {
335        return Value::Error(FsError { code: 2, message: "Ln: base must be greater than 0 and not equal to 1.".to_string(), line: -1, column: -1 });
336    }
337    Value::Number(v.log(base))
338}
339
340fn math_log10(args: &[Value]) -> Value {
341    match math_num1(args, "Log10") {
342        Ok(v) if v > 0.0 => Value::Number(v.log10()),
343        Ok(_) => Value::Error(FsError { code: 2, message: "Log10: value must be greater than 0.".to_string(), line: -1, column: -1 }),
344        Err(e) => e,
345    }
346}
347
348fn math_log2(args: &[Value]) -> Value {
349    match math_num1(args, "Log2") {
350        Ok(v) if v > 0.0 => Value::Number(v.log2()),
351        Ok(_) => Value::Error(FsError { code: 2, message: "Log2: value must be greater than 0.".to_string(), line: -1, column: -1 }),
352        Err(e) => e,
353    }
354}
355
356fn math_ceil(args: &[Value]) -> Value { math_num1(args, "Ceiling").map(|n| Value::Number(n.ceil())).unwrap_or_else(|e| e) }
357fn math_floor(args: &[Value]) -> Value { math_num1(args, "Floor").map(|n| Value::Number(n.floor())).unwrap_or_else(|e| e) }
358fn math_round(args: &[Value]) -> Value { math_num1(args, "Round").map(|n| Value::Number(n.round())).unwrap_or_else(|e| e) }
359fn math_trunc(args: &[Value]) -> Value { math_num1(args, "Trunc").map(|n| Value::Number(n.trunc())).unwrap_or_else(|e| e) }
360fn math_sign(args: &[Value]) -> Value { math_num1(args, "Sign").map(|n| Value::Int(n.signum() as i64)).unwrap_or_else(|e| e) }
361
362fn math_clamp(args: &[Value]) -> Value {
363    if args.len() != 3 {
364        return Value::Error(FsError { code: 1, message: "Clamp: Expected 3 parameters".to_string(), line: -1, column: -1 });
365    }
366    let x = match math_num1(&args[0..1], "Clamp") { Ok(v) => v, Err(e) => return e };
367    let lo = match math_num1(&args[1..2], "Clamp") { Ok(v) => v, Err(e) => return e };
368    let hi = match math_num1(&args[2..3], "Clamp") { Ok(v) => v, Err(e) => return e };
369    Value::Number(x.clamp(lo, hi))
370}
371
372fn math_random(args: &[Value]) -> Value {
373    // Deterministic seed → [0,1)
374    let seed = match args.get(0) {
375        None => 0.0,
376        Some(v) => match math_num1(std::slice::from_ref(v), "Random") {
377            Ok(x) => x,
378            Err(e) => return e,
379        },
380    };
381    let mut x = seed.to_bits() ^ 0x9E37_79B9_7F4A_7C15u64;
382    x ^= x >> 12;
383    x ^= x << 25;
384    x ^= x >> 27;
385    let y = x.wrapping_mul(0x2545_F491_4F6C_DD1D);
386    let u = (y >> 11) as f64 / ((1u64 << 53) as f64);
387    Value::Number(u)
388}
389
390fn math_cbrt(args: &[Value]) -> Value { math_num1(args, "Cbrt").map(|n| Value::Number(n.cbrt())).unwrap_or_else(|e| e) }
391fn math_deg_to_rad(args: &[Value]) -> Value { math_num1(args, "DegToRad").map(|n| Value::Number(n.to_radians())).unwrap_or_else(|e| e) }
392fn math_rad_to_deg(args: &[Value]) -> Value { math_num1(args, "RadToDeg").map(|n| Value::Number(n.to_degrees())).unwrap_or_else(|e| e) }
393
394fn float_is_nan(args: &[Value]) -> Value { math_num1(args, "IsNaN").map(|n| Value::Bool(n.is_nan())).unwrap_or_else(|e| e) }
395fn float_is_infinity(args: &[Value]) -> Value { math_num1(args, "IsInfinity").map(|n| Value::Bool(n.is_infinite())).unwrap_or_else(|e| e) }
396fn float_is_normal(args: &[Value]) -> Value { math_num1(args, "IsNormal").map(|n| Value::Bool(n.is_normal())).unwrap_or_else(|e| e) }
397
398fn text_regex(args: &[Value]) -> Value {
399    if args.len() < 2 || args.len() > 3 {
400        return Value::Error(FsError { code: 1, message: "regex: two or three parameters expected".to_string(), line: -1, column: -1 });
401    }
402    let text = match &args[0] {
403        Value::Error(e) => return Value::Error(e.clone()),
404        Value::Obj(o) => match &**o {
405            Obj::String(s) => s.clone(),
406            _ => return Value::Error(FsError { code: 2, message: "regex: text parameter must be string".to_string(), line: -1, column: -1 }),
407        },
408        _ => return Value::Error(FsError { code: 2, message: "regex: text parameter must be string".to_string(), line: -1, column: -1 }),
409    };
410    let pattern = match &args[1] {
411        Value::Error(e) => return Value::Error(e.clone()),
412        Value::Obj(o) => match &**o {
413            Obj::String(s) => s.clone(),
414            _ => return Value::Error(FsError { code: 2, message: "regex: pattern parameter must be string".to_string(), line: -1, column: -1 }),
415        },
416        _ => return Value::Error(FsError { code: 2, message: "regex: pattern parameter must be string".to_string(), line: -1, column: -1 }),
417    };
418    let flags = if args.len() == 3 {
419        match &args[2] {
420            Value::Nil => None,
421            Value::Error(e) => return Value::Error(e.clone()),
422            Value::Obj(o) => match &**o {
423                Obj::String(s) => Some(s.clone()),
424                _ => return Value::Error(FsError { code: 2, message: "regex: flags parameter must be string".to_string(), line: -1, column: -1 }),
425            },
426            _ => return Value::Error(FsError { code: 2, message: "regex: flags parameter must be string".to_string(), line: -1, column: -1 }),
427        }
428    } else {
429        None
430    };
431
432    let mut prefix = String::new();
433    if let Some(f) = flags {
434        for ch in f.chars() {
435            if ch.is_whitespace() || ch == ',' || ch == '|' { continue; }
436            match ch.to_ascii_lowercase() {
437                'i' => prefix.push_str("(?i)"),
438                'm' => prefix.push_str("(?m)"),
439                's' => prefix.push_str("(?s)"),
440                'x' => prefix.push_str("(?x)"),
441                other => {
442                    return Value::Error(FsError { code: 1, message: format!("regex: unsupported regex option '{other}'"), line: -1, column: -1 });
443                }
444            }
445        }
446    }
447
448    let pat = format!("{prefix}{pattern}");
449    match Regex::new(&pat) {
450        Ok(re) => Value::Bool(re.is_match(&text)),
451        Err(e) => Value::Error(FsError { code: 1, message: format!("regex: invalid pattern: {e}"), line: -1, column: -1 }),
452    }
453}
454
455fn text_parse(args: &[Value]) -> Value {
456    if args.is_empty() {
457        return Value::Error(FsError { code: 1, message: "parse requires at least one parameter".to_string(), line: -1, column: -1 });
458    }
459    if let Value::Error(e) = &args[0] { return Value::Error(e.clone()); }
460    if matches!(args[0], Value::Nil) { return Value::Nil; }
461    let s = args[0].to_string();
462    let fmt = if args.len() > 1 {
463        if let Value::Error(e) = &args[1] { return Value::Error(e.clone()); }
464        if matches!(args[1], Value::Nil) { None } else { Some(args[1].to_string()) }
465    } else {
466        None
467    };
468    let fmt = fmt.unwrap_or_default();
469    if fmt.trim().is_empty() {
470        return Value::Obj(Rc::new(Obj::String(s)));
471    }
472    match fmt.to_lowercase().as_str() {
473        "hex" => {
474            let t = s.trim();
475            let t = t.strip_prefix("0x").unwrap_or(t);
476            match i64::from_str_radix(t, 16) {
477                Ok(v) => Value::Int(v),
478                Err(_) => Value::Error(FsError { code: 1, message: "parse: invalid hex".to_string(), line: -1, column: -1 }),
479            }
480        }
481        "l" => match s.trim().parse::<i64>() {
482            Ok(v) => Value::Int(v),
483            Err(_) => Value::Error(FsError { code: 1, message: "parse: invalid int64".to_string(), line: -1, column: -1 }),
484        },
485        "fs" => {
486            let mut vm = crate::vm::VM::new();
487            match vm.interpret(&s) {
488                Ok(v) => v,
489                Err(e) => {
490                    let err = match e {
491                        crate::vm::InterpretResult::CompileError(err) => err,
492                        crate::vm::InterpretResult::RuntimeError(err) => err,
493                    };
494                    Value::Error(err)
495                }
496            }
497        }
498        _ => Value::Obj(Rc::new(Obj::String(s))),
499    }
500}
501
502fn text_format(args: &[Value]) -> Value {
503    if args.is_empty() {
504        return Value::Error(FsError { code: 1, message: "format requires at least one parameter.".to_string(), line: -1, column: -1 });
505    }
506    let value = &args[0];
507    if let Value::Error(e) = value { return Value::Error(e.clone()); }
508    let fmt = if args.len() > 1 {
509        if let Value::Error(e) = &args[1] { return Value::Error(e.clone()); }
510        match &args[1] {
511            Value::Nil => None,
512            Value::Obj(o) => match &**o {
513                Obj::String(s) => Some(s.clone()),
514                _ => Some(args[1].to_string()),
515            },
516            _ => Some(args[1].to_string()),
517        }
518    } else {
519        None
520    };
521    if let Some(f) = fmt {
522        if f.eq_ignore_ascii_case("json") {
523            // Best-effort JSON formatting (does not force-evaluate lazy KVC entries).
524            let json = format_json_value(value);
525            return Value::Obj(Rc::new(Obj::String(json)));
526        }
527        // For now, non-json uses Display formatting (Rust core doesn't implement .NET format patterns yet).
528    }
529    Value::Obj(Rc::new(Obj::String(value.to_string())))
530}
531
532fn format_json_escape(s: &str) -> String {
533    let mut out = String::with_capacity(s.len() + 2);
534    for ch in s.chars() {
535        match ch {
536            '\\' => out.push_str("\\\\"),
537            '"' => out.push_str("\\\""),
538            '\n' => out.push_str("\\n"),
539            '\r' => out.push_str("\\r"),
540            '\t' => out.push_str("\\t"),
541            c if c.is_control() => out.push_str(&format!("\\u{:04x}", c as u32)),
542            c => out.push(c),
543        }
544    }
545    out
546}
547
548fn format_json_value(v: &Value) -> String {
549    match v {
550        Value::Nil => "null".to_string(),
551        Value::Bool(b) => if *b { "true".to_string() } else { "false".to_string() },
552        Value::Int(n) => n.to_string(),
553        Value::BigInt(n) => n.to_string(),
554        Value::Number(n) => if n.is_finite() { n.to_string() } else { "null".to_string() },
555        Value::Error(e) => format!("{{\"kind\":\"value\",\"code\":{},\"message\":\"{}\",\"line\":{},\"column\":{}}}",
556            e.code, format_json_escape(&e.message), e.line, e.column),
557        Value::Obj(o) => match &**o {
558            Obj::String(s) => format!("\"{}\"", format_json_escape(s)),
559            Obj::List(items) => {
560                let parts: Vec<String> = items.iter().map(format_json_value).collect();
561                format!("[{}]", parts.join(","))
562            }
563            Obj::Range(r) => format!("{{\"type\":\"range\",\"start\":{},\"count\":{}}}", r.start, r.count),
564            Obj::Bytes(b) => format!("{{\"type\":\"bytes\",\"base64\":\"{}\"}}", format_json_escape(&general_purpose::STANDARD.encode(b))),
565            Obj::Guid(g) => format!("{{\"type\":\"guid\",\"value\":\"{}\"}}", format_json_escape(&g.to_string())),
566            Obj::DateTimeTicks(t) => format!("{{\"type\":\"datetime\",\"ticks\":{}}}", t),
567            Obj::Kvc(k) => {
568                let b = k.borrow();
569                let mut parts: Vec<String> = Vec::new();
570                for key_l in b.order.iter() {
571                    let display = b.display_names.get(key_l).cloned().unwrap_or_else(|| key_l.clone());
572                    let val = b.cache.get(key_l).cloned().unwrap_or(Value::Nil);
573                    parts.push(format!("\"{}\":{}", format_json_escape(&display), format_json_value(&val)));
574                }
575                format!("{{{}}}", parts.join(","))
576            }
577            Obj::Provider(p) => format_json_value(&p.current),
578            Obj::Function(f) => format!("{{\"type\":\"function\",\"name\":\"{}\",\"arity\":{}}}", format_json_escape(&f.name), f.arity),
579            Obj::NativeFn(_) => "{\"type\":\"native\"}".to_string(),
580        }
581    }
582}
583
584fn text_templatemerge(args: &[Value]) -> Value {
585    fn push_val(out: &mut String, v: &Value) {
586        match v {
587            Value::Nil => {}
588            Value::Obj(o) => match &**o {
589                Obj::List(items) => for it in items { push_val(out, it); }
590                Obj::Range(r) => for i in 0..r.count { out.push_str(&(r.start + i as i64).to_string()); }
591                Obj::String(s) => out.push_str(s),
592                _ => out.push_str(&v.to_string()),
593            },
594            _ => out.push_str(&v.to_string()),
595        }
596    }
597    let mut out = String::new();
598    for v in args {
599        if let Value::Error(e) = v { return Value::Error(e.clone()); }
600        push_val(&mut out, v);
601    }
602    Value::Obj(Rc::new(Obj::String(out)))
603}
604
605fn html_encode(args: &[Value]) -> Value {
606    if args.is_empty() { return Value::Nil; }
607    if let Value::Error(e) = &args[0] { return Value::Error(e.clone()); }
608    if matches!(args[0], Value::Nil) { return Value::Nil; }
609    let s = args[0].to_string();
610    let mut out = String::with_capacity(s.len());
611    for ch in s.chars() {
612        match ch {
613            '&' => out.push_str("&amp;"),
614            '<' => out.push_str("&lt;"),
615            '>' => out.push_str("&gt;"),
616            '"' => out.push_str("&quot;"),
617            '\'' => out.push_str("&#39;"),
618            _ => out.push(ch),
619        }
620    }
621    Value::Obj(Rc::new(Obj::String(out)))
622}
623
624fn misc_error(args: &[Value]) -> Value {
625    if args.is_empty() || args.len() > 2 {
626        return Value::Error(FsError { code: 1, message: "error: message and optional type expected".to_string(), line: -1, column: -1 });
627    }
628    let msg = match &args[0] {
629        Value::Error(e) => return Value::Error(e.clone()),
630        Value::Obj(o) => match &**o {
631            Obj::String(s) => s.clone(),
632            _ => return Value::Error(FsError { code: 2, message: "error: message must be a string".to_string(), line: -1, column: -1 }),
633        },
634        _ => return Value::Error(FsError { code: 2, message: "error: message must be a string".to_string(), line: -1, column: -1 }),
635    };
636    let typ = if args.len() == 2 {
637        match &args[1] {
638            Value::Nil => None,
639            Value::Error(e) => return Value::Error(e.clone()),
640            Value::Obj(o) => match &**o {
641                Obj::String(s) => Some(s.clone()),
642                _ => return Value::Error(FsError { code: 2, message: "error: optional type must be a string".to_string(), line: -1, column: -1 }),
643            },
644            _ => return Value::Error(FsError { code: 2, message: "error: optional type must be a string".to_string(), line: -1, column: -1 }),
645        }
646    } else { None };
647    let message = if let Some(t) = typ { format!("{t}: {msg}") } else { msg };
648    Value::Error(FsError { code: 3000, message, line: -1, column: -1 })
649}
650
651fn misc_log(args: &[Value]) -> Value {
652    if args.is_empty() {
653        return Value::Error(FsError { code: 1, message: "log: value expected".to_string(), line: -1, column: -1 });
654    }
655    if args.len() > 2 {
656        return Value::Error(FsError { code: 1, message: "log: invalid parameter count".to_string(), line: -1, column: -1 });
657    }
658    if let Value::Error(e) = &args[0] { return Value::Error(e.clone()); }
659    if args.len() == 1 {
660        host::log_line(&format_json_value(&args[0]));
661        return args[0].clone();
662    }
663    if let Value::Error(e) = &args[1] { return Value::Error(e.clone()); }
664    match &args[1] {
665        Value::Obj(o) => match &**o {
666            Obj::Function(_) | Obj::NativeFn(_) => {
667                host::log_line("<handler>");
668            }
669            _ => host::log_line(&args[1].to_string()),
670        },
671        _ => host::log_line(&args[1].to_string()),
672    }
673    args[0].clone()
674}
675
676fn os_file_text(args: &[Value]) -> Value {
677    if args.len() != 1 {
678        return Value::Error(FsError { code: 1, message: "file: invalid parameter count. 1 expected".to_string(), line: -1, column: -1 });
679    }
680    if let Value::Error(e) = &args[0] { return Value::Error(e.clone()); }
681    if matches!(args[0], Value::Nil) { return Value::Nil; }
682    let path = match &args[0] {
683        Value::Obj(o) => match &**o {
684            Obj::String(s) => s.clone(),
685            _ => return Value::Error(FsError { code: 2, message: "file: expected string".to_string(), line: -1, column: -1 }),
686        },
687        _ => return Value::Error(FsError { code: 2, message: "file: expected string".to_string(), line: -1, column: -1 }),
688    };
689    match host::file_read_text(&path) {
690        Ok(s) => Value::Obj(Rc::new(Obj::String(s))),
691        Err(e) => Value::Error(e),
692    }
693}
694
695fn os_file_exists(args: &[Value]) -> Value {
696    if args.len() != 1 {
697        return Value::Error(FsError { code: 1, message: "fileexists: invalid parameter count. 1 expected".to_string(), line: -1, column: -1 });
698    }
699    let path = match &args[0] {
700        Value::Obj(o) => match &**o {
701            Obj::String(s) => s.as_str(),
702            _ => return Value::Error(FsError { code: 2, message: "fileexists: expected a string".to_string(), line: -1, column: -1 }),
703        },
704        _ => return Value::Error(FsError { code: 2, message: "fileexists: expected a string".to_string(), line: -1, column: -1 }),
705    };
706    match host::file_exists(path) {
707        Ok(b) => Value::Bool(b),
708        Err(e) => Value::Error(e),
709    }
710}
711
712fn os_is_file(args: &[Value]) -> Value {
713    if args.len() != 1 {
714        return Value::Error(FsError { code: 1, message: "isfile: invalid parameter count. 1 expected".to_string(), line: -1, column: -1 });
715    }
716    let path = match &args[0] {
717        Value::Obj(o) => match &**o {
718            Obj::String(s) => s.as_str(),
719            _ => return Value::Error(FsError { code: 2, message: "isfile: expected a string".to_string(), line: -1, column: -1 }),
720        },
721        _ => return Value::Error(FsError { code: 2, message: "isfile: expected a string".to_string(), line: -1, column: -1 }),
722    };
723    match host::is_file(path) {
724        Ok(b) => Value::Bool(b),
725        Err(e) => Value::Error(e),
726    }
727}
728
729fn os_dir_list(args: &[Value]) -> Value {
730    if args.len() != 1 {
731        return Value::Error(FsError { code: 1, message: "dirlist: invalid parameter count. 1 expected".to_string(), line: -1, column: -1 });
732    }
733    let path = match &args[0] {
734        Value::Obj(o) => match &**o {
735            Obj::String(s) => s.clone(),
736            _ => return Value::Error(FsError { code: 2, message: "dirlist: expected a string".to_string(), line: -1, column: -1 }),
737        },
738        _ => return Value::Error(FsError { code: 2, message: "dirlist: expected a string".to_string(), line: -1, column: -1 }),
739    };
740    match host::dir_list(&path) {
741        Ok(entries) => {
742            let out: Vec<Value> = entries.into_iter().map(|s| Value::Obj(Rc::new(Obj::String(s)))).collect();
743            Value::Obj(Rc::new(Obj::List(out)))
744        }
745        Err(e) => Value::Error(e),
746    }
747}
748
749fn fs_len(args: &[Value]) -> Value {
750    if args.len() != 1 { return Value::Nil; }
751    if let Value::Error(e) = &args[0] {
752        return Value::Error(e.clone());
753    }
754    match &args[0] {
755        Value::Obj(o) => match &**o {
756            Obj::String(s) => Value::Int(s.len() as i64),
757            Obj::List(l) => Value::Int(l.len() as i64),
758            Obj::Range(r) => Value::Int(r.count as i64),
759            Obj::Bytes(b) => Value::Int(b.len() as i64),
760            Obj::Kvc(k) => Value::Int(k.borrow().order.len() as i64),
761            _ => Value::Nil,
762        },
763        _ => Value::Nil,
764    }
765}
766
767fn fs_first(args: &[Value]) -> Value {
768    if args.len() != 1 { return Value::Nil; }
769    if let Value::Error(e) = &args[0] {
770        return Value::Error(e.clone());
771    }
772    match &args[0] {
773        Value::Obj(o) => match &**o {
774             Obj::List(l) => l.first().cloned().unwrap_or(Value::Nil),
775             Obj::Range(r) => {
776                 if r.count == 0 { Value::Nil } else { Value::Int(r.start) }
777             }
778             Obj::Bytes(b) => {
779                 if b.is_empty() { Value::Nil } else { Value::Int(b[0] as i64) }
780             }
781             Obj::String(s) => if !s.is_empty() { 
782                 Value::Obj(Rc::new(Obj::String(s[0..1].to_string()))) 
783             } else { Value::Nil },
784             _ => Value::Nil,
785        },
786        _ => Value::Nil,
787    }
788}
789
790fn as_i64_exact(v: &Value) -> Option<i64> {
791    match v {
792        Value::Int(n) => Some(*n),
793        Value::Number(n)
794            if n.is_finite()
795                && n.fract() == 0.0
796                && *n >= (i64::MIN as f64)
797                && *n <= (i64::MAX as f64) =>
798        {
799            Some(*n as i64)
800        }
801        Value::BigInt(n) => n.to_i64(),
802        _ => None,
803    }
804}
805
806fn as_usize_exact(v: &Value) -> Option<usize> {
807    match v {
808        Value::Int(n) if *n >= 0 => (*n as u64).try_into().ok(),
809        Value::Number(n)
810            if n.is_finite() && n.fract() == 0.0 && *n >= 0.0 && *n <= (usize::MAX as f64) =>
811        {
812            Some(*n as usize)
813        }
814        Value::BigInt(n) => n.to_u64().and_then(|u| u.try_into().ok()),
815        _ => None,
816    }
817}
818
819fn fs_range(args: &[Value]) -> Value {
820    if args.len() != 2 { return Value::Nil; } 
821    let start_i = match as_i64_exact(&args[0]) {
822        Some(n) => n,
823        None => return Value::Nil,
824    };
825    if matches!(&args[1], Value::Int(n) if *n < 0)
826        || matches!(&args[1], Value::Number(n) if n.is_finite() && *n < 0.0)
827        || matches!(&args[1], Value::BigInt(n) if n.sign() == num_bigint::Sign::Minus)
828    {
829        return Value::Error(FsError { code: 1, message: "Range: count must be >= 0".to_string(), line: -1, column: -1 });
830    }
831    let count = match as_usize_exact(&args[1]) {
832        Some(n) => n,
833        None => return Value::Nil,
834    };
835
836    if count == 0 {
837        return Value::Obj(Rc::new(Obj::Range(crate::obj::RangeObject { start: start_i, count: 0 })));
838    }
839
840    if start_i.checked_add((count - 1) as i64).is_none() {
841        return Value::Error(FsError { code: 1, message: "Range: overflow".to_string(), line: -1, column: -1 });
842    }
843
844    Value::Obj(Rc::new(Obj::Range(crate::obj::RangeObject { start: start_i, count })))
845}
846
847fn fs_and(args: &[Value]) -> Value {
848    
849    let mut has_bool = false;
850    for v in args {
851        match v {
852            Value::Nil => continue,
853            Value::Error(e) => return Value::Error(e.clone()),
854            Value::Bool(b) => {
855                has_bool = true;
856                if !*b { return Value::Bool(false); }
857            }
858            _ => return Value::Error(FsError { code: 2, message: "and doesn't apply to this type".to_string(), line: -1, column: -1 }),
859        }
860    }
861    if !has_bool { Value::Nil } else { Value::Bool(true) }
862}
863
864fn fs_or(args: &[Value]) -> Value {
865    
866    let mut first_error: Option<FsError> = None;
867    let mut has_bool = false;
868    for v in args {
869        match v {
870            Value::Nil => continue,
871            Value::Error(e) => {
872                if first_error.is_none() {
873                    first_error = Some(e.clone());
874                }
875            }
876            Value::Bool(b) => {
877                has_bool = true;
878                if *b { return Value::Bool(true); }
879            }
880            _ => return Value::Error(FsError { code: 2, message: "or doesn't apply to this type".to_string(), line: -1, column: -1 }),
881        }
882    }
883    if let Some(e) = first_error { return Value::Error(e); }
884    if !has_bool { Value::Nil } else { Value::Bool(false) }
885}
886
887fn fs_in(args: &[Value]) -> Value {
888    if args.len() != 2 {
889        return Value::Error(FsError { code: 3, message: "in: invalid parameter count".to_string(), line: -1, column: -1 });
890    }
891    let needle = &args[0];
892    let hay = &args[1];
893    if let Value::Error(e) = needle {
894        return Value::Error(e.clone());
895    }
896    if let Value::Error(e) = hay {
897        return Value::Error(e.clone());
898    }
899    if matches!(hay, Value::Nil) {
900        return Value::Nil;
901    }
902  
903    if matches!(needle, Value::Nil) {
904        return Value::Bool(false);
905    }
906    let list = match hay {
907        Value::Obj(o) => match &**o {
908            Obj::List(items) => items,
909            Obj::Range(r) => {
910               
911                let needle_i = match as_i64_exact(needle) {
912                    Some(n) => n,
913                    None => return Value::Bool(false),
914                };
915                if needle_i < r.start || needle_i >= r.start + (r.count as i64) {
916                    return Value::Bool(false);
917                }
918                return Value::Bool(true);
919            }
920            _ => return Value::Error(FsError { code: 2, message: "in: list expected".to_string(), line: -1, column: -1 }),
921        },
922        _ => return Value::Error(FsError { code: 2, message: "in: list expected".to_string(), line: -1, column: -1 }),
923    };
924    for v in list.iter() {
925        if matches!(v, Value::Nil) {
926            continue;
927        }
928        
929        if needle == v { return Value::Bool(true); }
930    }
931    Value::Bool(false)
932}
933
934fn fs_template_merge(args: &[Value]) -> Value {
935    let mut out = String::new();
936    for v in args {
937        match v {
938            Value::Obj(o) => match &**o {
939                Obj::String(s) => out.push_str(s),
940                _ => out.push_str(&v.to_string()),
941            },
942            _ => out.push_str(&v.to_string()),
943        }
944    }
945    Value::Obj(Rc::new(Obj::String(out)))
946}
947
948fn fs_sum(args: &[Value]) -> Value {
949    if args.len() != 1 {
950        return Value::Nil;
951    }
952    if let Value::Error(e) = &args[0] {
953        return Value::Error(e.clone());
954    }
955    if matches!(args[0], Value::Nil) {
956        return Value::Nil;
957    }
958
959    fn err(msg: &str) -> Value {
960        Value::Error(FsError { code: 4, message: msg.to_string(), line: -1, column: -1 })
961    }
962
963    match &args[0] {
964        Value::Obj(o) => match &**o {
965            Obj::Range(r) => {
966                if r.count == 0 {
967                    return Value::Int(0);
968                }
969                let n = BigInt::from(r.count as u64);
970                let a0 = BigInt::from(r.start);
971                let a_last = BigInt::from(r.start + (r.count as i64) - 1);
972                let sum = (n.clone() * (a0 + a_last)) / BigInt::from(2);
973                if let Some(v) = sum.to_i64() { Value::Int(v) } else { Value::BigInt(sum) }
974            }
975            Obj::List(items) => {
976                let mut sum_i = BigInt::from(0);
977                let mut sum_f: Option<f64> = None;
978                for v in items.iter() {
979                    match v {
980                        Value::Int(n) => {
981                            if let Some(sf) = sum_f.as_mut() {
982                                *sf += *n as f64;
983                            } else {
984                                sum_i += BigInt::from(*n);
985                            }
986                        }
987                        Value::BigInt(n) => {
988                            if let Some(sf) = sum_f.as_mut() {
989                                let nf = match n.to_f64() {
990                                    Some(x) => x,
991                                    None => return err("Sum: bigint too large for float sum"),
992                                };
993                                *sf += nf;
994                            } else {
995                                sum_i += n.clone();
996                            }
997                        }
998                        Value::Number(n) if n.is_finite() => {
999                            let mut sf = sum_f.unwrap_or_else(|| sum_i.to_f64().unwrap_or(0.0));
1000                            sf += *n;
1001                            sum_f = Some(sf);
1002                        }
1003                        Value::Nil => {}
1004                        Value::Error(e) => return Value::Error(e.clone()),
1005                        _ => return err("Sum: expects list/range of numbers"),
1006                    }
1007                }
1008                if let Some(sf) = sum_f {
1009                    Value::Number(sf)
1010                } else if let Some(v) = sum_i.to_i64() {
1011                    Value::Int(v)
1012                } else {
1013                    Value::BigInt(sum_i)
1014                }
1015            }
1016            _ => Value::Nil,
1017        },
1018        _ => Value::Nil,
1019    }
1020}
1021
1022fn fs_sum_approx(args: &[Value]) -> Value {
1023    if args.len() != 1 {
1024        return Value::Nil;
1025    }
1026    if let Value::Error(e) = &args[0] {
1027        return Value::Error(e.clone());
1028    }
1029    if matches!(args[0], Value::Nil) {
1030        return Value::Nil;
1031    }
1032
1033    match &args[0] {
1034        Value::Obj(o) => match &**o {
1035            Obj::Range(r) => {
1036                let n = r.count as f64;
1037                if n == 0.0 {
1038                    return Value::Number(0.0);
1039                }
1040                let a0 = r.start as f64;
1041                let a_last = a0 + (n - 1.0);
1042                Value::Number(n * (a0 + a_last) / 2.0)
1043            }
1044            Obj::List(items) => {
1045                let mut sum = 0.0f64;
1046                for v in items.iter() {
1047                    match v {
1048                        Value::Int(n) => sum += *n as f64,
1049                        Value::BigInt(n) => {
1050                            let nf = match n.to_f64() {
1051                                Some(x) => x,
1052                                None => return Value::Error(FsError { code: 4, message: "SumApprox: bigint too large".to_string(), line: -1, column: -1 }),
1053                            };
1054                            sum += nf;
1055                        }
1056                        Value::Number(n) if n.is_finite() => sum += *n,
1057                        Value::Nil => {}
1058                        Value::Error(e) => return Value::Error(e.clone()),
1059                        _ => return Value::Error(FsError { code: 4, message: "SumApprox: expects list/range of numbers".to_string(), line: -1, column: -1 }),
1060                    }
1061                }
1062                Value::Number(sum)
1063            }
1064            _ => Value::Nil,
1065        },
1066        _ => Value::Nil,
1067    }
1068}
1069
1070fn fs_guid(args: &[Value]) -> Value {
1071    if args.len() != 1 { return Value::Nil; }
1072    if let Value::Error(e) = &args[0] { return Value::Error(e.clone()); }
1073    if matches!(args[0], Value::Nil) { return Value::Nil; }
1074    let s = match &args[0] {
1075        Value::Obj(o) => match &**o {
1076            Obj::String(s) => s.as_str(),
1077            _ => return Value::Error(FsError { code: 2, message: "guid: string expected".to_string(), line: -1, column: -1 }),
1078        },
1079        _ => return Value::Error(FsError { code: 2, message: "guid: string expected".to_string(), line: -1, column: -1 }),
1080    };
1081    match Uuid::parse_str(s) {
1082        Ok(u) => Value::Obj(Rc::new(Obj::Guid(u))),
1083        Err(_) => Value::Error(FsError { code: 1, message: format!("guid: '{s}' is not a valid GUID"), line: -1, column: -1 }),
1084    }
1085}
1086
1087fn fs_ticks_to_date(args: &[Value]) -> Value {
1088    if args.len() > 1 { return Value::Error(FsError { code: 1, message: "TicksToDate: invalid parameter count".to_string(), line: -1, column: -1 }); }
1089    if args.is_empty() { return Value::Nil; }
1090    if let Value::Error(e) = &args[0] { return Value::Error(e.clone()); }
1091    if matches!(args[0], Value::Nil) { return Value::Nil; }
1092    let ticks = match as_i64_exact(&args[0]) {
1093        Some(t) => t,
1094        None => return Value::Error(FsError { code: 2, message: "TicksToDate: integer ticks expected".to_string(), line: -1, column: -1 }),
1095    };
1096    Value::Obj(Rc::new(Obj::DateTimeTicks(ticks)))
1097}
1098
1099fn fs_date(args: &[Value]) -> Value {
1100    if args.is_empty() || args.len() > 2 {
1101        return Value::Error(FsError { code: 1, message: "Date: invalid parameter count".to_string(), line: -1, column: -1 });
1102    }
1103    if let Value::Error(e) = &args[0] { return Value::Error(e.clone()); }
1104    if matches!(args[0], Value::Nil) { return Value::Nil; }
1105    let s = match &args[0] {
1106        Value::Obj(o) => match &**o {
1107            Obj::String(s) => s.clone(),
1108            _ => return Value::Error(FsError { code: 2, message: "Date: string expected".to_string(), line: -1, column: -1 }),
1109        },
1110        _ => return Value::Error(FsError { code: 2, message: "Date: string expected".to_string(), line: -1, column: -1 }),
1111    };
1112    let format = if args.len() == 2 {
1113        match &args[1] {
1114            Value::Nil => None,
1115            Value::Error(e) => return Value::Error(e.clone()),
1116            Value::Obj(o) => match &**o {
1117                Obj::String(f) => Some(f.clone()),
1118                _ => return Value::Error(FsError { code: 2, message: "Date: format must be a string".to_string(), line: -1, column: -1 }),
1119            },
1120            _ => return Value::Error(FsError { code: 2, message: "Date: format must be a string".to_string(), line: -1, column: -1 }),
1121        }
1122    } else {
1123        None
1124    };
1125
1126    const UNIX_EPOCH_TICKS: i64 = 621_355_968_000_000_000;
1127    const TICKS_PER_SEC: i64 = 10_000_000;
1128
1129    let parse_iso = || -> Option<i64> {
1130        let dt = time::OffsetDateTime::parse(&s, &time::format_description::well_known::Rfc3339).ok()?;
1131        let unix_seconds = dt.unix_timestamp();
1132        let nanos = dt.nanosecond() as i64;
1133        let ticks = UNIX_EPOCH_TICKS
1134            .checked_add(unix_seconds.checked_mul(TICKS_PER_SEC)?)
1135            .and_then(|base| base.checked_add(nanos / 100))?;
1136        Some(ticks)
1137    };
1138
1139    let ticks = match format.as_deref() {
1140        None | Some("") => parse_iso(),
1141        Some("o") | Some("O") => parse_iso(),
1142        Some("yyyy-MM-dd") => {
1143            time::Date::parse(&s, &time::macros::format_description!("[year]-[month]-[day]"))
1144                .ok()
1145                .map(|d| {
1146                    let dt = d.with_time(time::Time::MIDNIGHT).assume_utc();
1147                    let unix_seconds = dt.unix_timestamp();
1148                    UNIX_EPOCH_TICKS + unix_seconds * TICKS_PER_SEC
1149                })
1150        }
1151        _ => None,
1152    };
1153
1154    match ticks {
1155        Some(t) => Value::Obj(Rc::new(Obj::DateTimeTicks(t))),
1156        None => Value::Error(FsError { code: 1, message: format!("Date: String '{s}' can't be converted to date"), line: -1, column: -1 }),
1157    }
1158}
1159
1160fn fs_change_type(args: &[Value]) -> Value {
1161    if args.len() != 2 {
1162        return Value::Error(FsError { code: 1, message: "ChangeType: invalid parameter count".to_string(), line: -1, column: -1 });
1163    }
1164    if let Value::Error(e) = &args[0] { return Value::Error(e.clone()); }
1165    if matches!(args[0], Value::Nil) { return Value::Nil; }
1166
1167    let type_name = match &args[1] {
1168        Value::Error(e) => return Value::Error(e.clone()),
1169        Value::Obj(o) => match &**o {
1170            Obj::String(s) if !s.trim().is_empty() => s.trim().to_string(),
1171            _ => return Value::Error(FsError { code: 2, message: "ChangeType: Type name must be a string.".to_string(), line: -1, column: -1 }),
1172        },
1173        _ => return Value::Error(FsError { code: 2, message: "ChangeType: Type name must be a string.".to_string(), line: -1, column: -1 }),
1174    };
1175    let tn = type_name.to_lowercase();
1176
1177    match tn.as_str() {
1178        "string" => {
1179            match &args[0] {
1180                Value::Obj(o) => match &**o {
1181                    Obj::Bytes(b) => {
1182                        let s = general_purpose::STANDARD.encode(b);
1183                        Value::Obj(Rc::new(Obj::String(s)))
1184                    }
1185                    Obj::Guid(g) => Value::Obj(Rc::new(Obj::String(g.to_string()))),
1186                    Obj::DateTimeTicks(t) => Value::Obj(Rc::new(Obj::String(t.to_string()))),
1187                    _ => Value::Obj(Rc::new(Obj::String(args[0].to_string()))),
1188                },
1189                _ => Value::Obj(Rc::new(Obj::String(args[0].to_string()))),
1190            }
1191        }
1192        "integer" => match &args[0] {
1193            Value::Int(n) => Value::Int(*n),
1194            Value::BigInt(n) => n.to_i64().map(Value::Int).unwrap_or(Value::Error(FsError { code: 1, message: "ChangeType: overflow converting to Integer".to_string(), line: -1, column: -1 })),
1195            Value::Number(n) if n.is_finite() && n.fract() == 0.0 => Value::Int(*n as i64),
1196            Value::Bool(b) => Value::Int(if *b { 1 } else { 0 }),
1197            Value::Obj(o) => match &**o {
1198                Obj::String(s) => s.parse::<i64>().map(Value::Int).unwrap_or(Value::Error(FsError { code: 1, message: "ChangeType: invalid Integer".to_string(), line: -1, column: -1 })),
1199                _ => Value::Error(FsError { code: 2, message: "ChangeType: Can't convert to Integer.".to_string(), line: -1, column: -1 }),
1200            },
1201            _ => Value::Error(FsError { code: 2, message: "ChangeType: Can't convert to Integer.".to_string(), line: -1, column: -1 }),
1202        },
1203        "biginteger" => match &args[0] {
1204            Value::Int(n) => Value::BigInt(BigInt::from(*n)),
1205            Value::BigInt(n) => Value::BigInt(n.clone()),
1206            Value::Number(n) if n.is_finite() && n.fract() == 0.0 => Value::BigInt(BigInt::from(*n as i64)),
1207            Value::Bool(b) => Value::BigInt(BigInt::from(if *b { 1 } else { 0 })),
1208            Value::Obj(o) => match &**o {
1209                Obj::String(s) => BigInt::parse_bytes(s.trim().as_bytes(), 10)
1210                    .map(Value::BigInt)
1211                    .unwrap_or(Value::Error(FsError { code: 1, message: "ChangeType: invalid BigInteger".to_string(), line: -1, column: -1 })),
1212                _ => Value::Error(FsError { code: 2, message: "ChangeType: Can't convert to BigInteger.".to_string(), line: -1, column: -1 }),
1213            },
1214            _ => Value::Error(FsError { code: 2, message: "ChangeType: Can't convert to BigInteger.".to_string(), line: -1, column: -1 }),
1215        },
1216        "float" => match &args[0] {
1217            Value::Number(n) => Value::Number(*n),
1218            Value::Int(n) => Value::Number(*n as f64),
1219            Value::BigInt(n) => n.to_f64().map(Value::Number).unwrap_or(Value::Error(FsError { code: 1, message: "ChangeType: overflow converting to Float".to_string(), line: -1, column: -1 })),
1220            Value::Bool(b) => Value::Number(if *b { 1.0 } else { 0.0 }),
1221            Value::Obj(o) => match &**o {
1222                Obj::String(s) => s.parse::<f64>().map(Value::Number).unwrap_or(Value::Error(FsError { code: 1, message: "ChangeType: invalid Float".to_string(), line: -1, column: -1 })),
1223                _ => Value::Error(FsError { code: 2, message: "ChangeType: Can't convert to Float.".to_string(), line: -1, column: -1 }),
1224            },
1225            _ => Value::Error(FsError { code: 2, message: "ChangeType: Can't convert to Float.".to_string(), line: -1, column: -1 }),
1226        },
1227        "boolean" => match &args[0] {
1228            Value::Bool(b) => Value::Bool(*b),
1229            Value::Int(n) => Value::Bool(*n != 0),
1230            Value::BigInt(n) => Value::Bool(!n.is_zero()),
1231            Value::Number(n) => Value::Bool(*n != 0.0),
1232            Value::Obj(o) => match &**o {
1233                Obj::String(s) => s.parse::<bool>().map(Value::Bool).unwrap_or(Value::Error(FsError { code: 1, message: "ChangeType: invalid Boolean".to_string(), line: -1, column: -1 })),
1234                _ => Value::Error(FsError { code: 2, message: "ChangeType: Can't convert to Boolean.".to_string(), line: -1, column: -1 }),
1235            },
1236            _ => Value::Error(FsError { code: 2, message: "ChangeType: Can't convert to Boolean.".to_string(), line: -1, column: -1 }),
1237        },
1238        "guid" => match &args[0] {
1239            Value::Obj(o) => match &**o {
1240                Obj::Guid(g) => Value::Obj(Rc::new(Obj::Guid(*g))),
1241                Obj::String(s) => match Uuid::parse_str(s) {
1242                    Ok(u) => Value::Obj(Rc::new(Obj::Guid(u))),
1243                    Err(_) => Value::Error(FsError { code: 1, message: "ChangeType: invalid Guid".to_string(), line: -1, column: -1 }),
1244                },
1245                _ => Value::Error(FsError { code: 2, message: "ChangeType: Can't convert to Guid.".to_string(), line: -1, column: -1 }),
1246            },
1247            _ => Value::Error(FsError { code: 2, message: "ChangeType: Can't convert to Guid.".to_string(), line: -1, column: -1 }),
1248        },
1249        "datetime" => match &args[0] {
1250            Value::Obj(o) => match &**o {
1251                Obj::DateTimeTicks(t) => Value::Obj(Rc::new(Obj::DateTimeTicks(*t))),
1252                Obj::String(s) => fs_date(&[Value::Obj(Rc::new(Obj::String(s.clone()))) ]),
1253                _ => Value::Error(FsError { code: 2, message: "ChangeType: Can't convert to DateTime.".to_string(), line: -1, column: -1 }),
1254            },
1255            Value::Int(t) => Value::Obj(Rc::new(Obj::DateTimeTicks(*t))),
1256            Value::BigInt(t) => t.to_i64().map(|x| Value::Obj(Rc::new(Obj::DateTimeTicks(x)))).unwrap_or(Value::Error(FsError { code: 1, message: "ChangeType: overflow converting to DateTime".to_string(), line: -1, column: -1 })),
1257            _ => Value::Error(FsError { code: 2, message: "ChangeType: Can't convert to DateTime.".to_string(), line: -1, column: -1 }),
1258        },
1259        "bytearray" => match &args[0] {
1260            Value::Obj(o) => match &**o {
1261                Obj::Bytes(b) => Value::Obj(Rc::new(Obj::Bytes(b.clone()))),
1262                Obj::String(s) => match general_purpose::STANDARD.decode(s.trim()) {
1263                    Ok(bytes) => Value::Obj(Rc::new(Obj::Bytes(bytes))),
1264                    Err(_) => Value::Error(FsError { code: 1, message: "ChangeType: invalid base64 for ByteArray".to_string(), line: -1, column: -1 }),
1265                },
1266                _ => Value::Error(FsError { code: 2, message: "ChangeType: Can't convert to ByteArray.".to_string(), line: -1, column: -1 }),
1267            },
1268            _ => Value::Error(FsError { code: 2, message: "ChangeType: Can't convert to ByteArray.".to_string(), line: -1, column: -1 }),
1269        },
1270        _ => Value::Error(FsError { code: 1, message: format!("ChangeType: Unknown target type '{type_name}'."), line: -1, column: -1 }),
1271    }
1272}
1273
1274fn text_lower(args: &[Value]) -> Value {
1275    if args.len() != 1 {
1276        return Value::Error(FsError { code: 1, message: "lower: single string parameter expected".to_string(), line: -1, column: -1 });
1277    }
1278    match &args[0] {
1279        Value::Error(e) => Value::Error(e.clone()),
1280        Value::Nil => Value::Nil,
1281        Value::Obj(o) => match &**o {
1282            Obj::String(s) => Value::Obj(Rc::new(Obj::String(s.to_lowercase()))),
1283            _ => Value::Error(FsError { code: 2, message: "lower: string parameter expected".to_string(), line: -1, column: -1 }),
1284        },
1285        _ => Value::Error(FsError { code: 2, message: "lower: string parameter expected".to_string(), line: -1, column: -1 }),
1286    }
1287}
1288
1289fn text_upper(args: &[Value]) -> Value {
1290    if args.len() != 1 {
1291        return Value::Error(FsError { code: 1, message: "upper: single string parameter expected".to_string(), line: -1, column: -1 });
1292    }
1293    match &args[0] {
1294        Value::Error(e) => Value::Error(e.clone()),
1295        Value::Nil => Value::Nil,
1296        Value::Obj(o) => match &**o {
1297            Obj::String(s) => Value::Obj(Rc::new(Obj::String(s.to_uppercase()))),
1298            _ => Value::Error(FsError { code: 2, message: "upper: string parameter expected".to_string(), line: -1, column: -1 }),
1299        },
1300        _ => Value::Error(FsError { code: 2, message: "upper: string parameter expected".to_string(), line: -1, column: -1 }),
1301    }
1302}
1303
1304fn text_endswith(args: &[Value]) -> Value {
1305    if args.len() != 2 {
1306        return Value::Error(FsError { code: 1, message: "endswith: two parameters expected".to_string(), line: -1, column: -1 });
1307    }
1308    if matches!(&args[0], Value::Nil) || matches!(&args[1], Value::Nil) {
1309        return Value::Bool(false);
1310    }
1311    match (&args[0], &args[1]) {
1312        (Value::Error(e), _) | (_, Value::Error(e)) => Value::Error(e.clone()),
1313        (Value::Obj(a), Value::Obj(b)) => match (&**a, &**b) {
1314            (Obj::String(s1), Obj::String(s2)) => Value::Bool(s1.ends_with(s2)),
1315            _ => Value::Error(FsError { code: 2, message: "endswith: both parameters must be strings".to_string(), line: -1, column: -1 }),
1316        },
1317        _ => Value::Error(FsError { code: 2, message: "endswith: both parameters must be strings".to_string(), line: -1, column: -1 }),
1318    }
1319}
1320
1321fn value_to_i64_default(v: &Value, default: i64) -> Result<i64, Value> {
1322    match v {
1323        Value::Nil => Ok(default),
1324        Value::Error(e) => Err(Value::Error(e.clone())),
1325        Value::Int(n) => Ok(*n),
1326        Value::BigInt(n) => n.to_i64().ok_or_else(|| Value::Error(FsError { code: 1, message: "numeric value is out of range".to_string(), line: -1, column: -1 })),
1327        Value::Number(n) if n.is_finite() => Ok(*n as i64),
1328        Value::Bool(b) => Ok(if *b { 1 } else { 0 }),
1329        Value::Obj(o) => match &**o {
1330            Obj::String(s) => s.trim().parse::<i64>().map_err(|_| Value::Error(FsError { code: 1, message: "invalid numeric string".to_string(), line: -1, column: -1 })),
1331            _ => Err(Value::Error(FsError { code: 2, message: "number expected".to_string(), line: -1, column: -1 })),
1332        },
1333        _ => Err(Value::Error(FsError { code: 2, message: "number expected".to_string(), line: -1, column: -1 })),
1334    }
1335}
1336
1337fn substring_by_char_indices(s: &str, index: i64, count: i64) -> String {
1338    if index < 0 {
1339        return "".to_string();
1340    }
1341    let chars: Vec<char> = s.chars().collect();
1342    let len = chars.len() as i64;
1343    if index >= len {
1344        return "".to_string();
1345    }
1346    let mut c = count;
1347    if c < 0 || index + c > len {
1348        c = len - index;
1349    }
1350    let start = index as usize;
1351    let end = (index + c) as usize;
1352    chars[start..end].iter().collect()
1353}
1354
1355fn text_substring(args: &[Value]) -> Value {
1356    if args.is_empty() {
1357        return Value::Error(FsError { code: 1, message: "substring requires at least one parameter.".to_string(), line: -1, column: -1 });
1358    }
1359    let s = match &args[0] {
1360        Value::Nil => return Value::Nil,
1361        Value::Error(e) => return Value::Error(e.clone()),
1362        Value::Obj(o) => match &**o {
1363            Obj::String(s) => s.clone(),
1364            _ => return Value::Nil,
1365        },
1366        _ => return Value::Nil,
1367    };
1368    let index = match value_to_i64_default(args.get(1).unwrap_or(&Value::Nil), 0) {
1369        Ok(v) => v,
1370        Err(e) => return e,
1371    };
1372    let count_default = s.chars().count() as i64;
1373    let count = match value_to_i64_default(args.get(2).unwrap_or(&Value::Nil), count_default) {
1374        Ok(v) => v,
1375        Err(e) => return e,
1376    };
1377    Value::Obj(Rc::new(Obj::String(substring_by_char_indices(&s, index, count))))
1378}
1379
1380fn text_find(args: &[Value]) -> Value {
1381    if args.len() < 2 || args.len() > 3 {
1382        return Value::Error(FsError { code: 1, message: "find: two or three parameters expected".to_string(), line: -1, column: -1 });
1383    }
1384    let text = match &args[0] {
1385        Value::Error(e) => return Value::Error(e.clone()),
1386        Value::Obj(o) => match &**o {
1387            Obj::String(s) => s.clone(),
1388            _ => return Value::Error(FsError { code: 2, message: "find: first parameter should be string".to_string(), line: -1, column: -1 }),
1389        },
1390        _ => return Value::Error(FsError { code: 2, message: "find: first parameter should be string".to_string(), line: -1, column: -1 }),
1391    };
1392    let search = match &args[1] {
1393        Value::Error(e) => return Value::Error(e.clone()),
1394        Value::Obj(o) => match &**o {
1395            Obj::String(s) => s.clone(),
1396            _ => return Value::Error(FsError { code: 2, message: "find: second parameter should be string".to_string(), line: -1, column: -1 }),
1397        },
1398        _ => return Value::Error(FsError { code: 2, message: "find: second parameter should be string".to_string(), line: -1, column: -1 }),
1399    };
1400
1401    let start_index = if args.len() == 3 {
1402        match &args[2] {
1403            Value::Int(i) => *i,
1404            _ => 0,
1405        }
1406    } else {
1407        0
1408    };
1409    let len = text.chars().count() as i64;
1410    if start_index < 0 || start_index >= len {
1411        return Value::Error(FsError { code: 1, message: "find: index is out of range".to_string(), line: -1, column: -1 });
1412    }
1413
1414    let hay: Vec<char> = text.chars().collect();
1415    let needle: Vec<char> = search.chars().collect();
1416    if needle.is_empty() {
1417        return Value::Int(start_index);
1418    }
1419    let start = start_index as usize;
1420    for i in start..=hay.len().saturating_sub(needle.len()) {
1421        if hay[i..i + needle.len()] == needle[..] {
1422            return Value::Int(i as i64);
1423        }
1424    }
1425    Value::Int(-1)
1426}
1427
1428fn text_is_blank(args: &[Value]) -> Value {
1429    if args.is_empty() {
1430        return Value::Error(FsError { code: 1, message: "isBlank: argument expected".to_string(), line: -1, column: -1 });
1431    }
1432    match &args[0] {
1433        Value::Error(e) => Value::Error(e.clone()),
1434        Value::Nil => Value::Bool(true),
1435        Value::Obj(o) => match &**o {
1436            Obj::String(s) => Value::Bool(s.trim().is_empty()),
1437            _ => Value::Error(FsError { code: 2, message: "isBlank: string expected".to_string(), line: -1, column: -1 }),
1438        },
1439        _ => Value::Error(FsError { code: 2, message: "isBlank: string expected".to_string(), line: -1, column: -1 }),
1440    }
1441}
1442
1443fn text_join(args: &[Value]) -> Value {
1444    if args.len() != 2 {
1445        return Value::Error(FsError { code: 1, message: "join: Two parameters expected".to_string(), line: -1, column: -1 });
1446    }
1447    let list_val = &args[0];
1448    let sep = match &args[1] {
1449        Value::Error(e) => return Value::Error(e.clone()),
1450        Value::Obj(o) => match &**o {
1451            Obj::String(s) => s.clone(),
1452            _ => return Value::Error(FsError { code: 2, message: "join: second parameter should be string".to_string(), line: -1, column: -1 }),
1453        },
1454        _ => return Value::Error(FsError { code: 2, message: "join: second parameter should be string".to_string(), line: -1, column: -1 }),
1455    };
1456
1457    if matches!(list_val, Value::Nil) {
1458        return Value::Error(FsError { code: 2, message: "join: first parameter should be list".to_string(), line: -1, column: -1 });
1459    }
1460    if let Value::Error(e) = list_val {
1461        return Value::Error(e.clone());
1462    }
1463
1464    let mut out = String::new();
1465    let mut first = true;
1466    match list_val {
1467        Value::Obj(o) => match &**o {
1468            Obj::List(items) => {
1469                for item in items.iter() {
1470                    if matches!(item, Value::Nil) { continue; }
1471                    if !first { out.push_str(&sep); }
1472                    first = false;
1473                    out.push_str(&item.to_string());
1474                }
1475            }
1476            Obj::Range(r) => {
1477                for i in 0..r.count {
1478                    if !first { out.push_str(&sep); }
1479                    first = false;
1480                    out.push_str(&(r.start + i as i64).to_string());
1481                }
1482            }
1483            _ => return Value::Error(FsError { code: 2, message: "join: first parameter should be list".to_string(), line: -1, column: -1 }),
1484        },
1485        _ => return Value::Error(FsError { code: 2, message: "join: first parameter should be list".to_string(), line: -1, column: -1 }),
1486    }
1487    Value::Obj(Rc::new(Obj::String(out)))
1488}
1489
1490fn list_take(args: &[Value]) -> Value {
1491    if args.len() != 2 {
1492        return Value::Error(FsError { code: 1, message: "Take: Invalid parameter count. Expected 2.".to_string(), line: -1, column: -1 });
1493    }
1494    if let Value::Error(e) = &args[0] { return Value::Error(e.clone()); }
1495    if matches!(args[0], Value::Nil) { return Value::Nil; }
1496    let n = match &args[1] {
1497        Value::Error(e) => return Value::Error(e.clone()),
1498        Value::Int(i) => *i,
1499        _ => return Value::Error(FsError { code: 2, message: "Take: second parameter should be Number".to_string(), line: -1, column: -1 }),
1500    };
1501    if n <= 0 {
1502        return Value::Obj(Rc::new(Obj::List(vec![])));
1503    }
1504    match &args[0] {
1505        Value::Obj(o) => match &**o {
1506            Obj::List(items) => {
1507                let take_n = (n as usize).min(items.len());
1508                Value::Obj(Rc::new(Obj::List(items.iter().take(take_n).cloned().collect())))
1509            }
1510            Obj::Range(r) => {
1511                let take_n = (n as usize).min(r.count);
1512                Value::Obj(Rc::new(Obj::Range(crate::obj::RangeObject { start: r.start, count: take_n })))
1513            }
1514            _ => Value::Error(FsError { code: 2, message: "Take: first parameter should be List".to_string(), line: -1, column: -1 }),
1515        },
1516        _ => Value::Error(FsError { code: 2, message: "Take: first parameter should be List".to_string(), line: -1, column: -1 }),
1517    }
1518}
1519
1520fn list_skip(args: &[Value]) -> Value {
1521    if args.len() != 2 {
1522        return Value::Error(FsError { code: 1, message: "Skip: Invalid parameter count. Expected 2.".to_string(), line: -1, column: -1 });
1523    }
1524    if let Value::Error(e) = &args[0] { return Value::Error(e.clone()); }
1525    if matches!(args[0], Value::Nil) { return Value::Nil; }
1526    let n = match &args[1] {
1527        Value::Error(e) => return Value::Error(e.clone()),
1528        Value::Int(i) => *i,
1529        _ => return Value::Error(FsError { code: 2, message: "Skip: second parameter should be Number".to_string(), line: -1, column: -1 }),
1530    };
1531    if n <= 0 {
1532        return args[0].clone();
1533    }
1534    match &args[0] {
1535        Value::Obj(o) => match &**o {
1536            Obj::List(items) => {
1537                if (n as usize) >= items.len() {
1538                    return Value::Obj(Rc::new(Obj::List(vec![])));
1539                }
1540                Value::Obj(Rc::new(Obj::List(items.iter().skip(n as usize).cloned().collect())))
1541            }
1542            Obj::Range(r) => {
1543                let skip_n = (n as usize).min(r.count);
1544                if skip_n >= r.count {
1545                    return Value::Obj(Rc::new(Obj::List(vec![])));
1546                }
1547                Value::Obj(Rc::new(Obj::Range(crate::obj::RangeObject { start: r.start + skip_n as i64, count: r.count - skip_n })))
1548            }
1549            _ => Value::Error(FsError { code: 2, message: "Skip: first parameter should be List".to_string(), line: -1, column: -1 }),
1550        },
1551        _ => Value::Error(FsError { code: 2, message: "Skip: first parameter should be List".to_string(), line: -1, column: -1 }),
1552    }
1553}
1554
1555fn list_reverse(args: &[Value]) -> Value {
1556    if args.len() != 1 {
1557        return Value::Error(FsError { code: 1, message: "Reverse: Invalid parameter count. Expected 1.".to_string(), line: -1, column: -1 });
1558    }
1559    if let Value::Error(e) = &args[0] { return Value::Error(e.clone()); }
1560    if matches!(args[0], Value::Nil) { return Value::Nil; }
1561    match &args[0] {
1562        Value::Obj(o) => match &**o {
1563            Obj::List(items) => {
1564                let mut out = items.clone();
1565                out.reverse();
1566                Value::Obj(Rc::new(Obj::List(out)))
1567            }
1568            Obj::Range(r) => {
1569                let mut out: Vec<Value> = Vec::with_capacity(r.count);
1570                for i in 0..r.count {
1571                    out.push(Value::Int(r.start + (r.count - 1 - i) as i64));
1572                }
1573                Value::Obj(Rc::new(Obj::List(out)))
1574            }
1575            _ => Value::Error(FsError { code: 2, message: "Reverse: parameter should be List".to_string(), line: -1, column: -1 }),
1576        },
1577        _ => Value::Error(FsError { code: 2, message: "Reverse: parameter should be List".to_string(), line: -1, column: -1 }),
1578    }
1579}
1580
1581fn list_distinct(args: &[Value]) -> Value {
1582    if args.len() != 1 {
1583        return Value::Error(FsError { code: 1, message: "Distinct: Invalid parameter count. Expected 1.".to_string(), line: -1, column: -1 });
1584    }
1585    if let Value::Error(e) = &args[0] { return Value::Error(e.clone()); }
1586    if matches!(args[0], Value::Nil) { return Value::Nil; }
1587    match &args[0] {
1588        Value::Obj(o) => match &**o {
1589            Obj::Range(r) => Value::Obj(Rc::new(Obj::Range(r.clone()))),
1590            Obj::List(items) => {
1591                let mut out: Vec<Value> = Vec::new();
1592                'outer: for v in items.iter().cloned() {
1593                    for seen in out.iter() {
1594                        if *seen == v {
1595                            continue 'outer;
1596                        }
1597                    }
1598                    out.push(v);
1599                }
1600                Value::Obj(Rc::new(Obj::List(out)))
1601            }
1602            _ => Value::Error(FsError { code: 2, message: "Distinct: parameter should be List".to_string(), line: -1, column: -1 }),
1603        },
1604        _ => Value::Error(FsError { code: 2, message: "Distinct: parameter should be List".to_string(), line: -1, column: -1 }),
1605    }
1606}
1607
1608fn list_contains(args: &[Value]) -> Value {
1609    if args.len() != 2 {
1610        return Value::Error(FsError { code: 1, message: "Contains: Invalid parameter count. Expected 2.".to_string(), line: -1, column: -1 });
1611    }
1612    let container = &args[0];
1613    let item = &args[1];
1614    if let Value::Error(e) = container { return Value::Error(e.clone()); }
1615    if let Value::Error(e) = item { return Value::Error(e.clone()); }
1616    match container {
1617        Value::Obj(o) => match &**o {
1618            Obj::List(items) => Value::Bool(items.iter().any(|x| x == item)),
1619            Obj::Range(r) => {
1620                let needle = as_i64_exact(item);
1621                if let Some(n) = needle {
1622                    Value::Bool(n >= r.start && n < r.start + (r.count as i64))
1623                } else {
1624                    Value::Bool(false)
1625                }
1626            }
1627            Obj::String(s) => {
1628                if let Value::Obj(o2) = item {
1629                    if let Obj::String(sub) = &**o2 {
1630                        Value::Bool(s.to_lowercase().contains(&sub.to_lowercase()))
1631                    } else {
1632                        Value::Error(FsError { code: 2, message: "Contains: Invalid types for parameters".to_string(), line: -1, column: -1 })
1633                    }
1634                } else {
1635                    Value::Error(FsError { code: 2, message: "Contains: Invalid types for parameters".to_string(), line: -1, column: -1 })
1636                }
1637            }
1638            _ => Value::Error(FsError { code: 2, message: "Contains: Invalid types for parameters".to_string(), line: -1, column: -1 }),
1639        },
1640        _ => Value::Error(FsError { code: 2, message: "Contains: Invalid types for parameters".to_string(), line: -1, column: -1 }),
1641    }
1642}