1use 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 insert("math", build_math_provider());
66 insert("text", build_text_provider());
67 insert("float", build_float_provider());
68
69 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 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 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 let json = format_json_value(value);
525 return Value::Obj(Rc::new(Obj::String(json)));
526 }
527 }
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("&"),
614 '<' => out.push_str("<"),
615 '>' => out.push_str(">"),
616 '"' => out.push_str("""),
617 '\'' => out.push_str("'"),
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}