Skip to main content

mq_lang/eval/
builtin.rs

1pub(super) mod bytes;
2pub(super) mod convert;
3pub(super) mod date;
4pub(super) mod path;
5mod range;
6mod regex;
7
8use crate::arena::Arena;
9use crate::ast::{constants, node as ast};
10use crate::error::runtime::RuntimeError;
11use crate::eval::builtin::convert::Convert;
12use crate::eval::env::{self, Env};
13use crate::ident::all_symbols;
14use crate::number::{self};
15use crate::selector::Selector;
16use crate::{Ident, Shared, SharedCell, Token, get_token, parse_markdown_input, parse_mdx_input};
17use base64::Engine;
18use chrono::{DateTime, Datelike, Local, NaiveDate, Timelike};
19use csv::ReaderBuilder;
20use itertools::Itertools;
21use quick_xml::XmlVersion;
22use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet};
23use similar::{ChangeTag, TextDiff};
24use smol_str::SmolStr;
25use std::borrow::Cow;
26use std::collections::BTreeMap;
27use std::io;
28use std::process::exit;
29use std::sync::LazyLock;
30use thiserror::Error;
31
32use self::range::{generate_char_range, generate_multi_char_range, generate_numeric_range};
33use self::regex::{capture_re, is_match_re, match_re, replace_re, split_re};
34use super::runtime_value::{self, RuntimeValue};
35use mq_markdown;
36
37/// Maximum number of elements allowed in a generated range
38pub(super) const MAX_RANGE_SIZE: usize = 1_000_000;
39const MAX_REPEAT_COUNT: usize = 1_000;
40
41type FunctionName = String;
42type ErrorArgs = Vec<RuntimeValue>;
43type SharedEnv = Shared<SharedCell<Env>>;
44pub type Args = Vec<RuntimeValue>;
45
46#[derive(Clone, Debug)]
47pub struct BuiltinFunction {
48    pub name: &'static str,
49    pub num_params: ParamNum,
50    pub func: fn(&Ident, &RuntimeValue, Args, &SharedEnv) -> Result<RuntimeValue, Error>,
51}
52
53#[derive(Clone, Debug)]
54pub enum ParamNum {
55    None,
56    Fixed(u8),
57    Range(u8, u8),
58}
59
60impl ParamNum {
61    #[inline(always)]
62    pub fn to_num(&self) -> u8 {
63        match self {
64            ParamNum::None => 0,
65            ParamNum::Fixed(n) => *n,
66            ParamNum::Range(min, _) => *min,
67        }
68    }
69
70    #[inline(always)]
71    pub fn is_valid(&self, num_args: u8) -> bool {
72        match self {
73            ParamNum::None => num_args == 0,
74            ParamNum::Fixed(n) => num_args == *n,
75            ParamNum::Range(min, max) => num_args >= *min && num_args <= *max,
76        }
77    }
78
79    #[inline(always)]
80    pub fn is_missing_one_params(&self, num_args: u8) -> bool {
81        match self {
82            ParamNum::Fixed(n) => num_args == n.checked_sub(1).unwrap_or_default(),
83            ParamNum::Range(n, _) => num_args == n.checked_sub(1).unwrap_or_default(),
84            _ => false,
85        }
86    }
87}
88
89impl BuiltinFunction {
90    pub fn new(
91        name: &'static str,
92        num_params: ParamNum,
93        func: fn(&Ident, &RuntimeValue, Args, &SharedEnv) -> Result<RuntimeValue, Error>,
94    ) -> Self {
95        BuiltinFunction { name, num_params, func }
96    }
97}
98#[mq_macros::mq_fn(name = "partial", params = Range(1, u8::MAX))]
99fn partial_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
100    if args.is_empty() {
101        return Err(Error::InvalidNumberOfArguments(ident.to_string(), 1, 0));
102    }
103    let fn_value = args.remove(0);
104    let provided = args;
105
106    match fn_value {
107        RuntimeValue::Function(params, program, fn_env) => {
108            if provided.len() >= params.len() {
109                return Err(Error::InvalidNumberOfArguments(
110                    ident.to_string(),
111                    params.len() as u8,
112                    provided.len() as u8 + 1,
113                ));
114            }
115            let partial_env = Shared::new(SharedCell::new(Env::with_parent(Shared::downgrade(&fn_env))));
116            let mut remaining = crate::ast::node::Params::new();
117            for (i, param) in params.iter().enumerate() {
118                if i < provided.len() {
119                    #[cfg(not(feature = "sync"))]
120                    partial_env.borrow_mut().define(param.ident.name, provided[i].clone());
121                    #[cfg(feature = "sync")]
122                    partial_env
123                        .write()
124                        .unwrap()
125                        .define(param.ident.name, provided[i].clone());
126                } else {
127                    remaining.push(param.clone());
128                }
129            }
130            Ok(RuntimeValue::Function(Box::new(remaining), program, partial_env))
131        }
132        other => Err(Error::InvalidTypes(ident.to_string(), vec![other])),
133    }
134}
135
136#[mq_macros::mq_fn(name = "halt", params = Fixed(1))]
137fn halt_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
138    match args.as_mut_slice() {
139        [RuntimeValue::Number(exit_code)] => exit(exit_code.value() as i32),
140        [a] => Err(Error::InvalidTypes(ident.to_string(), vec![std::mem::take(a)])),
141        _ => unreachable!("halt should always receive exactly one argument"),
142    }
143}
144
145#[mq_macros::mq_fn(name = "error", params = Fixed(1))]
146fn error_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
147    match args.as_mut_slice() {
148        [RuntimeValue::String(message)] => Err(Error::UserDefined(message.to_string())),
149        [a] => Err(Error::InvalidTypes(ident.to_string(), vec![std::mem::take(a)])),
150        _ => unreachable!("error should always receive exactly one argument"),
151    }
152}
153
154#[mq_macros::mq_fn(name = "print", params = Fixed(1))]
155fn print_impl(_: &Ident, current_value: &RuntimeValue, args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
156    match args.as_slice() {
157        [a] => {
158            #[cfg(target_arch = "wasm32")]
159            {
160                web_sys::console::log_1(&a.to_string().into());
161            }
162            #[cfg(not(target_arch = "wasm32"))]
163            {
164                println!("{}", a);
165            }
166            Ok(current_value.clone())
167        }
168        _ => unreachable!("print should always receive exactly one argument"),
169    }
170}
171
172#[mq_macros::mq_fn(name = "stderr", params = Fixed(1))]
173fn stderr_impl(_: &Ident, current_value: &RuntimeValue, args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
174    match args.as_slice() {
175        [a] => {
176            #[cfg(target_arch = "wasm32")]
177            {
178                web_sys::console::error_1(&a.to_string().into());
179            }
180            #[cfg(not(target_arch = "wasm32"))]
181            {
182                eprintln!("{}", a);
183            }
184
185            Ok(current_value.clone())
186        }
187        _ => unreachable!("stderr should always receive exactly one argument"),
188    }
189}
190
191#[mq_macros::mq_fn(name = "type", params = Fixed(1))]
192fn type_impl(_: &Ident, _: &RuntimeValue, args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
193    match args.first() {
194        Some(value) => Ok(value.name().to_string().into()),
195        None => Ok(RuntimeValue::NONE),
196    }
197}
198
199#[mq_macros::mq_fn(name = "array", params = Range(0, u8::MAX))]
200fn array_impl(_: &Ident, _: &RuntimeValue, args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
201    Ok(RuntimeValue::Array(args))
202}
203
204#[mq_macros::mq_fn(name = "flatten", params = Fixed(1))]
205fn flatten_impl(_: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
206    match args.as_mut_slice() {
207        [RuntimeValue::Array(arrays)] => Ok(convert::flatten(std::mem::take(arrays)).into()),
208        [a] => Ok(std::mem::take(a)),
209        _ => unreachable!("flatten should always receive exactly one argument"),
210    }
211}
212
213#[mq_macros::mq_fn(name = "convert", params = Fixed(2))]
214fn convert_impl(_: &Ident, _: &RuntimeValue, args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
215    match args.as_slice() {
216        [input, convert_value] => Convert::try_from(convert_value).map(|convert| convert.convert(input)),
217        _ => unreachable!("convert should always receive exactly two arguments"),
218    }
219}
220
221#[mq_macros::mq_fn(name = "from_date", params = Fixed(1))]
222fn from_date_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
223    match args.as_mut_slice() {
224        [RuntimeValue::String(date_str)] => convert::from_date(date_str),
225        [RuntimeValue::Markdown(node_value, _)] => convert::from_date(node_value.value().as_str()),
226        [a] => Err(Error::InvalidTypes(ident.to_string(), vec![std::mem::take(a)])),
227        _ => unreachable!("from_date should always receive exactly one argument"),
228    }
229}
230
231#[mq_macros::mq_fn(name = "to_date", params = Fixed(2))]
232fn to_date_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
233    match args.as_mut_slice() {
234        [RuntimeValue::Number(ms), RuntimeValue::String(format)] => convert::to_date(*ms, Some(format.as_str())),
235        [a, b] => Err(Error::InvalidTypes(
236            ident.to_string(),
237            vec![std::mem::take(a), std::mem::take(b)],
238        )),
239        _ => unreachable!("to_date should always receive exactly two arguments"),
240    }
241}
242
243#[mq_macros::mq_fn(name = "now", params = None)]
244fn now_impl(_: &Ident, _: &RuntimeValue, _: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
245    Ok(RuntimeValue::Number(
246        (std::time::SystemTime::now()
247            .duration_since(std::time::UNIX_EPOCH)
248            .map_err(|e| Error::Runtime(format!("{}", e)))?
249            .as_secs() as i64)
250            .into(),
251    ))
252}
253
254/// Array format: [year, month (0-11), day (1-31), hour (0-23), minute (0-59), second (0-60), weekday (0=Sun), day-of-year (0-365)]
255fn broken_down_time_array<Tz: chrono::TimeZone>(dt: &chrono::DateTime<Tz>) -> RuntimeValue {
256    RuntimeValue::Array(vec![
257        RuntimeValue::Number(((dt.year()) as i64).into()),
258        RuntimeValue::Number((dt.month0() as i64).into()),
259        RuntimeValue::Number((dt.day() as i64).into()),
260        RuntimeValue::Number((dt.hour() as i64).into()),
261        RuntimeValue::Number((dt.minute() as i64).into()),
262        RuntimeValue::Number((dt.second() as i64).into()),
263        RuntimeValue::Number((dt.weekday().num_days_from_sunday() as i64).into()),
264        RuntimeValue::Number((dt.ordinal0() as i64).into()),
265    ])
266}
267
268fn broken_down_time_to_naive(caller: &str, arr: &[RuntimeValue]) -> Result<chrono::NaiveDateTime, Error> {
269    let get_i64 = |v: &RuntimeValue| -> Result<i64, Error> {
270        match v {
271            RuntimeValue::Number(n) => Ok(n.value() as i64),
272            _ => Err(Error::Runtime(format!("{caller}: array elements must be numbers"))),
273        }
274    };
275    let year = get_i64(&arr[0])? as i32;
276    let month = (get_i64(&arr[1])? + 1) as u32;
277    let day = get_i64(&arr[2])? as u32;
278    let hour = get_i64(&arr[3])? as u32;
279    let minute = get_i64(&arr[4])? as u32;
280    let second = get_i64(&arr[5])? as u32;
281    NaiveDate::from_ymd_opt(year, month, day)
282        .and_then(|d| d.and_hms_opt(hour, minute, second))
283        .ok_or_else(|| Error::Runtime(format!("{caller}: invalid date components")))
284}
285
286/// Converts Unix timestamp (seconds) to broken-down UTC time array:
287/// [year, month (0-11), day, hour, minute, second, weekday (0=Sunday), day-of-year (0-365)]
288#[mq_macros::mq_fn(name = "gmtime", params = Fixed(1))]
289fn gmtime_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
290    match args.as_mut_slice() {
291        [RuntimeValue::Number(secs)] => {
292            let secs_val = secs.value() as i64;
293            DateTime::from_timestamp(secs_val, 0)
294                .map(|dt| broken_down_time_array(&dt))
295                .ok_or_else(|| Error::Runtime(format!("Invalid timestamp: {}", secs_val)))
296        }
297        [a] => Err(Error::InvalidTypes(ident.to_string(), vec![std::mem::take(a)])),
298        _ => unreachable!("gmtime should always receive exactly one argument"),
299    }
300}
301
302/// Converts Unix timestamp (seconds) to broken-down local time array:
303/// [year, month (0-11), day, hour, minute, second, weekday (0=Sunday), day-of-year (0-365)]
304#[mq_macros::mq_fn(name = "localtime", params = Fixed(1))]
305fn localtime_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
306    match args.as_mut_slice() {
307        [RuntimeValue::Number(secs)] => {
308            let secs_val = secs.value() as i64;
309            DateTime::from_timestamp(secs_val, 0)
310                .map(|dt| broken_down_time_array(&dt.with_timezone(&Local)))
311                .ok_or_else(|| Error::Runtime(format!("Invalid timestamp: {}", secs_val)))
312        }
313        [a] => Err(Error::InvalidTypes(ident.to_string(), vec![std::mem::take(a)])),
314        _ => unreachable!("localtime should always receive exactly one argument"),
315    }
316}
317
318/// Converts broken-down UTC time array to Unix timestamp (seconds).
319/// Input format: [year, month (0-11), day, hour, minute, second, weekday, day-of-year]
320#[mq_macros::mq_fn(name = "mktime", params = Fixed(1))]
321fn mktime_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
322    match args.as_mut_slice() {
323        [RuntimeValue::Array(arr)] if arr.len() == 8 => {
324            broken_down_time_to_naive("mktime", arr).map(|dt| RuntimeValue::Number(dt.and_utc().timestamp().into()))
325        }
326        [a] => Err(Error::InvalidTypes(ident.to_string(), vec![std::mem::take(a)])),
327        _ => unreachable!("mktime should always receive exactly one argument"),
328    }
329}
330
331/// Formats a Unix timestamp (seconds) as a date string using the given strftime format.
332#[mq_macros::mq_fn(name = "strftime", params = Fixed(2))]
333fn strftime_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
334    match args.as_mut_slice() {
335        [RuntimeValue::Number(secs), RuntimeValue::String(fmt)] => {
336            let secs_val = secs.value() as i64;
337            DateTime::from_timestamp(secs_val, 0)
338                .map(|dt| RuntimeValue::String(dt.format(fmt.as_str()).to_string()))
339                .ok_or_else(|| Error::Runtime(format!("strftime: invalid timestamp: {}", secs_val)))
340        }
341        [a, b] => Err(Error::InvalidTypes(
342            ident.to_string(),
343            vec![std::mem::take(a), std::mem::take(b)],
344        )),
345        _ => unreachable!("strftime should always receive exactly two arguments"),
346    }
347}
348
349/// Adds n units to a broken-down time array and returns a new broken-down array (UTC).
350/// Input/output format: [year, month (0-11), day, hour, minute, second, weekday, day-of-year]
351/// Units: "seconds", "minutes", "hours", "days", "weeks", "months", "years"
352/// Month/year arithmetic is calendar-aware (e.g. Jan 31 + 1 month = Feb 28/29).
353#[mq_macros::mq_fn(name = "date_add", params = Fixed(3))]
354fn date_add_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
355    match args.as_mut_slice() {
356        [
357            RuntimeValue::Array(arr),
358            RuntimeValue::Number(n),
359            RuntimeValue::String(unit),
360        ] if arr.len() == 8 => {
361            let amount = n.value() as i64;
362            let dt = broken_down_time_to_naive("date_add", arr)?.and_utc();
363            date::add(dt, amount, unit.as_str()).map(|dt| broken_down_time_array(&dt))
364        }
365        [a, b, c] => Err(Error::InvalidTypes(
366            ident.to_string(),
367            vec![std::mem::take(a), std::mem::take(b), std::mem::take(c)],
368        )),
369        _ => unreachable!("date_add should always receive exactly three arguments"),
370    }
371}
372
373/// Returns the difference (array2 - array1) in the given unit.
374/// Input format: [year, month (0-11), day, hour, minute, second, weekday, day-of-year]
375/// Units: "seconds", "minutes", "hours", "days", "weeks"
376#[mq_macros::mq_fn(name = "date_diff", params = Fixed(3))]
377fn date_diff_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
378    match args.as_mut_slice() {
379        [
380            RuntimeValue::Array(arr1),
381            RuntimeValue::Array(arr2),
382            RuntimeValue::String(unit),
383        ] if arr1.len() == 8 && arr2.len() == 8 => {
384            let dt1 = broken_down_time_to_naive("date_diff", arr1)?.and_utc();
385            let dt2 = broken_down_time_to_naive("date_diff", arr2)?.and_utc();
386            let duration = dt2.signed_duration_since(dt1);
387            date::diff(duration, unit.as_str()).map(|n| RuntimeValue::Number(n.into()))
388        }
389        [a, b, c] => Err(Error::InvalidTypes(
390            ident.to_string(),
391            vec![std::mem::take(a), std::mem::take(b), std::mem::take(c)],
392        )),
393        _ => unreachable!("date_diff should always receive exactly three arguments"),
394    }
395}
396
397#[mq_macros::mq_fn(name = "base64", params = Fixed(1))]
398fn base64_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
399    match args.as_mut_slice() {
400        [RuntimeValue::String(s)] => convert::base64(s),
401        [RuntimeValue::Bytes(b)] => convert::base64_bytes(b),
402        [node @ RuntimeValue::Markdown(_, _)] => node
403            .markdown_node()
404            .map(|md| {
405                convert::base64(md.value().as_str()).and_then(|b| match b {
406                    RuntimeValue::String(s) => Ok(node.update_markdown_value(&s)),
407                    a => Err(Error::InvalidTypes(ident.to_string(), vec![a.clone()])),
408                })
409            })
410            .unwrap_or_else(|| Ok(RuntimeValue::NONE)),
411        [a] => Err(Error::InvalidTypes(ident.to_string(), vec![std::mem::take(a)])),
412        _ => unreachable!("base64 should always receive exactly one argument"),
413    }
414}
415
416#[mq_macros::mq_fn(name = "base64d", params = Fixed(1))]
417fn base64d_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
418    match args.as_mut_slice() {
419        [RuntimeValue::String(s)] => convert::base64d(s),
420        [node @ RuntimeValue::Markdown(_, _)] => node
421            .markdown_node()
422            .map(|md| {
423                convert::base64d(md.value().as_str()).and_then(|o| match o {
424                    RuntimeValue::String(s) => Ok(node.update_markdown_value(&s)),
425                    a => Err(Error::InvalidTypes(ident.to_string(), vec![a.clone()])),
426                })
427            })
428            .unwrap_or_else(|| Ok(RuntimeValue::NONE)),
429        [a] => Err(Error::InvalidTypes(ident.to_string(), vec![std::mem::take(a)])),
430        _ => unreachable!("base64d should always receive exactly one argument"),
431    }
432}
433
434#[mq_macros::mq_fn(name = "base64url", params = Fixed(1))]
435fn base64url_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
436    match args.as_mut_slice() {
437        [RuntimeValue::String(s)] => convert::base64url(s),
438        [node @ RuntimeValue::Markdown(_, _)] => node
439            .markdown_node()
440            .map(|md| {
441                convert::base64url(md.value().as_str()).and_then(|b| match b {
442                    RuntimeValue::String(s) => Ok(node.update_markdown_value(&s)),
443                    a => Err(Error::InvalidTypes(ident.to_string(), vec![a.clone()])),
444                })
445            })
446            .unwrap_or_else(|| Ok(RuntimeValue::NONE)),
447        [a] => Err(Error::InvalidTypes(ident.to_string(), vec![std::mem::take(a)])),
448        _ => unreachable!("base64url should always receive exactly one argument"),
449    }
450}
451
452#[mq_macros::mq_fn(name = "base64urld", params = Fixed(1))]
453fn base64urld_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
454    match args.as_mut_slice() {
455        [RuntimeValue::String(s)] => convert::base64urld(s),
456        [node @ RuntimeValue::Markdown(_, _)] => node
457            .markdown_node()
458            .map(|md| {
459                convert::base64urld(md.value().as_str()).and_then(|o| match o {
460                    RuntimeValue::String(s) => Ok(node.update_markdown_value(&s)),
461                    a => Err(Error::InvalidTypes(ident.to_string(), vec![a.clone()])),
462                })
463            })
464            .unwrap_or_else(|| Ok(RuntimeValue::NONE)),
465        [a] => Err(Error::InvalidTypes(ident.to_string(), vec![std::mem::take(a)])),
466        _ => unreachable!("base64urld should always receive exactly one argument"),
467    }
468}
469
470#[mq_macros::mq_fn(name = "md5", params = Fixed(1))]
471fn md5_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
472    match args.as_mut_slice() {
473        [RuntimeValue::String(s)] => convert::md5(s),
474        [RuntimeValue::Bytes(b)] => convert::md5_bytes(b),
475        [node @ RuntimeValue::Markdown(_, _)] => node
476            .markdown_node()
477            .map(|md| {
478                convert::md5(md.value().as_str()).and_then(|h| match h {
479                    RuntimeValue::String(s) => Ok(node.update_markdown_value(&s)),
480                    a => Err(Error::InvalidTypes(ident.to_string(), vec![a.clone()])),
481                })
482            })
483            .unwrap_or_else(|| Ok(RuntimeValue::NONE)),
484        [RuntimeValue::None] => Ok(RuntimeValue::NONE),
485        [a] => convert::md5(&a.to_string()),
486        _ => unreachable!("md5 should always receive exactly one argument"),
487    }
488}
489
490#[mq_macros::mq_fn(name = "sha256", params = Fixed(1))]
491fn sha256_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
492    match args.as_mut_slice() {
493        [RuntimeValue::String(s)] => convert::sha256(s),
494        [RuntimeValue::Bytes(b)] => convert::sha256_bytes(b),
495        [node @ RuntimeValue::Markdown(_, _)] => node
496            .markdown_node()
497            .map(|md| {
498                convert::sha256(md.value().as_str()).and_then(|h| match h {
499                    RuntimeValue::String(s) => Ok(node.update_markdown_value(&s)),
500                    a => Err(Error::InvalidTypes(ident.to_string(), vec![a.clone()])),
501                })
502            })
503            .unwrap_or_else(|| Ok(RuntimeValue::NONE)),
504        [RuntimeValue::None] => Ok(RuntimeValue::NONE),
505        [a] => convert::sha256(&a.to_string()),
506        _ => unreachable!("sha256 should always receive exactly one argument"),
507    }
508}
509
510#[mq_macros::mq_fn(name = "sha512", params = Fixed(1))]
511fn sha512_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
512    match args.as_mut_slice() {
513        [RuntimeValue::String(s)] => convert::sha512(s),
514        [RuntimeValue::Bytes(b)] => convert::sha512_bytes(b),
515        [node @ RuntimeValue::Markdown(_, _)] => node
516            .markdown_node()
517            .map(|md| {
518                convert::sha512(md.value().as_str()).and_then(|h| match h {
519                    RuntimeValue::String(s) => Ok(node.update_markdown_value(&s)),
520                    a => Err(Error::InvalidTypes(ident.to_string(), vec![a.clone()])),
521                })
522            })
523            .unwrap_or_else(|| Ok(RuntimeValue::NONE)),
524        [RuntimeValue::None] => Ok(RuntimeValue::NONE),
525        [a] => convert::sha512(&a.to_string()),
526        _ => unreachable!("sha512 should always receive exactly one argument"),
527    }
528}
529
530#[mq_macros::mq_fn(name = "from_hex", params = Fixed(1))]
531fn from_hex_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
532    match args.as_mut_slice() {
533        [RuntimeValue::String(s)] => convert::from_hex(s),
534        [node @ RuntimeValue::Markdown(_, _)] => node
535            .markdown_node()
536            .map(|md| convert::from_hex(md.value().as_str()))
537            .unwrap_or_else(|| Ok(RuntimeValue::NONE)),
538        [RuntimeValue::None] => Ok(RuntimeValue::NONE),
539        [a] => Err(Error::InvalidTypes(ident.to_string(), vec![std::mem::take(a)])),
540        _ => unreachable!("from_hex should always receive exactly one argument"),
541    }
542}
543
544#[mq_macros::mq_fn(name = "to_hex", params = Fixed(1))]
545fn to_hex_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
546    match args.as_mut_slice() {
547        [RuntimeValue::Bytes(b)] => convert::to_hex(b),
548        [RuntimeValue::None] => Ok(RuntimeValue::NONE),
549        [a] => Err(Error::InvalidTypes(ident.to_string(), vec![std::mem::take(a)])),
550        _ => unreachable!("to_hex should always receive exactly one argument"),
551    }
552}
553
554#[mq_macros::mq_fn(name = "utf8", params = Fixed(1))]
555fn utf8_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
556    match args.as_mut_slice() {
557        [RuntimeValue::Bytes(b)] => convert::utf8(b),
558        [RuntimeValue::None] => Ok(RuntimeValue::NONE),
559        [a] => Err(Error::InvalidTypes(ident.to_string(), vec![std::mem::take(a)])),
560        _ => unreachable!("utf8 should always receive exactly one argument"),
561    }
562}
563
564#[mq_macros::mq_fn(name = "xor", params = Fixed(2))]
565fn xor_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
566    match args.as_mut_slice() {
567        [RuntimeValue::Bytes(b1), RuntimeValue::Bytes(b2)] => {
568            if b1.len() != b2.len() {
569                return Err(Error::Runtime(format!(
570                    "xor: byte slices must have the same length ({} != {})",
571                    b1.len(),
572                    b2.len()
573                )));
574            }
575            Ok(RuntimeValue::Bytes(
576                b1.iter().zip(b2.iter()).map(|(a, b)| a ^ b).collect(),
577            ))
578        }
579        [a, b] => Err(Error::InvalidTypes(
580            ident.to_string(),
581            vec![std::mem::take(a), std::mem::take(b)],
582        )),
583        _ => unreachable!("xor should always receive exactly two arguments"),
584    }
585}
586
587#[mq_macros::mq_fn(name = "band", params = Fixed(2))]
588fn band_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
589    match args.as_mut_slice() {
590        [RuntimeValue::Bytes(b1), RuntimeValue::Bytes(b2)] => {
591            if b1.len() != b2.len() {
592                return Err(Error::Runtime(format!(
593                    "band: byte slices must have the same length ({} != {})",
594                    b1.len(),
595                    b2.len()
596                )));
597            }
598            Ok(RuntimeValue::Bytes(
599                b1.iter().zip(b2.iter()).map(|(a, b)| a & b).collect(),
600            ))
601        }
602        [a, b] => Err(Error::InvalidTypes(
603            ident.to_string(),
604            vec![std::mem::take(a), std::mem::take(b)],
605        )),
606        _ => unreachable!("band should always receive exactly two arguments"),
607    }
608}
609
610#[mq_macros::mq_fn(name = "bor", params = Fixed(2))]
611fn bor_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
612    match args.as_mut_slice() {
613        [RuntimeValue::Bytes(b1), RuntimeValue::Bytes(b2)] => {
614            if b1.len() != b2.len() {
615                return Err(Error::Runtime(format!(
616                    "bor: byte slices must have the same length ({} != {})",
617                    b1.len(),
618                    b2.len()
619                )));
620            }
621            Ok(RuntimeValue::Bytes(
622                b1.iter().zip(b2.iter()).map(|(a, b)| a | b).collect(),
623            ))
624        }
625        [a, b] => Err(Error::InvalidTypes(
626            ident.to_string(),
627            vec![std::mem::take(a), std::mem::take(b)],
628        )),
629        _ => unreachable!("bor should always receive exactly two arguments"),
630    }
631}
632
633#[mq_macros::mq_fn(name = "bnot", params = Fixed(1))]
634fn bnot_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
635    match args.as_mut_slice() {
636        [RuntimeValue::Bytes(b)] => Ok(RuntimeValue::Bytes(b.iter().map(|x| !x).collect())),
637        [a] => Err(Error::InvalidTypes(ident.to_string(), vec![std::mem::take(a)])),
638        _ => unreachable!("bnot should always receive exactly one argument"),
639    }
640}
641
642#[mq_macros::mq_fn(name = "pack", params = Fixed(2))]
643fn pack_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
644    match args.as_mut_slice() {
645        [RuntimeValue::String(fmt), RuntimeValue::Number(n)] => bytes::pack_number(fmt, n.value()),
646        [a, b] => Err(Error::InvalidTypes(
647            ident.to_string(),
648            vec![std::mem::take(a), std::mem::take(b)],
649        )),
650        _ => unreachable!("pack should always receive exactly two arguments"),
651    }
652}
653
654#[mq_macros::mq_fn(name = "unpack", params = Fixed(2))]
655fn unpack_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
656    match args.as_mut_slice() {
657        [RuntimeValue::String(fmt), RuntimeValue::Bytes(b)] => bytes::unpack_bytes(fmt, b),
658        [a, b] => Err(Error::InvalidTypes(
659            ident.to_string(),
660            vec![std::mem::take(a), std::mem::take(b)],
661        )),
662        _ => unreachable!("unpack should always receive exactly two arguments"),
663    }
664}
665
666#[mq_macros::mq_fn(name = "min", params = Fixed(2))]
667fn min_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
668    match args.as_mut_slice() {
669        [RuntimeValue::Number(n1), RuntimeValue::Number(n2)] => Ok(std::cmp::min(*n1, *n2).into()),
670        [RuntimeValue::String(s1), RuntimeValue::String(s2)] => Ok(std::mem::take(std::cmp::min(s1, s2)).into()),
671        [RuntimeValue::Symbol(s1), RuntimeValue::Symbol(s2)] => Ok(std::mem::take(std::cmp::min(s1, s2)).into()),
672        [RuntimeValue::None, _] | [_, RuntimeValue::None] => Ok(RuntimeValue::NONE),
673        [a, b] => Err(Error::InvalidTypes(
674            ident.to_string(),
675            vec![std::mem::take(a), std::mem::take(b)],
676        )),
677        _ => unreachable!("min should always receive exactly two arguments"),
678    }
679}
680
681#[mq_macros::mq_fn(name = "max", params = Fixed(2))]
682fn max_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
683    match args.as_mut_slice() {
684        [RuntimeValue::Number(n1), RuntimeValue::Number(n2)] => Ok(std::cmp::max(*n1, *n2).into()),
685        [RuntimeValue::String(s1), RuntimeValue::String(s2)] => Ok(std::mem::take(std::cmp::max(s1, s2)).into()),
686        [RuntimeValue::Symbol(s1), RuntimeValue::Symbol(s2)] => Ok(std::mem::take(std::cmp::max(s1, s2)).into()),
687        [RuntimeValue::None, a] | [a, RuntimeValue::None] => Ok(std::mem::take(a)),
688        [a, b] => Err(Error::InvalidTypes(
689            ident.to_string(),
690            vec![std::mem::take(a), std::mem::take(b)],
691        )),
692        _ => unreachable!("max should always receive exactly two arguments"),
693    }
694}
695
696#[mq_macros::mq_fn(name = "from_html", params = Fixed(1))]
697fn from_html_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
698    match args.as_mut_slice() {
699        [RuntimeValue::String(s)] => {
700            let markdown = mq_markdown::convert_html_to_markdown(s, mq_markdown::ConversionOptions::default())
701                .map_err(|e| Error::Runtime(format!("Failed to convert HTML: {}", e)))?;
702            Ok(RuntimeValue::Array(parse_markdown_input(&markdown).map_err(|e| {
703                Error::Runtime(format!("Failed to parse converted markdown: {}", e))
704            })?))
705        }
706        [RuntimeValue::None] => Ok(RuntimeValue::NONE),
707        [a] => Err(Error::InvalidTypes(ident.to_string(), vec![std::mem::take(a)])),
708        _ => unreachable!("from_html should always receive exactly one argument"),
709    }
710}
711
712#[mq_macros::mq_fn(name = "to_html", params = Fixed(1))]
713fn to_html_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
714    match args.as_mut_slice() {
715        [a] => convert::to_html(a).map_err(|_| Error::InvalidTypes(ident.to_string(), vec![std::mem::take(a)])),
716        _ => unreachable!("to_html should always receive exactly one argument"),
717    }
718}
719
720#[mq_macros::mq_fn(name = "to_markdown_string", params = Fixed(1))]
721fn to_markdown_string_impl(_: &Ident, _: &RuntimeValue, args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
722    convert::to_markdown_string(args)
723}
724
725#[mq_macros::mq_fn(name = "to_string", params = Fixed(1))]
726fn to_string_impl(_: &Ident, _: &RuntimeValue, args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
727    match args.first() {
728        Some(value) => convert::to_string(value),
729        None => unreachable!("to_string should always receive exactly one argument"),
730    }
731}
732
733#[mq_macros::mq_fn(name = "to_number", params = Fixed(1))]
734fn to_number_impl(_: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
735    convert::to_number(&mut args[0])
736}
737
738#[mq_macros::mq_fn(name = "to_array", params = Fixed(1))]
739fn to_array_impl(_: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
740    convert::to_array(&mut args[0])
741}
742
743#[mq_macros::mq_fn(name = "to_bytes", params = Fixed(1))]
744fn to_bytes_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
745    match args.as_mut_slice() {
746        [RuntimeValue::String(s)] => Ok(RuntimeValue::Bytes(std::mem::take(s).into_bytes())),
747        [RuntimeValue::Bytes(b)] => Ok(RuntimeValue::Bytes(std::mem::take(b))),
748        [RuntimeValue::Array(arr)] => {
749            let mut bytes = Vec::with_capacity(arr.len());
750            for v in arr.iter() {
751                match v {
752                    RuntimeValue::Number(n) => {
753                        let f = n.value();
754                        if !f.is_finite() || !n.is_int() || !(0.0..=255.0).contains(&f) {
755                            return Err(Error::InvalidTypes(ident.to_string(), vec![v.clone()]));
756                        }
757                        bytes.push(f as u8);
758                    }
759                    other => return Err(Error::InvalidTypes(ident.to_string(), vec![other.clone()])),
760                }
761            }
762            Ok(RuntimeValue::Bytes(bytes))
763        }
764        [a] => Err(Error::InvalidTypes(ident.to_string(), vec![std::mem::take(a)])),
765        _ => unreachable!("to_bytes should always receive exactly one argument"),
766    }
767}
768
769#[mq_macros::mq_fn(name = "url_encode", params = Fixed(1))]
770fn url_encode_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
771    match args.as_mut_slice() {
772        [RuntimeValue::String(s)] => convert::url_encode(s),
773        [node @ RuntimeValue::Markdown(_, _)] => node
774            .markdown_node()
775            .map(|md| {
776                convert::url_encode(md.value().as_str()).and_then(|o| match o {
777                    RuntimeValue::String(s) => Ok(node.update_markdown_value(&s)),
778                    a => Err(Error::InvalidTypes(ident.to_string(), vec![a.clone()])),
779                })
780            })
781            .unwrap_or_else(|| Ok(RuntimeValue::NONE)),
782        [a] => convert::url_encode(&a.to_string()),
783        _ => unreachable!("url_encode should always receive exactly one argument"),
784    }
785}
786
787#[mq_macros::mq_fn(name = "to_text", params = Fixed(1))]
788fn to_text_impl(_: &Ident, _: &RuntimeValue, args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
789    match args.first() {
790        Some(value) => convert::to_text(value),
791        None => unreachable!("to_text should always receive exactly one argument"),
792    }
793}
794
795#[mq_macros::mq_fn(name = "ends_with", params = Fixed(2))]
796fn ends_with_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, env: &SharedEnv) -> Result<RuntimeValue, Error> {
797    match args.as_mut_slice() {
798        [node @ RuntimeValue::Markdown(_, _), RuntimeValue::String(s)] => node
799            .markdown_node()
800            .map(|md| Ok(md.value().ends_with(&*s).into()))
801            .unwrap_or_else(|| Ok(RuntimeValue::FALSE)),
802        [RuntimeValue::String(s1), RuntimeValue::String(s2)] => Ok(s1.ends_with(&*s2).into()),
803        [RuntimeValue::Bytes(b1), RuntimeValue::Bytes(b2)] => Ok(b1.ends_with(b2).into()),
804        [RuntimeValue::Array(array), RuntimeValue::String(s)] => Ok(array
805            .last()
806            .map_or(Ok(RuntimeValue::FALSE), |o| {
807                eval_builtin(o, ident, vec![RuntimeValue::String(std::mem::take(s))], env)
808            })
809            .unwrap_or(RuntimeValue::FALSE)),
810        [RuntimeValue::None, RuntimeValue::String(_)] => Ok(RuntimeValue::FALSE),
811        [a, b] => Err(Error::InvalidTypes(
812            ident.to_string(),
813            vec![std::mem::take(a), std::mem::take(b)],
814        )),
815        _ => unreachable!("ends_with should always receive exactly two arguments"),
816    }
817}
818
819#[mq_macros::mq_fn(name = "starts_with", params = Fixed(2))]
820fn starts_with_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, env: &SharedEnv) -> Result<RuntimeValue, Error> {
821    match args.as_mut_slice() {
822        [node @ RuntimeValue::Markdown(_, _), RuntimeValue::String(s)] => node
823            .markdown_node()
824            .map(|md| Ok(md.value().starts_with(&*s).into()))
825            .unwrap_or_else(|| Ok(RuntimeValue::FALSE)),
826        [RuntimeValue::String(s1), RuntimeValue::String(s2)] => Ok(s1.starts_with(&*s2).into()),
827        [RuntimeValue::Bytes(b1), RuntimeValue::Bytes(b2)] => Ok(b1.starts_with(b2).into()),
828        [RuntimeValue::Array(array), RuntimeValue::String(s)] => Ok(array
829            .first()
830            .map_or(Ok(RuntimeValue::FALSE), |o| {
831                eval_builtin(o, ident, vec![RuntimeValue::String(std::mem::take(s))], env)
832            })
833            .unwrap_or(RuntimeValue::FALSE)),
834        [RuntimeValue::None, RuntimeValue::String(_)] => Ok(RuntimeValue::FALSE),
835        [a, b] => Err(Error::InvalidTypes(
836            ident.to_string(),
837            vec![std::mem::take(a), std::mem::take(b)],
838        )),
839        _ => unreachable!("starts_with should always receive exactly two arguments"),
840    }
841}
842
843#[mq_macros::mq_fn(name = "regex_match", params = Fixed(2))]
844fn regex_match_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
845    match args.as_mut_slice() {
846        [RuntimeValue::String(s), RuntimeValue::String(pattern)] => match_re(s, pattern),
847        [node @ RuntimeValue::Markdown(_, _), RuntimeValue::String(pattern)] => node
848            .markdown_node()
849            .map(|md| match_re(&md.value(), pattern))
850            .unwrap_or_else(|| Ok(RuntimeValue::EMPTY_ARRAY)),
851        [RuntimeValue::None, RuntimeValue::String(_)] => Ok(RuntimeValue::EMPTY_ARRAY),
852        [a, b] => Err(Error::InvalidTypes(
853            ident.to_string(),
854            vec![std::mem::take(a), std::mem::take(b)],
855        )),
856        _ => unreachable!("regex_match should always receive exactly two arguments"),
857    }
858}
859
860#[mq_macros::mq_fn(name = "is_regex_match", params = Fixed(2))]
861fn is_regex_match_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
862    match args.as_mut_slice() {
863        [RuntimeValue::String(s), RuntimeValue::String(pattern)] => is_match_re(s, pattern),
864        [node @ RuntimeValue::Markdown(_, _), RuntimeValue::String(pattern)] => node
865            .markdown_node()
866            .map(|md| is_match_re(&md.value(), pattern))
867            .unwrap_or_else(|| Ok(RuntimeValue::FALSE)),
868        [RuntimeValue::None, RuntimeValue::String(_)] => Ok(RuntimeValue::FALSE),
869        [a, b] => Err(Error::InvalidTypes(
870            ident.to_string(),
871            vec![std::mem::take(a), std::mem::take(b)],
872        )),
873        _ => unreachable!("is_regex_match should always receive exactly two arguments"),
874    }
875}
876
877#[mq_macros::mq_fn(name = "is_not_regex_match", params = Fixed(2))]
878fn is_not_regex_match_impl(_: &Ident, _: &RuntimeValue, args: Args, env: &SharedEnv) -> Result<RuntimeValue, Error> {
879    eval_builtin(
880        &RuntimeValue::NONE,
881        &Ident::new(constants::builtins::IS_REGEX_MATCH),
882        args,
883        env,
884    )
885    .map(|result| result.negated())
886}
887
888#[mq_macros::mq_fn(name = "capture", params = Fixed(2))]
889fn capture_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
890    match args.as_mut_slice() {
891        [RuntimeValue::String(s), RuntimeValue::String(pattern)] => capture_re(s, pattern),
892        [node @ RuntimeValue::Markdown(_, _), RuntimeValue::String(pattern)] => node
893            .markdown_node()
894            .map(|md| capture_re(&md.value(), pattern))
895            .unwrap_or_else(|| Ok(RuntimeValue::new_dict())),
896        [RuntimeValue::None, RuntimeValue::String(_)] => Ok(RuntimeValue::new_dict()),
897        [a, b] => Err(Error::InvalidTypes(
898            ident.to_string(),
899            vec![std::mem::take(a), std::mem::take(b)],
900        )),
901        _ => unreachable!("capture should always receive exactly two arguments"),
902    }
903}
904
905#[mq_macros::mq_fn(name = "downcase", params = Fixed(1))]
906fn downcase_impl(_: &Ident, _: &RuntimeValue, args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
907    match args.as_slice() {
908        [node @ RuntimeValue::Markdown(_, _)] => node
909            .markdown_node()
910            .map(|md| Ok(node.update_markdown_value(md.value().to_lowercase().as_str())))
911            .unwrap_or_else(|| Ok(RuntimeValue::NONE)),
912        [RuntimeValue::String(s)] => Ok(s.to_lowercase().into()),
913        _ => Ok(RuntimeValue::NONE),
914    }
915}
916
917#[mq_macros::mq_fn(name = "gsub", params = Fixed(3))]
918fn gsub_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
919    match args.as_mut_slice() {
920        [
921            RuntimeValue::String(s1),
922            RuntimeValue::String(s2),
923            RuntimeValue::String(s3),
924        ] => Ok(replace_re(s1, s2, s3)?),
925        [
926            node @ RuntimeValue::Markdown(_, _),
927            RuntimeValue::String(s1),
928            RuntimeValue::String(s2),
929        ] => node
930            .markdown_node()
931            .map(|md| Ok(node.update_markdown_value(&replace_re(md.value().as_str(), &*s1, &*s2)?.to_string())))
932            .unwrap_or_else(|| Ok(RuntimeValue::NONE)),
933        [RuntimeValue::None, _, _] => Ok(RuntimeValue::NONE),
934        [a, b, c] => Err(Error::InvalidTypes(
935            ident.to_string(),
936            vec![std::mem::take(a), std::mem::take(b), std::mem::take(c)],
937        )),
938        _ => unreachable!("gsub should always receive exactly three arguments"),
939    }
940}
941
942#[mq_macros::mq_fn(name = "replace", params = Fixed(3))]
943fn replace_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
944    match args.as_mut_slice() {
945        [
946            RuntimeValue::String(s1),
947            RuntimeValue::String(s2),
948            RuntimeValue::String(s3),
949        ] => Ok(s1.replace(&*s2, &*s3).into()),
950        [
951            node @ RuntimeValue::Markdown(_, _),
952            RuntimeValue::String(s1),
953            RuntimeValue::String(s2),
954        ] => node
955            .markdown_node()
956            .map(|md| Ok(node.update_markdown_value(md.value().replace(&*s1, &*s2).as_str())))
957            .unwrap_or_else(|| Ok(RuntimeValue::NONE)),
958        [RuntimeValue::None, RuntimeValue::String(_), RuntimeValue::String(_)] => Ok(RuntimeValue::NONE),
959        [a, b, c] => Err(Error::InvalidTypes(
960            ident.to_string(),
961            vec![std::mem::take(a), std::mem::take(b), std::mem::take(c)],
962        )),
963        _ => unreachable!("replace should always receive exactly three arguments"),
964    }
965}
966
967#[mq_macros::mq_fn(name = "repeat", params = Fixed(2))]
968fn repeat_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
969    match args.as_mut_slice() {
970        [v, RuntimeValue::Number(n)] => repeat(v, n.value() as usize),
971        [a, b] => Err(Error::InvalidTypes(
972            ident.to_string(),
973            vec![std::mem::take(a), std::mem::take(b)],
974        )),
975        _ => unreachable!("repeat should always receive exactly two arguments"),
976    }
977}
978
979#[mq_macros::mq_fn(name = "explode", params = Fixed(1))]
980fn explode_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
981    match args.as_mut_slice() {
982        [RuntimeValue::String(s)] => Ok(RuntimeValue::Array(
983            s.chars()
984                .map(|c| RuntimeValue::Number((c as u32).into()))
985                .collect::<Vec<_>>(),
986        )),
987        [node @ RuntimeValue::Markdown(_, _)] => Ok(RuntimeValue::Array(
988            node.markdown_node()
989                .map(|md| {
990                    md.value()
991                        .chars()
992                        .map(|c| RuntimeValue::Number((c as u32).into()))
993                        .collect::<Vec<_>>()
994                })
995                .unwrap_or_default(),
996        )),
997        [a] => Err(Error::InvalidTypes(ident.to_string(), vec![std::mem::take(a)])),
998        _ => unreachable!("explode should always receive exactly one argument"),
999    }
1000}
1001
1002#[mq_macros::mq_fn(name = "implode", params = Fixed(1))]
1003fn implode_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
1004    match args.as_mut_slice() {
1005        [RuntimeValue::Array(array)] => {
1006            let result: String = array
1007                .iter()
1008                .map(|o| match o {
1009                    RuntimeValue::Number(n) => std::char::from_u32(n.value() as u32).unwrap_or_default().to_string(),
1010                    _ => "".to_string(),
1011                })
1012                .collect();
1013            Ok(result.into())
1014        }
1015        [a] => Err(Error::InvalidTypes(ident.to_string(), vec![std::mem::take(a)])),
1016        _ => unreachable!("implode should always receive exactly one argument"),
1017    }
1018}
1019
1020#[mq_macros::mq_fn(name = "trim", params = Fixed(1))]
1021fn trim_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
1022    match args.as_mut_slice() {
1023        [RuntimeValue::String(s)] => Ok(s.trim().to_string().into()),
1024        [node @ RuntimeValue::Markdown(_, _)] => node
1025            .markdown_node()
1026            .map(|md| Ok(node.update_markdown_value(md.to_string().trim())))
1027            .unwrap_or_else(|| Ok(RuntimeValue::NONE)),
1028        [RuntimeValue::None] => Ok(RuntimeValue::NONE),
1029        [a] => Err(Error::InvalidTypes(ident.to_string(), vec![std::mem::take(a)])),
1030        _ => unreachable!("trim should always receive exactly one argument"),
1031    }
1032}
1033
1034#[mq_macros::mq_fn(name = "ltrim", params = Fixed(1))]
1035fn ltrim_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
1036    match args.as_mut_slice() {
1037        [RuntimeValue::String(s)] => Ok(s.trim_start().to_string().into()),
1038        [node @ RuntimeValue::Markdown(_, _)] => node
1039            .markdown_node()
1040            .map(|md| Ok(node.update_markdown_value(md.to_string().trim_start())))
1041            .unwrap_or_else(|| Ok(RuntimeValue::NONE)),
1042        [RuntimeValue::None] => Ok(RuntimeValue::NONE),
1043        [a] => Err(Error::InvalidTypes(ident.to_string(), vec![std::mem::take(a)])),
1044        _ => unreachable!("ltrim should always receive exactly one argument"),
1045    }
1046}
1047
1048#[mq_macros::mq_fn(name = "rtrim", params = Fixed(1))]
1049fn rtrim_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
1050    match args.as_mut_slice() {
1051        [RuntimeValue::String(s)] => Ok(s.trim_end().to_string().into()),
1052        [node @ RuntimeValue::Markdown(_, _)] => node
1053            .markdown_node()
1054            .map(|md| Ok(node.update_markdown_value(md.to_string().trim_end())))
1055            .unwrap_or_else(|| Ok(RuntimeValue::NONE)),
1056        [RuntimeValue::None] => Ok(RuntimeValue::NONE),
1057        [a] => Err(Error::InvalidTypes(ident.to_string(), vec![std::mem::take(a)])),
1058        _ => unreachable!("rtrim should always receive exactly one argument"),
1059    }
1060}
1061
1062#[mq_macros::mq_fn(name = "upcase", params = Fixed(1))]
1063fn upcase_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
1064    match args.as_mut_slice() {
1065        [node @ RuntimeValue::Markdown(_, _)] => node
1066            .markdown_node()
1067            .map(|md| Ok(node.update_markdown_value(md.value().to_uppercase().as_str())))
1068            .unwrap_or_else(|| Ok(RuntimeValue::NONE)),
1069        [RuntimeValue::String(s)] => Ok(s.to_uppercase().into()),
1070        [RuntimeValue::None] => Ok(RuntimeValue::NONE),
1071        [a] => Err(Error::InvalidTypes(ident.to_string(), vec![std::mem::take(a)])),
1072        _ => unreachable!("upcase should always receive exactly one argument"),
1073    }
1074}
1075
1076#[mq_macros::mq_fn(name = "update", params = Fixed(2))]
1077fn update_impl(_: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
1078    match args.as_mut_slice() {
1079        [
1080            node1 @ RuntimeValue::Markdown(_, _),
1081            node2 @ RuntimeValue::Markdown(_, _),
1082        ] => node2
1083            .markdown_node()
1084            .map(|md| Ok(node1.update_markdown_value(&md.value())))
1085            .unwrap_or_else(|| Ok(RuntimeValue::NONE)),
1086        [RuntimeValue::Markdown(node_value, _), RuntimeValue::String(s)] => Ok(node_value.with_value(s).into()),
1087        [RuntimeValue::None, _] => Ok(RuntimeValue::NONE),
1088        [_, a] => Ok(std::mem::take(a)),
1089        _ => unreachable!("update should always receive exactly two arguments"),
1090    }
1091}
1092
1093#[mq_macros::mq_fn(name = "slice", params = Fixed(3))]
1094fn slice_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
1095    match args.as_mut_slice() {
1096        [
1097            RuntimeValue::String(s),
1098            RuntimeValue::Number(start),
1099            RuntimeValue::Number(end),
1100        ] => {
1101            let chars: Vec<char> = s.chars().collect();
1102            let len = chars.len();
1103            let start = start.value() as isize;
1104            let end = end.value() as isize;
1105
1106            let real_start = if start < 0 {
1107                (len as isize + start).max(0) as usize
1108            } else {
1109                (start as usize).min(len)
1110            };
1111
1112            let real_end = if end < 0 {
1113                (len as isize + end).max(0) as usize
1114            } else {
1115                (end as usize).min(len)
1116            };
1117
1118            if real_start >= len || real_end <= real_start {
1119                return Ok("".into());
1120            }
1121
1122            let sub: String = chars[real_start..real_end].iter().collect();
1123            Ok(sub.into())
1124        }
1125        [
1126            RuntimeValue::Array(arrays),
1127            RuntimeValue::Number(start),
1128            RuntimeValue::Number(end),
1129        ] => {
1130            let len = arrays.len();
1131            let start = start.value() as isize;
1132            let end = end.value() as isize;
1133
1134            let real_start = if start < 0 {
1135                (len as isize + start).max(0) as usize
1136            } else {
1137                (start as usize).min(len)
1138            };
1139            let real_end = if end < 0 {
1140                (len as isize + end).max(0) as usize
1141            } else {
1142                (end as usize).min(len)
1143            };
1144
1145            if real_start >= len || real_end <= real_start {
1146                return Ok(RuntimeValue::EMPTY_ARRAY);
1147            }
1148
1149            Ok(RuntimeValue::Array(arrays[real_start..real_end].to_vec()))
1150        }
1151        [
1152            node @ RuntimeValue::Markdown(_, _),
1153            RuntimeValue::Number(start),
1154            RuntimeValue::Number(end),
1155        ] => node
1156            .markdown_node()
1157            .map(|md| {
1158                let chars: Vec<char> = md.value().chars().collect();
1159                let len = chars.len();
1160                let start = start.value() as isize;
1161                let end = end.value() as isize;
1162
1163                let real_start = if start < 0 {
1164                    (len as isize + start).max(0) as usize
1165                } else {
1166                    (start as usize).min(len)
1167                };
1168                let real_end = if end < 0 {
1169                    (len as isize + end).max(0) as usize
1170                } else {
1171                    (end as usize).min(len)
1172                };
1173
1174                if real_start >= len || real_end <= real_start {
1175                    return Ok(node.update_markdown_value(""));
1176                }
1177
1178                let sub: String = chars[real_start..real_end].iter().collect();
1179                Ok(node.update_markdown_value(&sub))
1180            })
1181            .unwrap_or_else(|| Ok(RuntimeValue::NONE)),
1182        [
1183            RuntimeValue::Bytes(b),
1184            RuntimeValue::Number(start),
1185            RuntimeValue::Number(end),
1186        ] => {
1187            let len = b.len();
1188            let start = start.value() as isize;
1189            let end = end.value() as isize;
1190            let real_start = if start < 0 {
1191                (len as isize + start).max(0) as usize
1192            } else {
1193                (start as usize).min(len)
1194            };
1195            let real_end = if end < 0 {
1196                (len as isize + end).max(0) as usize
1197            } else {
1198                (end as usize).min(len)
1199            };
1200            if real_start >= len || real_end <= real_start {
1201                return Ok(RuntimeValue::Bytes(vec![]));
1202            }
1203            Ok(RuntimeValue::Bytes(b[real_start..real_end].to_vec()))
1204        }
1205        [RuntimeValue::None, RuntimeValue::Number(_), RuntimeValue::Number(_)] => Ok(RuntimeValue::NONE),
1206        [a, b, c] => Err(Error::InvalidTypes(
1207            ident.to_string(),
1208            vec![std::mem::take(a), std::mem::take(b), std::mem::take(c)],
1209        )),
1210        _ => unreachable!("slice should always receive exactly three arguments"),
1211    }
1212}
1213
1214#[mq_macros::mq_fn(name = "pow", params = Fixed(2))]
1215fn pow_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
1216    match args.as_mut_slice() {
1217        [RuntimeValue::Number(base), RuntimeValue::Number(exp)] => {
1218            if exp.is_int() && exp.value() >= 0.0 {
1219                Ok(RuntimeValue::Number(
1220                    (base.value() as i64).pow(exp.value() as u32).into(),
1221                ))
1222            } else {
1223                Ok(RuntimeValue::Number(base.value().powf(exp.value()).into()))
1224            }
1225        }
1226        [a, b] => Err(Error::InvalidTypes(
1227            ident.to_string(),
1228            vec![std::mem::take(a), std::mem::take(b)],
1229        )),
1230        _ => unreachable!("pow should always receive exactly two arguments"),
1231    }
1232}
1233
1234#[mq_macros::mq_fn(name = "ln", params = Fixed(1))]
1235fn ln_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
1236    match args.as_mut_slice() {
1237        [RuntimeValue::Number(n)] => Ok(RuntimeValue::Number(n.value().ln().into())),
1238        [a] => Err(Error::InvalidTypes(ident.to_string(), vec![std::mem::take(a)])),
1239        _ => unreachable!("ln should always receive exactly one argument"),
1240    }
1241}
1242
1243#[mq_macros::mq_fn(name = "log10", params = Fixed(1))]
1244fn log10_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
1245    match args.as_mut_slice() {
1246        [RuntimeValue::Number(n)] => Ok(RuntimeValue::Number(n.value().log10().into())),
1247        [a] => Err(Error::InvalidTypes(ident.to_string(), vec![std::mem::take(a)])),
1248        _ => unreachable!("log10 should always receive exactly one argument"),
1249    }
1250}
1251
1252#[mq_macros::mq_fn(name = "sqrt", params = Fixed(1))]
1253fn sqrt_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
1254    match args.as_mut_slice() {
1255        [RuntimeValue::Number(n)] => Ok(RuntimeValue::Number(n.value().sqrt().into())),
1256        [a] => Err(Error::InvalidTypes(ident.to_string(), vec![std::mem::take(a)])),
1257        _ => unreachable!("sqrt should always receive exactly one argument"),
1258    }
1259}
1260
1261#[mq_macros::mq_fn(name = "exp", params = Fixed(1))]
1262fn exp_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
1263    match args.as_mut_slice() {
1264        [RuntimeValue::Number(n)] => Ok(RuntimeValue::Number(n.value().exp().into())),
1265        [a] => Err(Error::InvalidTypes(ident.to_string(), vec![std::mem::take(a)])),
1266        _ => unreachable!("exp should always receive exactly one argument"),
1267    }
1268}
1269
1270#[mq_macros::mq_fn(name = "index", params = Fixed(2))]
1271fn index_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
1272    match args.as_mut_slice() {
1273        [RuntimeValue::String(s1), RuntimeValue::String(s2)] => Ok(RuntimeValue::Number(
1274            (s1.find(s2.as_str()).map(|v| v as isize).unwrap_or_else(|| -1) as i64).into(),
1275        )),
1276        [node @ RuntimeValue::Markdown(_, _), RuntimeValue::String(s)] => node
1277            .markdown_node()
1278            .map(|md| {
1279                Ok(RuntimeValue::Number(
1280                    (md.value().find(&*s).map(|v| v as isize).unwrap_or_else(|| -1) as i64).into(),
1281                ))
1282            })
1283            .unwrap_or_else(|| Ok(RuntimeValue::Number((-1_i64).into()))),
1284        [RuntimeValue::Bytes(haystack), RuntimeValue::Bytes(needle)] => {
1285            let pos = haystack
1286                .windows(needle.len().max(1))
1287                .position(|w| w == needle.as_slice())
1288                .map(|i| i as i64)
1289                .unwrap_or(-1);
1290            Ok(RuntimeValue::Number(pos.into()))
1291        }
1292        [RuntimeValue::Array(array), v] => Ok(array
1293            .iter()
1294            .position(|o| o == v)
1295            .map(|i| RuntimeValue::Number((i as i64).into()))
1296            .unwrap_or(RuntimeValue::Number((-1_i64).into()))),
1297        [RuntimeValue::None, _] => Ok(RuntimeValue::Number((-1_i64).into())),
1298        [a, b] => Err(Error::InvalidTypes(
1299            ident.to_string(),
1300            vec![std::mem::take(a), std::mem::take(b)],
1301        )),
1302        _ => unreachable!("index should always receive exactly two arguments"),
1303    }
1304}
1305
1306#[mq_macros::mq_fn(name = "len", params = Fixed(1))]
1307fn len_impl(_: &Ident, _: &RuntimeValue, args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
1308    match args.as_slice() {
1309        [RuntimeValue::String(s)] => Ok(RuntimeValue::Number(s.chars().count().into())),
1310        [node @ RuntimeValue::Markdown(_, _)] => node
1311            .markdown_node()
1312            .map(|md| Ok(RuntimeValue::Number(md.value().chars().count().into())))
1313            .unwrap_or_else(|| Ok(RuntimeValue::Number(0.into()))),
1314        [a] => Ok(RuntimeValue::Number(a.len().into())),
1315        _ => unreachable!("len should always receive exactly one argument"),
1316    }
1317}
1318
1319#[mq_macros::mq_fn(name = "utf8bytelen", params = Fixed(1))]
1320fn utf8bytelen_impl(_: &Ident, _: &RuntimeValue, args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
1321    match args.as_slice() {
1322        [a] => Ok(RuntimeValue::Number(a.len().into())),
1323        _ => unreachable!("utf8bytelen should always receive exactly one argument"),
1324    }
1325}
1326
1327#[mq_macros::mq_fn(name = "rindex", params = Fixed(2))]
1328fn rindex_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
1329    match args.as_mut_slice() {
1330        [RuntimeValue::String(s1), RuntimeValue::String(s2)] => Ok(RuntimeValue::Number(
1331            s1.rfind(&*s2).map(|v| v as isize).unwrap_or_else(|| -1).into(),
1332        )),
1333        [node @ RuntimeValue::Markdown(_, _), RuntimeValue::String(s)] => node
1334            .markdown_node()
1335            .map(|md| {
1336                Ok(RuntimeValue::Number(
1337                    md.value().rfind(&*s).map(|v| v as isize).unwrap_or_else(|| -1).into(),
1338                ))
1339            })
1340            .unwrap_or_else(|| Ok(RuntimeValue::Number((-1_i64).into()))),
1341        [RuntimeValue::Bytes(haystack), RuntimeValue::Bytes(needle)] => {
1342            let nlen = needle.len().max(1);
1343            let pos = haystack
1344                .windows(nlen)
1345                .rposition(|w| w == needle.as_slice())
1346                .map(|i| i as i64)
1347                .unwrap_or(-1);
1348            Ok(RuntimeValue::Number(pos.into()))
1349        }
1350        [RuntimeValue::Array(array), RuntimeValue::String(s)] => Ok(array
1351            .iter()
1352            .rposition(|o| match o {
1353                RuntimeValue::String(s1) => s1 == s,
1354                _ => false,
1355            })
1356            .map(|i| RuntimeValue::Number(i.into()))
1357            .unwrap_or(RuntimeValue::Number((-1_i64).into()))),
1358        [RuntimeValue::None, RuntimeValue::String(_)] => Ok(RuntimeValue::Number((-1_i64).into())),
1359        [a, b] => Err(Error::InvalidTypes(
1360            ident.to_string(),
1361            vec![std::mem::take(a), std::mem::take(b)],
1362        )),
1363        _ => unreachable!("rindex should always receive exactly two arguments"),
1364    }
1365}
1366
1367#[mq_macros::mq_fn(name = "range", params = Range(1, 3))]
1368fn range_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
1369    match args.as_mut_slice() {
1370        // Numeric range: range(end)
1371        [RuntimeValue::Number(end)] => {
1372            let end_val = end.value() as isize;
1373            generate_numeric_range(0, end_val, 1).map(RuntimeValue::Array)
1374        }
1375        // Numeric range: range(start, end)
1376        [RuntimeValue::Number(start), RuntimeValue::Number(end)] => {
1377            let start_val = start.value() as isize;
1378            let end_val = end.value() as isize;
1379            let step = if start_val <= end_val { 1 } else { -1 };
1380            generate_numeric_range(start_val, end_val, step).map(RuntimeValue::Array)
1381        }
1382        // Numeric range: range(start, end, step)
1383        [
1384            RuntimeValue::Number(start),
1385            RuntimeValue::Number(end),
1386            RuntimeValue::Number(step),
1387        ] => {
1388            let start_val = start.value() as isize;
1389            let end_val = end.value() as isize;
1390            let step_val = step.value() as isize;
1391            generate_numeric_range(start_val, end_val, step_val).map(RuntimeValue::Array)
1392        }
1393        // String range: range("a", "z") or range("A", "Z") or range("aa", "zz")
1394        [RuntimeValue::String(start), RuntimeValue::String(end)] => {
1395            let start_chars: Vec<char> = start.chars().collect();
1396            let end_chars: Vec<char> = end.chars().collect();
1397
1398            if start_chars.len() == 1 && end_chars.len() == 1 {
1399                generate_char_range(start_chars[0], end_chars[0], None).map(RuntimeValue::Array)
1400            } else {
1401                generate_multi_char_range(start, end).map(RuntimeValue::Array)
1402            }
1403        }
1404        // String range with step: range("a", "z", step)
1405        [
1406            RuntimeValue::String(start),
1407            RuntimeValue::String(end),
1408            RuntimeValue::Number(step),
1409        ] => {
1410            let start_chars: Vec<char> = start.chars().collect();
1411            let end_chars: Vec<char> = end.chars().collect();
1412
1413            if start_chars.len() == 1 && end_chars.len() == 1 {
1414                let step_val = step.value() as i32;
1415                generate_char_range(start_chars[0], end_chars[0], Some(step_val)).map(RuntimeValue::Array)
1416            } else {
1417                Err(Error::Runtime(
1418                    "String range with step is only supported for single characters".to_string(),
1419                ))
1420            }
1421        }
1422        _ => Err(Error::InvalidTypes(ident.to_string(), args.to_vec())),
1423    }
1424}
1425
1426#[mq_macros::mq_fn(name = "del", params = Fixed(2))]
1427fn del_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
1428    match args.as_mut_slice() {
1429        [RuntimeValue::Array(array), RuntimeValue::Number(n)] => {
1430            let mut array = std::mem::take(array);
1431            array.remove(n.value() as usize);
1432            Ok(RuntimeValue::Array(array))
1433        }
1434        [RuntimeValue::String(s), RuntimeValue::Number(n)] => {
1435            let mut s = std::mem::take(s).chars().collect::<Vec<_>>();
1436            s.remove(n.value() as usize);
1437            Ok(s.into_iter().collect::<String>().into())
1438        }
1439        [RuntimeValue::None, RuntimeValue::Number(_)] => Ok(RuntimeValue::NONE),
1440        [RuntimeValue::Dict(dict), RuntimeValue::String(key)] => {
1441            let mut dict = std::mem::take(dict);
1442            dict.remove(&Ident::new(key));
1443            Ok(RuntimeValue::Dict(dict))
1444        }
1445        [RuntimeValue::Dict(dict), RuntimeValue::Symbol(key)] => {
1446            let mut dict = std::mem::take(dict);
1447            dict.remove(key);
1448            Ok(RuntimeValue::Dict(dict))
1449        }
1450        [a, b] => Err(Error::InvalidTypes(
1451            ident.to_string(),
1452            vec![std::mem::take(a), std::mem::take(b)],
1453        )),
1454        _ => unreachable!("del should always receive exactly two arguments"),
1455    }
1456}
1457
1458#[mq_macros::mq_fn(name = "join", params = Fixed(2))]
1459fn join_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
1460    match args.as_mut_slice() {
1461        [RuntimeValue::Array(array), RuntimeValue::String(s)] => Ok(array.iter().join(s).into()),
1462        [a, b] => Err(Error::InvalidTypes(
1463            ident.to_string(),
1464            vec![std::mem::take(a), std::mem::take(b)],
1465        )),
1466        _ => unreachable!("join should always receive exactly two arguments"),
1467    }
1468}
1469
1470#[mq_macros::mq_fn(name = "reverse", params = Fixed(1))]
1471fn reverse_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
1472    match args.as_mut_slice() {
1473        [RuntimeValue::Array(array)] => {
1474            let mut vec = std::mem::take(array);
1475            vec.reverse();
1476            Ok(RuntimeValue::Array(vec))
1477        }
1478        [RuntimeValue::String(s)] => Ok(s.chars().rev().collect::<String>().into()),
1479        [RuntimeValue::Bytes(b)] => {
1480            let mut v = std::mem::take(b);
1481            v.reverse();
1482            Ok(RuntimeValue::Bytes(v))
1483        }
1484        [a] => Err(Error::InvalidTypes(ident.to_string(), vec![std::mem::take(a)])),
1485        _ => unreachable!("reverse should always receive exactly one argument"),
1486    }
1487}
1488
1489#[mq_macros::mq_fn(name = "sort", params = Fixed(1))]
1490fn sort_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
1491    match args.as_mut_slice() {
1492        [RuntimeValue::Array(array)] => {
1493            let mut vec = std::mem::take(array);
1494            vec.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
1495
1496            let vec = vec
1497                .into_iter()
1498                .map(|v| match v {
1499                    RuntimeValue::Markdown(mut node, s) => {
1500                        node.set_position(None);
1501                        RuntimeValue::Markdown(node, s)
1502                    }
1503                    _ => v,
1504                })
1505                .collect();
1506            Ok(RuntimeValue::Array(vec))
1507        }
1508        [a] => Err(Error::InvalidTypes(ident.to_string(), vec![std::mem::take(a)])),
1509        _ => unreachable!("sort should always receive exactly one argument"),
1510    }
1511}
1512
1513#[mq_macros::mq_fn(name = "_sort_by_impl", params = Fixed(1))]
1514fn _sort_by_impl_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
1515    match args.as_mut_slice() {
1516        [RuntimeValue::Array(array)] => {
1517            let mut vec = std::mem::take(array);
1518            vec.sort_by(|a, b| match (a, b) {
1519                (RuntimeValue::Array(a1), RuntimeValue::Array(a2)) => a1
1520                    .first()
1521                    .unwrap()
1522                    .partial_cmp(a2.first().unwrap())
1523                    .unwrap_or(std::cmp::Ordering::Equal),
1524                _ => unreachable!("_sort_by_impl should only be called with an array of arrays"),
1525            });
1526            let vec = vec
1527                .into_iter()
1528                .map(|v| match v {
1529                    RuntimeValue::Array(mut arr) if arr.len() >= 2 => {
1530                        if let RuntimeValue::Markdown(node, s) = &arr[1] {
1531                            let mut new_node = node.clone();
1532                            new_node.set_position(None);
1533
1534                            arr[1] = RuntimeValue::Markdown(new_node, s.clone());
1535                            RuntimeValue::Array(arr)
1536                        } else {
1537                            RuntimeValue::Array(arr)
1538                        }
1539                    }
1540                    _ => unreachable!("_sort_by_impl should only be called with an array of arrays"),
1541                })
1542                .collect();
1543
1544            Ok(RuntimeValue::Array(vec))
1545        }
1546        [a] => Err(Error::InvalidTypes(ident.to_string(), vec![std::mem::take(a)])),
1547        _ => unreachable!("_sort_by_impl should always receive exactly one argument"),
1548    }
1549}
1550
1551#[mq_macros::mq_fn(name = "compact", params = Fixed(1))]
1552fn compact_impl(_: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
1553    match args.as_mut_slice() {
1554        [RuntimeValue::Array(array)] => Ok(RuntimeValue::Array(
1555            std::mem::take(array)
1556                .into_iter()
1557                .filter(|v| !v.is_none())
1558                .collect::<Vec<_>>(),
1559        )),
1560        [a] => Ok(std::mem::take(a)),
1561        _ => unreachable!("compact should always receive exactly one argument"),
1562    }
1563}
1564
1565#[mq_macros::mq_fn(name = "split", params = Fixed(2))]
1566fn split_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
1567    match args.as_mut_slice() {
1568        [RuntimeValue::String(s1), RuntimeValue::String(s2)] => Ok(split_re(s1, s2)?),
1569        [node @ RuntimeValue::Markdown(_, _), RuntimeValue::String(s)] => node
1570            .markdown_node()
1571            .map(|md| split_re(md.value().as_str(), s))
1572            .unwrap_or_else(|| Ok(RuntimeValue::NONE)),
1573        [RuntimeValue::Array(array), v] => {
1574            if array.is_empty() {
1575                return Ok(RuntimeValue::Array(vec![RuntimeValue::Array(Vec::new())]));
1576            }
1577
1578            let mut positions = Vec::new();
1579            for (i, a) in array.iter().enumerate() {
1580                if a == v {
1581                    positions.push(i);
1582                }
1583            }
1584
1585            if positions.is_empty() {
1586                return Ok(RuntimeValue::Array(vec![RuntimeValue::Array(std::mem::take(array))]));
1587            }
1588
1589            let mut result = Vec::with_capacity(positions.len() + 1);
1590            let mut start = 0;
1591
1592            for pos in positions {
1593                result.push(RuntimeValue::Array(array[start..pos].to_vec()));
1594                start = pos + 1;
1595            }
1596
1597            if start < array.len() {
1598                result.push(RuntimeValue::Array(array[start..].to_vec()));
1599            }
1600
1601            Ok(RuntimeValue::Array(result))
1602        }
1603        [RuntimeValue::None, RuntimeValue::String(_)] => Ok(RuntimeValue::EMPTY_ARRAY),
1604        [a, b] => Err(Error::InvalidTypes(
1605            ident.to_string(),
1606            vec![std::mem::take(a), std::mem::take(b)],
1607        )),
1608        _ => unreachable!("split should always receive exactly two arguments"),
1609    }
1610}
1611
1612#[mq_macros::mq_fn(name = "uniq", params = Fixed(1))]
1613fn uniq_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
1614    match args.as_mut_slice() {
1615        [RuntimeValue::Array(array)] => {
1616            let mut vec = std::mem::take(array);
1617            let mut seen = FxHashSet::default();
1618            vec.retain(|item| seen.insert(item.to_string()));
1619            Ok(RuntimeValue::Array(vec))
1620        }
1621        [a] => Err(Error::InvalidTypes(ident.to_string(), vec![std::mem::take(a)])),
1622        _ => unreachable!("uniq should always receive exactly one argument"),
1623    }
1624}
1625
1626#[mq_macros::mq_fn(name = "ceil", params = Fixed(1))]
1627fn ceil_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
1628    match args.as_mut_slice() {
1629        [RuntimeValue::Number(n)] => Ok(RuntimeValue::Number(n.value().ceil().into())),
1630        [a] => Err(Error::InvalidTypes(ident.to_string(), vec![std::mem::take(a)])),
1631        _ => unreachable!("ceil should always receive exactly one argument"),
1632    }
1633}
1634
1635#[mq_macros::mq_fn(name = "floor", params = Fixed(1))]
1636fn floor_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
1637    match args.as_mut_slice() {
1638        [RuntimeValue::Number(n)] => Ok(RuntimeValue::Number(n.value().floor().into())),
1639        [a] => Err(Error::InvalidTypes(ident.to_string(), vec![std::mem::take(a)])),
1640        _ => unreachable!("floor should always receive exactly one argument"),
1641    }
1642}
1643
1644#[mq_macros::mq_fn(name = "round", params = Fixed(1))]
1645fn round_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
1646    match args.as_mut_slice() {
1647        [RuntimeValue::Number(n)] => Ok(RuntimeValue::Number(n.value().round().into())),
1648        [a] => Err(Error::InvalidTypes(ident.to_string(), vec![std::mem::take(a)])),
1649        _ => unreachable!("round should always receive exactly one argument"),
1650    }
1651}
1652
1653#[mq_macros::mq_fn(name = "trunc", params = Fixed(1))]
1654fn trunc_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
1655    match args.as_mut_slice() {
1656        [RuntimeValue::Number(n)] => Ok(RuntimeValue::Number(n.value().trunc().into())),
1657        [a] => Err(Error::InvalidTypes(ident.to_string(), vec![std::mem::take(a)])),
1658        _ => unreachable!("trunc should always receive exactly one argument"),
1659    }
1660}
1661
1662#[mq_macros::mq_fn(name = "abs", params = Fixed(1))]
1663fn abs_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
1664    match args.as_mut_slice() {
1665        [RuntimeValue::Number(n)] => Ok(RuntimeValue::Number(n.value().abs().into())),
1666        [a] => Err(Error::InvalidTypes(ident.to_string(), vec![std::mem::take(a)])),
1667        _ => unreachable!("abs should always receive exactly one argument"),
1668    }
1669}
1670
1671#[mq_macros::mq_fn(name = "eq", params = Fixed(2))]
1672fn eq_impl(_: &Ident, _: &RuntimeValue, args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
1673    match args.as_slice() {
1674        [a, b] => Ok((a == b).into()),
1675        _ => unreachable!("eq should always receive exactly two arguments"),
1676    }
1677}
1678
1679#[mq_macros::mq_fn(name = "ne", params = Fixed(2))]
1680fn ne_impl(_: &Ident, _: &RuntimeValue, args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
1681    match args.as_slice() {
1682        [a, b] => Ok((a != b).into()),
1683        _ => unreachable!("ne should always receive exactly two arguments"),
1684    }
1685}
1686
1687#[mq_macros::mq_fn(name = "gt", params = Fixed(2))]
1688fn gt_impl(_: &Ident, _: &RuntimeValue, args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
1689    match args.as_slice() {
1690        [RuntimeValue::String(s1), RuntimeValue::String(s2)] => Ok((s1 > s2).into()),
1691        [RuntimeValue::Symbol(s1), RuntimeValue::Symbol(s2)] => Ok((s1 > s2).into()),
1692        [RuntimeValue::Number(n1), RuntimeValue::Number(n2)] => Ok((n1 > n2).into()),
1693        [RuntimeValue::Boolean(b1), RuntimeValue::Boolean(b2)] => Ok((b1 > b2).into()),
1694        [RuntimeValue::Bytes(b1), RuntimeValue::Bytes(b2)] => Ok((b1 > b2).into()),
1695        [RuntimeValue::Markdown(n1, _), RuntimeValue::Markdown(n2, _)] => Ok((n1 > n2).into()),
1696        [_, _] => Ok(RuntimeValue::FALSE),
1697        _ => unreachable!("gt should always receive exactly two arguments"),
1698    }
1699}
1700
1701#[mq_macros::mq_fn(name = "gte", params = Fixed(2))]
1702fn gte_impl(_: &Ident, _: &RuntimeValue, args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
1703    match args.as_slice() {
1704        [RuntimeValue::String(s1), RuntimeValue::String(s2)] => Ok((s1 >= s2).into()),
1705        [RuntimeValue::Symbol(s1), RuntimeValue::Symbol(s2)] => Ok((s1 >= s2).into()),
1706        [RuntimeValue::Number(n1), RuntimeValue::Number(n2)] => Ok((n1 >= n2).into()),
1707        [RuntimeValue::Boolean(b1), RuntimeValue::Boolean(b2)] => Ok((b1 >= b2).into()),
1708        [RuntimeValue::Bytes(b1), RuntimeValue::Bytes(b2)] => Ok((b1 >= b2).into()),
1709        [RuntimeValue::Markdown(n1, _), RuntimeValue::Markdown(n2, _)] => Ok((n1 >= n2).into()),
1710        [_, _] => Ok(RuntimeValue::FALSE),
1711        _ => unreachable!("gte should always receive exactly two arguments"),
1712    }
1713}
1714
1715#[mq_macros::mq_fn(name = "lt", params = Fixed(2))]
1716fn lt_impl(_: &Ident, _: &RuntimeValue, args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
1717    match args.as_slice() {
1718        [RuntimeValue::String(s1), RuntimeValue::String(s2)] => Ok((s1 < s2).into()),
1719        [RuntimeValue::Symbol(s1), RuntimeValue::Symbol(s2)] => Ok((s1 < s2).into()),
1720        [RuntimeValue::Number(n1), RuntimeValue::Number(n2)] => Ok((n1 < n2).into()),
1721        [RuntimeValue::Boolean(b1), RuntimeValue::Boolean(b2)] => Ok((b1 < b2).into()),
1722        [RuntimeValue::Bytes(b1), RuntimeValue::Bytes(b2)] => Ok((b1 < b2).into()),
1723        [RuntimeValue::Markdown(n1, _), RuntimeValue::Markdown(n2, _)] => Ok((n1 < n2).into()),
1724        [_, _] => Ok(RuntimeValue::FALSE),
1725        _ => unreachable!("lt should always receive exactly two arguments"),
1726    }
1727}
1728
1729#[mq_macros::mq_fn(name = "lte", params = Fixed(2))]
1730fn lte_impl(_: &Ident, _: &RuntimeValue, args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
1731    match args.as_slice() {
1732        [RuntimeValue::String(s1), RuntimeValue::String(s2)] => Ok((s1 <= s2).into()),
1733        [RuntimeValue::Symbol(s1), RuntimeValue::Symbol(s2)] => Ok((s1 <= s2).into()),
1734        [RuntimeValue::Number(n1), RuntimeValue::Number(n2)] => Ok((n1 <= n2).into()),
1735        [RuntimeValue::Boolean(b1), RuntimeValue::Boolean(b2)] => Ok((b1 <= b2).into()),
1736        [RuntimeValue::Bytes(b1), RuntimeValue::Bytes(b2)] => Ok((b1 <= b2).into()),
1737        [RuntimeValue::Markdown(n1, _), RuntimeValue::Markdown(n2, _)] => Ok((n1 <= n2).into()),
1738        [_, _] => Ok(RuntimeValue::FALSE),
1739        _ => unreachable!("lte should always receive exactly two arguments"),
1740    }
1741}
1742
1743#[mq_macros::mq_fn(name = "add", params = Fixed(2))]
1744fn add_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
1745    match args.as_mut_slice() {
1746        [RuntimeValue::String(s1), RuntimeValue::String(s2)] => {
1747            s1.push_str(s2);
1748            Ok(std::mem::take(s1).into())
1749        }
1750        [RuntimeValue::String(s), RuntimeValue::Number(n)] | [RuntimeValue::Number(n), RuntimeValue::String(s)] => {
1751            s.push_str(n.to_string().as_str());
1752            Ok(std::mem::take(s).into())
1753        }
1754        [node @ RuntimeValue::Markdown(_, _), RuntimeValue::String(s)] => node
1755            .markdown_node()
1756            .map(|md| Ok(node.update_markdown_value(format!("{}{}", md.value(), s).as_str())))
1757            .unwrap_or_else(|| Ok(RuntimeValue::NONE)),
1758        [RuntimeValue::String(s), node @ RuntimeValue::Markdown(_, _)] => node
1759            .markdown_node()
1760            .map(|md| Ok(node.update_markdown_value(format!("{}{}", s, md.value()).as_str())))
1761            .unwrap_or_else(|| Ok(RuntimeValue::NONE)),
1762        [
1763            node1 @ RuntimeValue::Markdown(_, _),
1764            node2 @ RuntimeValue::Markdown(_, _),
1765        ] => Ok(node2
1766            .markdown_node()
1767            .and_then(|md2| {
1768                node1
1769                    .markdown_node()
1770                    .map(|md1| node1.update_markdown_value(format!("{}{}", md1.value(), md2.value()).as_str()))
1771            })
1772            .unwrap_or(RuntimeValue::NONE)),
1773        [RuntimeValue::Number(n1), RuntimeValue::Number(n2)] => Ok((*n1 + *n2).into()),
1774        [RuntimeValue::Bytes(b1), RuntimeValue::Bytes(b2)] => {
1775            let mut result = std::mem::take(b1);
1776            result.extend_from_slice(b2);
1777            Ok(RuntimeValue::Bytes(result))
1778        }
1779        [RuntimeValue::Array(a1), RuntimeValue::Array(a2)] => {
1780            let total_size = a1.len().saturating_add(a2.len());
1781            if total_size > MAX_RANGE_SIZE {
1782                return Err(Error::Runtime(format!(
1783                    "array concatenation size {} exceeds maximum allowed size of {}",
1784                    total_size, MAX_RANGE_SIZE
1785                )));
1786            }
1787            let mut a = std::mem::take(a1);
1788            a.reserve(a2.len());
1789            a.extend_from_slice(a2);
1790            Ok(RuntimeValue::Array(a))
1791        }
1792        [RuntimeValue::Array(a1), a2] => {
1793            let total_size = a1.len().saturating_add(1);
1794            if total_size > MAX_RANGE_SIZE {
1795                return Err(Error::Runtime(format!(
1796                    "array size {} exceeds maximum allowed size of {}",
1797                    total_size, MAX_RANGE_SIZE
1798                )));
1799            }
1800
1801            let mut a = std::mem::take(a1);
1802            a.reserve(1);
1803            a.push(std::mem::take(a2));
1804            Ok(RuntimeValue::Array(a))
1805        }
1806        [v, RuntimeValue::Array(a)] => {
1807            let total_size = a.len().saturating_add(1);
1808            if total_size > MAX_RANGE_SIZE {
1809                return Err(Error::Runtime(format!(
1810                    "array size {} exceeds maximum allowed size of {}",
1811                    total_size, MAX_RANGE_SIZE
1812                )));
1813            }
1814
1815            let mut arr = Vec::with_capacity(total_size);
1816            arr.push(std::mem::take(v));
1817            arr.extend(std::mem::take(a));
1818
1819            Ok(RuntimeValue::Array(arr))
1820        }
1821        [RuntimeValue::Dict(d1), RuntimeValue::Dict(d2)] => {
1822            let mut result = std::mem::take(d1);
1823            result.extend(std::mem::take(d2));
1824            Ok(RuntimeValue::Dict(result))
1825        }
1826        [a, RuntimeValue::None] | [RuntimeValue::None, a] => Ok(std::mem::take(a)),
1827        [a, b] => Err(Error::InvalidTypes(
1828            ident.to_string(),
1829            vec![std::mem::take(a), std::mem::take(b)],
1830        )),
1831        _ => unreachable!("add should always receive exactly two arguments"),
1832    }
1833}
1834
1835#[mq_macros::mq_fn(name = "sub", params = Fixed(2))]
1836fn sub_impl(_: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
1837    match args.as_mut_slice() {
1838        [RuntimeValue::Number(n1), RuntimeValue::Number(n2)] => Ok((*n1 - *n2).into()),
1839        [a, b] => match (convert::to_number(a)?, convert::to_number(b)?) {
1840            (RuntimeValue::Number(n1), RuntimeValue::Number(n2)) => Ok((n1 - n2).into()),
1841            _ => Err(Error::InvalidTypes(
1842                "Both operands could not be converted to numbers: {:?}, {:?}".to_string(),
1843                vec![std::mem::take(a), std::mem::take(b)],
1844            )),
1845        },
1846        _ => unreachable!("sub should always receive exactly two arguments"),
1847    }
1848}
1849
1850#[mq_macros::mq_fn(name = "div", params = Fixed(2))]
1851fn div_impl(_: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
1852    match args.as_mut_slice() {
1853        [RuntimeValue::Number(n1), RuntimeValue::Number(n2)] => {
1854            if n2.is_zero() {
1855                Err(Error::ZeroDivision)
1856            } else {
1857                Ok((*n1 / *n2).into())
1858            }
1859        }
1860        [a, b] => match (convert::to_number(a)?, convert::to_number(b)?) {
1861            (RuntimeValue::Number(n1), RuntimeValue::Number(n2)) => Ok((n1 / n2).into()),
1862            (RuntimeValue::None, _) | (_, RuntimeValue::None) => Ok(RuntimeValue::NONE),
1863            _ => Err(Error::InvalidTypes(
1864                "Both operands could not be converted to numbers: {:?}, {:?}".to_string(),
1865                vec![std::mem::take(a), std::mem::take(b)],
1866            )),
1867        },
1868        _ => unreachable!("div should always receive exactly two arguments"),
1869    }
1870}
1871
1872#[mq_macros::mq_fn(name = "mul", params = Fixed(2))]
1873fn mul_impl(_: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
1874    match args.as_mut_slice() {
1875        [RuntimeValue::Number(n1), RuntimeValue::Number(n2)] => Ok((*n1 * *n2).into()),
1876        [RuntimeValue::Array(array), RuntimeValue::Number(n)]
1877        | [RuntimeValue::Number(n), RuntimeValue::Array(array)] => {
1878            if n.is_int() && n.value() >= 0.0 && n.value() <= MAX_REPEAT_COUNT as f64 {
1879                // Integer multiplication within repeat limit: repeat the array
1880                repeat(&mut RuntimeValue::Array(std::mem::take(array)), n.value() as usize)
1881            } else {
1882                // Non-integer, negative, or too large multiplication: multiply each element
1883                let result: Result<Vec<RuntimeValue>, Error> = std::mem::take(array)
1884                    .into_iter()
1885                    .map(|v| {
1886                        let mut args = vec![v, RuntimeValue::Number(*n)];
1887                        match args.as_mut_slice() {
1888                            [RuntimeValue::Number(n1), RuntimeValue::Number(n2)] => Ok((*n1 * *n2).into()),
1889                            [a, b] => match (convert::to_number(a)?, convert::to_number(b)?) {
1890                                (RuntimeValue::Number(n1), RuntimeValue::Number(n2)) => Ok((n1 * n2).into()),
1891                                (RuntimeValue::None, _) | (_, RuntimeValue::None) => Ok(RuntimeValue::NONE),
1892                                _ => Err(Error::InvalidTypes(
1893                                    constants::builtins::MUL.to_string(),
1894                                    vec![std::mem::take(&mut args[0]), std::mem::take(&mut args[1])],
1895                                )),
1896                            },
1897                            _ => unreachable!("mul should always receive exactly two arguments"),
1898                        }
1899                    })
1900                    .collect();
1901                result.map(RuntimeValue::Array)
1902            }
1903        }
1904        [v, RuntimeValue::Number(n)] | [RuntimeValue::Number(n), v] => {
1905            if n.is_int() && n.value() >= 0.0 {
1906                repeat(v, n.value() as usize)
1907            } else {
1908                Err(Error::InvalidTypes(
1909                    constants::builtins::MUL.to_string(),
1910                    vec![std::mem::take(v), RuntimeValue::Number(*n)],
1911                ))
1912            }
1913        }
1914        [a, b] => match (convert::to_number(a)?, convert::to_number(b)?) {
1915            (RuntimeValue::Number(n1), RuntimeValue::Number(n2)) => Ok((n1 * n2).into()),
1916            (RuntimeValue::None, _) | (_, RuntimeValue::None) => Ok(RuntimeValue::NONE),
1917            _ => Ok(RuntimeValue::Number(0.into())),
1918        },
1919        _ => unreachable!("mul should always receive exactly two arguments"),
1920    }
1921}
1922
1923#[mq_macros::mq_fn(name = "mod", params = Fixed(2))]
1924fn mod_impl(_: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
1925    match args.as_mut_slice() {
1926        [RuntimeValue::Number(n1), RuntimeValue::Number(n2)] => Ok((*n1 % *n2).into()),
1927        [a, b] => match (convert::to_number(a)?, convert::to_number(b)?) {
1928            (RuntimeValue::Number(n1), RuntimeValue::Number(n2)) => Ok((n1 % n2).into()),
1929            _ => Err(Error::InvalidTypes(
1930                "".to_string(),
1931                vec![std::mem::take(a), std::mem::take(b)],
1932            )),
1933        },
1934        _ => unreachable!("mod should always receive exactly two arguments"),
1935    }
1936}
1937
1938#[mq_macros::mq_fn(name = "and", params = Range(2, u8::MAX))]
1939fn and_impl(_: &Ident, _: &RuntimeValue, args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
1940    let mut last_truthy = None;
1941    for arg in args {
1942        if !arg.is_truthy() {
1943            return Ok(RuntimeValue::Boolean(false));
1944        }
1945        let mut arg = arg;
1946        last_truthy = Some(std::mem::take(&mut arg));
1947    }
1948    Ok(last_truthy.unwrap_or(RuntimeValue::Boolean(true)))
1949}
1950
1951#[mq_macros::mq_fn(name = "or", params = Range(2, u8::MAX))]
1952fn or_impl(_: &Ident, _: &RuntimeValue, args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
1953    for arg in args {
1954        if arg.is_truthy() {
1955            let mut arg = arg;
1956            return Ok(std::mem::take(&mut arg));
1957        }
1958    }
1959    Ok(RuntimeValue::Boolean(false))
1960}
1961
1962#[mq_macros::mq_fn(name = "not", params = Fixed(1))]
1963fn not_impl(_: &Ident, _: &RuntimeValue, args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
1964    match args.as_slice() {
1965        [a] => Ok((!a.is_truthy()).into()),
1966        _ => unreachable!("not should always receive exactly one argument"),
1967    }
1968}
1969
1970#[mq_macros::mq_fn(name = "attr", params = Fixed(2))]
1971fn attr_impl(_: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
1972    match args.as_mut_slice() {
1973        [RuntimeValue::Markdown(node, _), RuntimeValue::String(attr)] => {
1974            Ok(node.attr(attr).map(Into::into).unwrap_or(RuntimeValue::NONE))
1975        }
1976        [RuntimeValue::Array(nodes), RuntimeValue::String(attr)] => Ok(nodes
1977            .iter_mut()
1978            .flat_map(|node| match node {
1979                RuntimeValue::Markdown(node, _) => {
1980                    let value = node.attr(attr).map(Into::into).unwrap_or(RuntimeValue::NONE);
1981
1982                    match value {
1983                        RuntimeValue::Array(arr) => arr,
1984                        RuntimeValue::None => Vec::new(),
1985                        v => vec![v],
1986                    }
1987                }
1988                a => vec![std::mem::take(a)],
1989            })
1990            .collect::<Vec<_>>()
1991            .into()),
1992        [a, ..] => Ok(std::mem::take(a)),
1993        _ => unreachable!("attr should always receive at least two arguments"),
1994    }
1995}
1996
1997#[mq_macros::mq_fn(name = "set_attr", params = Fixed(3))]
1998fn set_attr_impl(_: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
1999    match args.as_mut_slice() {
2000        [
2001            RuntimeValue::Markdown(node, selector),
2002            RuntimeValue::String(attr),
2003            value,
2004        ] => {
2005            let mut new_node = std::mem::take(node);
2006            let value = match value {
2007                RuntimeValue::String(s) => mq_markdown::AttrValue::String(s.to_string()),
2008                RuntimeValue::Number(n) => {
2009                    if n.is_int() {
2010                        mq_markdown::AttrValue::Integer(n.value() as i64)
2011                    } else {
2012                        mq_markdown::AttrValue::Number(n.value())
2013                    }
2014                }
2015                RuntimeValue::Boolean(b) => mq_markdown::AttrValue::Boolean(*b),
2016                RuntimeValue::None => mq_markdown::AttrValue::Null,
2017                _ => {
2018                    return Err(Error::InvalidTypes(
2019                        "set_attr".to_string(),
2020                        vec![
2021                            RuntimeValue::Markdown(new_node, selector.take()),
2022                            RuntimeValue::String(attr.clone()),
2023                            std::mem::take(value),
2024                        ],
2025                    ));
2026                }
2027            };
2028            new_node.set_attr(attr, value);
2029            Ok(RuntimeValue::Markdown(new_node, selector.take()))
2030        }
2031        [a, ..] => Ok(std::mem::take(a)),
2032        _ => unreachable!("set_attr should always receive at least three arguments"),
2033    }
2034}
2035
2036#[mq_macros::mq_fn(name = "to_code", params = Fixed(2))]
2037fn to_code_impl(_: &Ident, _: &RuntimeValue, args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
2038    match args.as_slice() {
2039        [a, RuntimeValue::String(lang)] => Ok(mq_markdown::Node::Code(mq_markdown::Code {
2040            value: a.to_string(),
2041            lang: Some(lang.to_string()),
2042            position: None,
2043            meta: None,
2044            fence: true,
2045        })
2046        .into()),
2047        [a, RuntimeValue::None] if !a.is_none() => Ok(mq_markdown::Node::Code(mq_markdown::Code {
2048            value: a.to_string(),
2049            lang: None,
2050            position: None,
2051            meta: None,
2052            fence: true,
2053        })
2054        .into()),
2055        _ => Ok(RuntimeValue::NONE),
2056    }
2057}
2058
2059#[mq_macros::mq_fn(name = "to_code_inline", params = Fixed(1))]
2060fn to_code_inline_impl(_: &Ident, _: &RuntimeValue, args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
2061    match args.as_slice() {
2062        [a] if !a.is_none() => Ok(mq_markdown::Node::CodeInline(mq_markdown::CodeInline {
2063            value: a.to_string().into(),
2064            position: None,
2065        })
2066        .into()),
2067        _ => Ok(RuntimeValue::NONE),
2068    }
2069}
2070
2071#[mq_macros::mq_fn(name = "to_h", params = Fixed(2))]
2072fn to_h_impl(_: &Ident, _: &RuntimeValue, args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
2073    match args.as_slice() {
2074        [RuntimeValue::Markdown(node, _), RuntimeValue::Number(depth)] => {
2075            Ok(mq_markdown::Node::Heading(mq_markdown::Heading {
2076                depth: (*depth).value() as u8,
2077                values: node.node_values(),
2078                position: None,
2079            })
2080            .into())
2081        }
2082        [a, RuntimeValue::Number(depth)] => Ok(mq_markdown::Node::Heading(mq_markdown::Heading {
2083            depth: (*depth).value() as u8,
2084            values: vec![a.to_string().into()],
2085            position: None,
2086        })
2087        .into()),
2088        _ => Ok(RuntimeValue::NONE),
2089    }
2090}
2091
2092#[mq_macros::mq_fn(name = "to_hr", params = Fixed(0))]
2093fn to_hr_impl(_: &Ident, _: &RuntimeValue, _: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
2094    Ok(mq_markdown::Node::HorizontalRule(mq_markdown::HorizontalRule { position: None }).into())
2095}
2096
2097#[mq_macros::mq_fn(name = "to_link", params = Fixed(3))]
2098fn to_link_impl(_: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
2099    match args.as_mut_slice() {
2100        [
2101            RuntimeValue::String(url),
2102            RuntimeValue::String(value),
2103            RuntimeValue::String(title),
2104        ] => Ok(mq_markdown::Node::Link(mq_markdown::Link {
2105            url: mq_markdown::Url::new(url.to_string()),
2106            values: vec![value.to_string().into()],
2107            title: if title.is_empty() {
2108                None
2109            } else {
2110                Some(mq_markdown::Title::new((&*title).into()))
2111            },
2112            position: None,
2113        })
2114        .into()),
2115        _ => Ok(RuntimeValue::NONE),
2116    }
2117}
2118
2119#[mq_macros::mq_fn(name = "to_image", params = Fixed(3))]
2120fn to_image_impl(_: &Ident, _: &RuntimeValue, args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
2121    match args.as_slice() {
2122        [
2123            RuntimeValue::String(url),
2124            RuntimeValue::String(alt),
2125            RuntimeValue::String(title),
2126        ] => Ok(mq_markdown::Node::Image(mq_markdown::Image {
2127            alt: alt.to_string(),
2128            url: url.to_string(),
2129            title: Some(title.to_string()),
2130            position: None,
2131        })
2132        .into()),
2133        _ => Ok(RuntimeValue::NONE),
2134    }
2135}
2136
2137#[mq_macros::mq_fn(name = "to_math", params = Fixed(1))]
2138fn to_math_impl(_: &Ident, _: &RuntimeValue, args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
2139    match args.as_slice() {
2140        [a] => Ok(mq_markdown::Node::Math(mq_markdown::Math {
2141            value: a.to_string(),
2142            position: None,
2143        })
2144        .into()),
2145        _ => Ok(RuntimeValue::NONE),
2146    }
2147}
2148
2149#[mq_macros::mq_fn(name = "to_math_inline", params = Fixed(1))]
2150fn to_math_inline_impl(_: &Ident, _: &RuntimeValue, args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
2151    match args.as_slice() {
2152        [a] => Ok(mq_markdown::Node::MathInline(mq_markdown::MathInline {
2153            value: a.to_string().into(),
2154            position: None,
2155        })
2156        .into()),
2157        _ => Ok(RuntimeValue::NONE),
2158    }
2159}
2160
2161#[mq_macros::mq_fn(name = "to_md_name", params = Fixed(1))]
2162fn to_md_name_impl(_: &Ident, _: &RuntimeValue, args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
2163    match args.as_slice() {
2164        [RuntimeValue::Markdown(node, _)] => Ok(node.name().to_string().into()),
2165        _ => Ok(RuntimeValue::NONE),
2166    }
2167}
2168
2169#[mq_macros::mq_fn(name = "set_list_ordered", params = Fixed(2))]
2170fn set_list_ordered_impl(_: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
2171    match args.as_mut_slice() {
2172        [RuntimeValue::Markdown(node, _), RuntimeValue::Boolean(ordered)]
2173            if matches!(**node, mq_markdown::Node::List(_)) =>
2174        {
2175            let ordered = *ordered;
2176            if let mq_markdown::Node::List(list) = &mut **node {
2177                Ok(mq_markdown::Node::List(mq_markdown::List {
2178                    ordered,
2179                    ..std::mem::take(list)
2180                })
2181                .into())
2182            } else {
2183                unreachable!()
2184            }
2185        }
2186        [a, ..] => Ok(std::mem::take(a)),
2187        _ => Ok(RuntimeValue::NONE),
2188    }
2189}
2190
2191#[mq_macros::mq_fn(name = "to_strong", params = Fixed(1))]
2192fn to_strong_impl(_: &Ident, _: &RuntimeValue, args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
2193    match args.as_slice() {
2194        [RuntimeValue::Markdown(node, _)] => Ok(mq_markdown::Node::Strong(mq_markdown::Strong {
2195            values: node.node_values(),
2196            position: None,
2197        })
2198        .into()),
2199        [a] if !a.is_none() => Ok(mq_markdown::Node::Strong(mq_markdown::Strong {
2200            values: vec![a.to_string().into()],
2201            position: None,
2202        })
2203        .into()),
2204        _ => Ok(RuntimeValue::NONE),
2205    }
2206}
2207
2208#[mq_macros::mq_fn(name = "to_em", params = Fixed(1))]
2209fn to_em_impl(_: &Ident, _: &RuntimeValue, args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
2210    match args.as_slice() {
2211        [RuntimeValue::Markdown(node, _)] => Ok(mq_markdown::Node::Emphasis(mq_markdown::Emphasis {
2212            values: node.node_values(),
2213            position: None,
2214        })
2215        .into()),
2216        [a] if !a.is_none() => Ok(mq_markdown::Node::Emphasis(mq_markdown::Emphasis {
2217            values: vec![a.to_string().into()],
2218            position: None,
2219        })
2220        .into()),
2221        _ => Ok(RuntimeValue::NONE),
2222    }
2223}
2224
2225#[mq_macros::mq_fn(name = "to_md_text", params = Fixed(1))]
2226fn to_md_text_impl(_: &Ident, _: &RuntimeValue, args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
2227    match args.as_slice() {
2228        [a] if !a.is_none() => Ok(mq_markdown::Node::Text(mq_markdown::Text {
2229            value: a.to_string(),
2230            position: None,
2231        })
2232        .into()),
2233        _ => Ok(RuntimeValue::NONE),
2234    }
2235}
2236
2237#[mq_macros::mq_fn(name = "to_md_list", params = Fixed(2))]
2238fn to_md_list_impl(_: &Ident, _: &RuntimeValue, args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
2239    match args.as_slice() {
2240        [RuntimeValue::Markdown(node, _), RuntimeValue::Number(level)] => {
2241            Ok(mq_markdown::Node::List(mq_markdown::List {
2242                values: node.node_values(),
2243                index: 0,
2244                ordered: false,
2245                level: level.value() as u8,
2246                checked: None,
2247                position: None,
2248            })
2249            .into())
2250        }
2251        [a, RuntimeValue::Number(level)] if !a.is_none() => Ok(mq_markdown::Node::List(mq_markdown::List {
2252            values: vec![a.to_string().into()],
2253            index: 0,
2254            ordered: false,
2255            level: level.value() as u8,
2256            checked: None,
2257            position: None,
2258        })
2259        .into()),
2260        _ => Ok(RuntimeValue::NONE),
2261    }
2262}
2263
2264#[mq_macros::mq_fn(name = "to_md_table_row", params = Range(1, u8::MAX))]
2265fn to_md_table_row_impl(_: &Ident, _: &RuntimeValue, args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
2266    let mut current_index = 0;
2267    let values = args
2268        .iter()
2269        .flat_map(|arg| match arg {
2270            RuntimeValue::Array(array) => array
2271                .iter()
2272                .map(move |v| {
2273                    current_index += 1;
2274                    mq_markdown::Node::TableCell(mq_markdown::TableCell {
2275                        row: 0,
2276                        column: current_index - 1,
2277                        values: vec![v.to_string().into()],
2278                        position: None,
2279                    })
2280                })
2281                .collect::<Vec<_>>(),
2282            v => {
2283                current_index += 1;
2284                vec![mq_markdown::Node::TableCell(mq_markdown::TableCell {
2285                    row: 0,
2286                    column: current_index - 1,
2287                    values: vec![v.to_string().into()],
2288                    position: None,
2289                })]
2290            }
2291        })
2292        .collect::<Vec<_>>();
2293
2294    Ok(RuntimeValue::Markdown(
2295        Box::new(mq_markdown::Node::TableRow(mq_markdown::TableRow {
2296            values,
2297            position: None,
2298        })),
2299        None,
2300    ))
2301}
2302
2303#[mq_macros::mq_fn(name = "to_md_table_cell", params = Fixed(3))]
2304fn to_md_table_cell_impl(_: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
2305    match args.as_mut_slice() {
2306        [value, RuntimeValue::Number(row), RuntimeValue::Number(column)] => Ok(RuntimeValue::Markdown(
2307            Box::new(mq_markdown::Node::TableCell(mq_markdown::TableCell {
2308                row: row.value() as usize,
2309                column: column.value() as usize,
2310                values: vec![value.to_string().into()],
2311                position: None,
2312            })),
2313            None,
2314        )),
2315        [a, b, c] => Err(Error::InvalidTypes(
2316            "table_cell".to_string(),
2317            vec![std::mem::take(a), std::mem::take(b), std::mem::take(c)],
2318        )),
2319        _ => unreachable!("to_md_table_cell should always receive exactly three arguments"),
2320    }
2321}
2322
2323#[mq_macros::mq_fn(name = "get_title", params = Fixed(1))]
2324fn get_title_impl(_: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
2325    match args.as_mut_slice() {
2326        [RuntimeValue::Markdown(node, _)]
2327            if matches!(**node, mq_markdown::Node::Definition(_) | mq_markdown::Node::Link(_)) =>
2328        {
2329            match &mut **node {
2330                mq_markdown::Node::Definition(mq_markdown::Definition { title, .. })
2331                | mq_markdown::Node::Link(mq_markdown::Link { title, .. }) => std::mem::take(title)
2332                    .map(|t| Ok(RuntimeValue::String(t.to_value())))
2333                    .unwrap_or_else(|| Ok(RuntimeValue::NONE)),
2334                _ => unreachable!(),
2335            }
2336        }
2337        [RuntimeValue::Markdown(node, _)] if matches!(**node, mq_markdown::Node::Image(_)) => {
2338            if let mq_markdown::Node::Image(mq_markdown::Image { title, .. }) = &mut **node {
2339                std::mem::take(title)
2340                    .map(|t| Ok(RuntimeValue::String(t)))
2341                    .unwrap_or_else(|| Ok(RuntimeValue::NONE))
2342            } else {
2343                unreachable!()
2344            }
2345        }
2346        [_] => Ok(RuntimeValue::NONE),
2347        _ => unreachable!("get_title should always receive exactly one argument"),
2348    }
2349}
2350
2351#[mq_macros::mq_fn(name = "get_url", params = Fixed(1))]
2352fn get_url_impl(_: &Ident, _: &RuntimeValue, args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
2353    match args.as_slice() {
2354        [RuntimeValue::Markdown(node, _)] => match &**node {
2355            mq_markdown::Node::Definition(def) => Ok(def.url.as_str().into()),
2356            mq_markdown::Node::Link(link) => Ok(link.url.as_str().into()),
2357            mq_markdown::Node::Image(image) => Ok(image.url.to_owned().into()),
2358            _ => Ok(RuntimeValue::NONE),
2359        },
2360        _ => Ok(RuntimeValue::NONE),
2361    }
2362}
2363
2364#[mq_macros::mq_fn(name = "set_check", params = Fixed(2))]
2365fn set_check_impl(_: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
2366    match args.as_mut_slice() {
2367        [RuntimeValue::Markdown(node, _), RuntimeValue::Boolean(checked)]
2368            if matches!(**node, mq_markdown::Node::List(_)) =>
2369        {
2370            let checked = *checked;
2371            if let mq_markdown::Node::List(list) = &mut **node {
2372                Ok(mq_markdown::Node::List(mq_markdown::List {
2373                    checked: Some(checked),
2374                    ..std::mem::take(list)
2375                })
2376                .into())
2377            } else {
2378                unreachable!()
2379            }
2380        }
2381        [a, ..] => Ok(std::mem::take(a)),
2382        _ => Ok(RuntimeValue::NONE),
2383    }
2384}
2385
2386#[mq_macros::mq_fn(name = "set_ref", params = Fixed(2))]
2387fn set_ref_impl(_: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
2388    match args.as_mut_slice() {
2389        [RuntimeValue::Markdown(node, selector), RuntimeValue::String(s)] => {
2390            match &mut **node {
2391                mq_markdown::Node::Definition(def) => {
2392                    return Ok(mq_markdown::Node::Definition(mq_markdown::Definition {
2393                        label: Some(s.to_owned()),
2394                        ..std::mem::take(def)
2395                    })
2396                    .into());
2397                }
2398                mq_markdown::Node::ImageRef(image_ref) => {
2399                    return Ok(mq_markdown::Node::ImageRef(mq_markdown::ImageRef {
2400                        label: if s == &image_ref.ident {
2401                            None
2402                        } else {
2403                            Some(s.to_owned())
2404                        },
2405                        ..std::mem::take(image_ref)
2406                    })
2407                    .into());
2408                }
2409                mq_markdown::Node::LinkRef(link_ref) => {
2410                    return Ok(mq_markdown::Node::LinkRef(mq_markdown::LinkRef {
2411                        label: if s == &link_ref.ident { None } else { Some(s.to_owned()) },
2412                        ..std::mem::take(link_ref)
2413                    })
2414                    .into());
2415                }
2416                mq_markdown::Node::Footnote(footnote) => {
2417                    return Ok(mq_markdown::Node::Footnote(mq_markdown::Footnote {
2418                        ident: s.to_owned(),
2419                        ..std::mem::take(footnote)
2420                    })
2421                    .into());
2422                }
2423                mq_markdown::Node::FootnoteRef(footnote_ref) => {
2424                    return Ok(mq_markdown::Node::FootnoteRef(mq_markdown::FootnoteRef {
2425                        label: Some(s.to_owned()),
2426                        ..std::mem::take(footnote_ref)
2427                    })
2428                    .into());
2429                }
2430                _ => {}
2431            }
2432
2433            Ok(RuntimeValue::Markdown(std::mem::take(node), std::mem::take(selector)))
2434        }
2435        [a, ..] => Ok(std::mem::take(a)),
2436        _ => Ok(RuntimeValue::NONE),
2437    }
2438}
2439
2440#[mq_macros::mq_fn(name = "set_code_block_lang", params = Fixed(2))]
2441fn set_code_block_lang_impl(_: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
2442    match args.as_mut_slice() {
2443        [RuntimeValue::Markdown(node, _), RuntimeValue::String(lang)]
2444            if matches!(**node, mq_markdown::Node::Code(_)) =>
2445        {
2446            if let mq_markdown::Node::Code(code) = &mut **node {
2447                let lang = std::mem::take(lang);
2448                let mut new_code = std::mem::take(code);
2449                new_code.lang = if lang.is_empty() { None } else { Some(lang) };
2450                Ok(mq_markdown::Node::Code(new_code).into())
2451            } else {
2452                unreachable!()
2453            }
2454        }
2455        [a, ..] => Ok(std::mem::take(a)),
2456        _ => Ok(RuntimeValue::NONE),
2457    }
2458}
2459
2460#[mq_macros::mq_fn(name = "dict", params = Range(0, u8::MAX))]
2461fn dict_impl(_: &Ident, _: &RuntimeValue, args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
2462    if args.is_empty() {
2463        Ok(RuntimeValue::new_dict())
2464    } else {
2465        let mut dict = BTreeMap::default();
2466        let entries: Cow<'_, [RuntimeValue]> = match args.as_slice() {
2467            [RuntimeValue::Array(entries)] => match entries.as_slice() {
2468                [RuntimeValue::Array(_)] if args.len() == 1 => Cow::Borrowed(entries),
2469                [RuntimeValue::Array(inner)] => Cow::Borrowed(inner),
2470                [RuntimeValue::String(_), ..] | [RuntimeValue::Symbol(_), ..] => {
2471                    Cow::Owned(vec![RuntimeValue::Array(entries.clone())])
2472                }
2473                _ => Cow::Borrowed(entries),
2474            },
2475            _ => Cow::Borrowed(args.as_slice()),
2476        };
2477
2478        for entry in entries.iter() {
2479            if let RuntimeValue::Array(arr) = entry {
2480                match arr.as_slice() {
2481                    [RuntimeValue::Symbol(key), value] => {
2482                        dict.insert(*key, value.clone());
2483                        continue;
2484                    }
2485                    [key, value] => {
2486                        dict.insert(Ident::new(&key.to_string()), value.clone());
2487                        continue;
2488                    }
2489                    a => return Err(Error::InvalidTypes("dict".to_string(), a.to_vec())),
2490                }
2491            } else {
2492                return Err(Error::InvalidTypes("dict".to_string(), vec![entry.clone()]));
2493            }
2494        }
2495
2496        Ok(dict.into())
2497    }
2498}
2499
2500#[mq_macros::mq_fn(name = "get", params = Fixed(2))]
2501fn get_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
2502    match args.as_mut_slice() {
2503        [RuntimeValue::Dict(map), RuntimeValue::String(key)] => Ok(map
2504            .get_mut(&Ident::new(key))
2505            .map(std::mem::take)
2506            .unwrap_or(RuntimeValue::NONE)),
2507        [RuntimeValue::Dict(map), RuntimeValue::Symbol(key)] => {
2508            Ok(map.get_mut(key).map(std::mem::take).unwrap_or(RuntimeValue::NONE))
2509        }
2510        [RuntimeValue::Array(array), RuntimeValue::Number(index)] => {
2511            let len = array.len();
2512            let idx = index.value() as isize;
2513            let real_idx = if idx < 0 {
2514                (len as isize + idx).max(0) as usize
2515            } else {
2516                idx as usize
2517            };
2518            Ok(array
2519                .get_mut(real_idx)
2520                .map(std::mem::take)
2521                .unwrap_or(RuntimeValue::NONE))
2522        }
2523        [RuntimeValue::String(s), RuntimeValue::Number(n)] => {
2524            let len = s.chars().count();
2525            let idx = n.value() as isize;
2526            let real_idx = if idx < 0 {
2527                (len as isize + idx).max(0) as usize
2528            } else {
2529                idx as usize
2530            };
2531            match s.chars().nth(real_idx) {
2532                Some(o) => Ok(o.to_string().into()),
2533                None => Ok(RuntimeValue::NONE),
2534            }
2535        }
2536        [RuntimeValue::Markdown(node, _), RuntimeValue::Number(i)] => {
2537            let idx = i.value() as isize;
2538            let real_idx = if idx < 0 {
2539                let len = node.value().chars().count();
2540                (len as isize + idx).max(0) as usize
2541            } else {
2542                idx as usize
2543            };
2544            Ok(RuntimeValue::Markdown(
2545                std::mem::take(node),
2546                Some(runtime_value::Selector::Index(real_idx)),
2547            ))
2548        }
2549        [RuntimeValue::None, _] | [_, RuntimeValue::None] => Ok(RuntimeValue::NONE),
2550        [a, b] => Err(Error::InvalidTypes(
2551            ident.to_string(),
2552            vec![std::mem::take(a), std::mem::take(b)],
2553        )),
2554        _ => unreachable!("get should always receive exactly two arguments"),
2555    }
2556}
2557
2558#[mq_macros::mq_fn(name = "set", params = Fixed(3))]
2559fn set_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
2560    match args.as_mut_slice() {
2561        [RuntimeValue::Dict(map_val), RuntimeValue::String(key_val), value_val] => {
2562            let mut new_dict = std::mem::take(map_val);
2563            new_dict.insert(Ident::new(key_val), std::mem::take(value_val));
2564            Ok(RuntimeValue::Dict(new_dict))
2565        }
2566        [RuntimeValue::Dict(map_val), RuntimeValue::Symbol(key_val), value_val] => {
2567            let mut new_dict = std::mem::take(map_val);
2568            new_dict.insert(*key_val, std::mem::take(value_val));
2569            Ok(RuntimeValue::Dict(new_dict))
2570        }
2571        [
2572            RuntimeValue::Array(array_val),
2573            RuntimeValue::Number(index_val),
2574            value_val,
2575        ] => {
2576            let index = index_val.value() as usize;
2577
2578            // Extend array size if necessary
2579            let mut new_array = if index >= array_val.len() {
2580                // If index is out of bounds, extend array and fill with None
2581                let mut resized_array = Vec::with_capacity(index + 1);
2582                resized_array.extend_from_slice(array_val);
2583                resized_array.resize(index + 1, RuntimeValue::NONE);
2584                resized_array
2585            } else {
2586                // If index is within bounds, clone existing array
2587                std::mem::take(array_val)
2588            };
2589
2590            // Set value at specified index
2591            new_array[index] = std::mem::take(value_val);
2592            Ok(RuntimeValue::Array(new_array))
2593        }
2594        [a, b, c] => Err(Error::InvalidTypes(
2595            ident.to_string(),
2596            vec![std::mem::take(a), std::mem::take(b), std::mem::take(c)],
2597        )),
2598        _ => unreachable!("set should always receive exactly three arguments"),
2599    }
2600}
2601
2602#[mq_macros::mq_fn(name = "keys", params = Fixed(1))]
2603fn keys_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
2604    match args.as_mut_slice() {
2605        [RuntimeValue::Dict(map)] => {
2606            let keys = map
2607                .keys()
2608                .map(|k| RuntimeValue::String(k.as_str()))
2609                .collect::<Vec<RuntimeValue>>();
2610            Ok(RuntimeValue::Array(keys))
2611        }
2612        [RuntimeValue::None] => Ok(RuntimeValue::NONE),
2613        [a] => Err(Error::InvalidTypes(ident.to_string(), vec![std::mem::take(a)])),
2614        _ => unreachable!("keys should always receive exactly one argument"),
2615    }
2616}
2617
2618#[mq_macros::mq_fn(name = "values", params = Fixed(1))]
2619fn values_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
2620    match args.as_mut_slice() {
2621        [RuntimeValue::Dict(map)] => {
2622            let values = map.values().cloned().collect::<Vec<RuntimeValue>>();
2623            Ok(RuntimeValue::Array(values))
2624        }
2625        [RuntimeValue::None] => Ok(RuntimeValue::NONE),
2626        [a] => Err(Error::InvalidTypes(ident.to_string(), vec![std::mem::take(a)])),
2627        _ => unreachable!("values should always receive exactly one argument"),
2628    }
2629}
2630
2631#[mq_macros::mq_fn(name = "entries", params = Fixed(1))]
2632fn entries_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
2633    match args.as_mut_slice() {
2634        [RuntimeValue::Dict(map)] => {
2635            let entries = map
2636                .iter()
2637                .map(|(k, v)| RuntimeValue::Array(vec![RuntimeValue::String(k.as_str()), v.to_owned()]))
2638                .collect::<Vec<RuntimeValue>>();
2639            Ok(RuntimeValue::Array(entries))
2640        }
2641        [RuntimeValue::None] => Ok(RuntimeValue::NONE),
2642        [a] => Err(Error::InvalidTypes(ident.to_string(), vec![std::mem::take(a)])),
2643        _ => unreachable!("entries should always receive exactly one argument"),
2644    }
2645}
2646
2647#[mq_macros::mq_fn(name = "insert", params = Fixed(3))]
2648fn insert_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
2649    match args.as_mut_slice() {
2650        // Insert into array at index
2651        [RuntimeValue::Array(array), RuntimeValue::Number(index), value] => {
2652            let mut new_array = std::mem::take(array);
2653            let idx = index.value() as usize;
2654            if idx > new_array.len() {
2655                new_array.resize(idx, RuntimeValue::NONE);
2656            }
2657            new_array.insert(idx, std::mem::take(value));
2658            Ok(RuntimeValue::Array(new_array))
2659        }
2660        // Insert into string at index
2661        [RuntimeValue::String(s), RuntimeValue::Number(index), value] => {
2662            let mut chars: Vec<char> = s.chars().collect();
2663            let idx = index.value() as usize;
2664            let insert_str = value.to_string();
2665            if idx > chars.len() {
2666                chars.resize(idx, ' ');
2667            }
2668            for (i, c) in insert_str.chars().enumerate() {
2669                chars.insert(idx + i, c);
2670            }
2671            let result: String = chars.into_iter().collect();
2672            Ok(RuntimeValue::String(result))
2673        }
2674        // Insert into dict (same as set, but error if key exists)
2675        [RuntimeValue::Dict(map_val), RuntimeValue::String(key_val), value_val] => {
2676            let mut new_dict = std::mem::take(map_val);
2677            new_dict.insert(Ident::new(key_val), std::mem::take(value_val));
2678            Ok(RuntimeValue::Dict(new_dict))
2679        }
2680        [RuntimeValue::Dict(map_val), RuntimeValue::Symbol(key_val), value_val] => {
2681            let mut new_dict = std::mem::take(map_val);
2682            new_dict.insert(*key_val, std::mem::take(value_val));
2683            Ok(RuntimeValue::Dict(new_dict))
2684        }
2685        [a, b, c] => Err(Error::InvalidTypes(
2686            ident.to_string(),
2687            vec![std::mem::take(a), std::mem::take(b), std::mem::take(c)],
2688        )),
2689        _ => unreachable!("insert should always receive exactly three arguments"),
2690    }
2691}
2692
2693#[mq_macros::mq_fn(name = "negate", params = Fixed(1))]
2694fn negate_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
2695    match args.as_mut_slice() {
2696        [RuntimeValue::Number(n)] => Ok(RuntimeValue::Number(-(*n))),
2697        [a] => Err(Error::InvalidTypes(ident.to_string(), vec![std::mem::take(a)])),
2698        _ => unreachable!("negate should always receive exactly one argument"),
2699    }
2700}
2701
2702#[mq_macros::mq_fn(name = "intern", params = Fixed(1))]
2703fn intern_impl(_: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
2704    match args.as_mut_slice() {
2705        [RuntimeValue::String(s)] => Ok(RuntimeValue::String(Ident::new(s).as_str())),
2706        [a] => Ok(RuntimeValue::String(Ident::new(&a.to_string()).as_str())),
2707        _ => unreachable!("intern should always receive exactly one argument"),
2708    }
2709}
2710
2711#[mq_macros::mq_fn(name = "nan", params = None)]
2712fn nan_impl(_: &Ident, _: &RuntimeValue, _: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
2713    Ok(RuntimeValue::Number(number::NAN))
2714}
2715
2716#[mq_macros::mq_fn(name = "is_nan", params = Fixed(1))]
2717fn is_nan_impl(_: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
2718    match args.as_mut_slice() {
2719        [RuntimeValue::Number(n)] => Ok(RuntimeValue::Boolean(n.is_nan())),
2720        [_] => Ok(RuntimeValue::FALSE),
2721        _ => unreachable!("is_nan should always receive exactly one argument"),
2722    }
2723}
2724
2725#[mq_macros::mq_fn(name = "infinite", params = None)]
2726fn infinite_impl(_: &Ident, _: &RuntimeValue, _: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
2727    Ok(RuntimeValue::Number(number::INFINITE))
2728}
2729
2730#[mq_macros::mq_fn(name = "coalesce", params = Fixed(2))]
2731fn coalesce_impl(_: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
2732    match args.as_mut_slice() {
2733        [a, b] => {
2734            if a.is_none() {
2735                Ok(std::mem::take(b))
2736            } else {
2737                Ok(std::mem::take(a))
2738            }
2739        }
2740        _ => unreachable!("coalesce should always receive exactly two arguments"),
2741    }
2742}
2743
2744#[mq_macros::mq_fn(name = "input", params = None)]
2745fn input_impl(_: &Ident, _: &RuntimeValue, _: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
2746    let mut input = String::new();
2747    io::stdin()
2748        .read_line(&mut input)
2749        .map_err(|e| Error::Runtime(format!("Failed to read from stdin: {}", e)))?;
2750    input.truncate(input.trim_end_matches(&['\n', '\r'][..]).len());
2751
2752    Ok(RuntimeValue::String(input))
2753}
2754
2755#[mq_macros::mq_fn(name = "all_symbols", params = None)]
2756fn all_symbols_impl(_: &Ident, _: &RuntimeValue, _: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
2757    Ok(RuntimeValue::Array(
2758        all_symbols()
2759            .into_iter()
2760            .map(|symbol| RuntimeValue::Symbol(Ident::new(&symbol)))
2761            .collect(),
2762    ))
2763}
2764
2765#[mq_macros::mq_fn(name = "to_markdown", params = Fixed(1))]
2766fn to_markdown_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
2767    match args.as_mut_slice() {
2768        [RuntimeValue::String(s)] => {
2769            Ok(RuntimeValue::Array(parse_markdown_input(s).map_err(|e| {
2770                Error::Runtime(format!("Failed to parse markdown: {}", e))
2771            })?))
2772        }
2773        [a] => Err(Error::InvalidTypes(ident.to_string(), vec![std::mem::take(a)])),
2774        _ => unreachable!("to_markdown should always receive exactly one argument"),
2775    }
2776}
2777
2778#[mq_macros::mq_fn(name = "to_mdx", params = Fixed(1))]
2779fn to_mdx_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
2780    match args.as_mut_slice() {
2781        [RuntimeValue::String(s)] => Ok(RuntimeValue::Array(
2782            parse_mdx_input(s).map_err(|e| Error::Runtime(format!("Failed to parse mdx: {}", e)))?,
2783        )),
2784        [a] => Err(Error::InvalidTypes(ident.to_string(), vec![std::mem::take(a)])),
2785        _ => unreachable!("to_mdx should always receive exactly one argument"),
2786    }
2787}
2788
2789#[mq_macros::mq_fn(name = "_get_markdown_position", params = Fixed(1))]
2790fn _get_markdown_position_impl(
2791    ident: &Ident,
2792    _: &RuntimeValue,
2793    mut args: Args,
2794    _: &SharedEnv,
2795) -> Result<RuntimeValue, Error> {
2796    match args.as_mut_slice() {
2797        [RuntimeValue::Markdown(node, _)] => node
2798            .position()
2799            .map(|pos| {
2800                Ok(vec![
2801                    ("start_line".to_string(), pos.start.line.into()),
2802                    ("start_column".to_string(), pos.start.column.into()),
2803                    ("end_line".to_string(), pos.end.line.into()),
2804                    ("end_column".to_string(), pos.end.column.into()),
2805                ]
2806                .into())
2807            })
2808            .unwrap_or(Ok(RuntimeValue::NONE)),
2809        [a] => Err(Error::InvalidTypes(ident.to_string(), vec![std::mem::take(a)])),
2810        _ => unreachable!("_get_markdown_position should always receive exactly one argument"),
2811    }
2812}
2813
2814#[mq_macros::mq_fn(name = "_csv_parse", params = Range(1, 3))]
2815fn _csv_parse_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
2816    let (csv_str, delimiter, has_header) = match args.as_mut_slice() {
2817        [RuntimeValue::String(s)] => (std::mem::take(s), b',', false),
2818        [RuntimeValue::String(s), RuntimeValue::String(delim)] => {
2819            let ch = delim
2820                .chars()
2821                .next()
2822                .ok_or_else(|| Error::Runtime("Delimiter must be a non-empty string".to_string()))?;
2823            if !ch.is_ascii() {
2824                return Err(Error::Runtime("Delimiter must be an ASCII character".to_string()));
2825            }
2826            (std::mem::take(s), ch as u8, false)
2827        }
2828        [
2829            RuntimeValue::String(s),
2830            RuntimeValue::String(delim),
2831            RuntimeValue::Boolean(b),
2832        ] => {
2833            let ch = delim
2834                .chars()
2835                .next()
2836                .ok_or_else(|| Error::Runtime("Delimiter must be a non-empty string".to_string()))?;
2837            if !ch.is_ascii() {
2838                return Err(Error::Runtime("Delimiter must be an ASCII character".to_string()));
2839            }
2840            (std::mem::take(s), ch as u8, *b)
2841        }
2842        [a] => return Err(Error::InvalidTypes(ident.to_string(), vec![std::mem::take(a)])),
2843        [a, b] => {
2844            return Err(Error::InvalidTypes(
2845                ident.to_string(),
2846                vec![std::mem::take(a), std::mem::take(b)],
2847            ));
2848        }
2849        [a, b, c] => {
2850            return Err(Error::InvalidTypes(
2851                ident.to_string(),
2852                vec![std::mem::take(a), std::mem::take(b), std::mem::take(c)],
2853            ));
2854        }
2855        _ => unreachable!("_csv_parse should receive between 1 and 3 arguments"),
2856    };
2857
2858    let mut reader = ReaderBuilder::new()
2859        .has_headers(has_header)
2860        .delimiter(delimiter)
2861        .from_reader(csv_str.as_bytes());
2862
2863    if has_header {
2864        let headers: Vec<String> = reader
2865            .headers()
2866            .map_err(|e| Error::Runtime(format!("Failed to parse CSV headers: {e}")))?
2867            .iter()
2868            .map(|s| s.to_string())
2869            .collect();
2870
2871        let rows: Result<Vec<RuntimeValue>, Error> = reader
2872            .records()
2873            .map(|record| {
2874                let record = record.map_err(|e| Error::Runtime(format!("Failed to parse CSV record: {e}")))?;
2875                let map: BTreeMap<Ident, RuntimeValue> = headers
2876                    .iter()
2877                    .zip(record.iter())
2878                    .map(|(k, v)| (Ident::new(k), RuntimeValue::String(v.to_string())))
2879                    .collect();
2880                Ok(RuntimeValue::Dict(map))
2881            })
2882            .collect();
2883
2884        Ok(RuntimeValue::Array(rows?))
2885    } else {
2886        let rows: Result<Vec<RuntimeValue>, Error> = reader
2887            .records()
2888            .map(|record| {
2889                let record = record.map_err(|e| Error::Runtime(format!("Failed to parse CSV record: {e}")))?;
2890                let arr: Vec<RuntimeValue> = record.iter().map(|v| RuntimeValue::String(v.to_string())).collect();
2891                Ok(RuntimeValue::Array(arr))
2892            })
2893            .collect();
2894
2895        Ok(RuntimeValue::Array(rows?))
2896    }
2897}
2898
2899#[mq_macros::mq_fn(name = "_json_parse", params = Fixed(1))]
2900fn _json_parse_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
2901    match args.as_mut_slice() {
2902        [RuntimeValue::String(s)] => {
2903            let value: serde_json::Value =
2904                serde_json::from_str(s).map_err(|e| Error::Runtime(format!("Failed to parse JSON: {}", e)))?;
2905            Ok(value.into())
2906        }
2907        [a] => Err(Error::InvalidTypes(ident.to_string(), vec![std::mem::take(a)])),
2908        _ => unreachable!("_json_parse should always receive exactly one argument"),
2909    }
2910}
2911
2912#[mq_macros::mq_fn(name = "_yaml_parse", params = Fixed(1))]
2913fn _yaml_parse_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
2914    match args.as_mut_slice() {
2915        [RuntimeValue::String(s)] => {
2916            let docs = yaml_rust2::YamlLoader::load_from_str(s)
2917                .map_err(|e| Error::Runtime(format!("Failed to parse YAML: {}", e)))?;
2918            match docs.into_iter().next() {
2919                Some(doc) => Ok(doc.into()),
2920                None => Ok(RuntimeValue::NONE),
2921            }
2922        }
2923        [a] => Err(Error::InvalidTypes(ident.to_string(), vec![std::mem::take(a)])),
2924        _ => unreachable!("_yaml_parse should always receive exactly one argument"),
2925    }
2926}
2927
2928#[mq_macros::mq_fn(name = "_toon_parse", params = Fixed(1))]
2929fn _toon_parse_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
2930    match args.as_mut_slice() {
2931        [RuntimeValue::String(s)] => Ok(toon_format::decode::<serde_json::Value>(
2932            s,
2933            &toon_format::DecodeOptions::default(),
2934        )
2935        .map_err(|e| Error::Runtime(format!("Failed to parse TOON: {}", e)))?
2936        .into()),
2937        [a] => Err(Error::InvalidTypes(ident.to_string(), vec![std::mem::take(a)])),
2938        _ => unreachable!("_toon_parse should always receive exactly one argument"),
2939    }
2940}
2941
2942#[mq_macros::mq_fn(name = "_toml_parse", params = Fixed(1))]
2943fn _toml_parse_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
2944    match args.as_mut_slice() {
2945        [RuntimeValue::String(s)] => {
2946            let value: serde_json::Value =
2947                toml::from_str(s).map_err(|e| Error::Runtime(format!("Failed to parse TOML: {}", e)))?;
2948            Ok(value.into())
2949        }
2950        [a] => Err(Error::InvalidTypes(ident.to_string(), vec![std::mem::take(a)])),
2951        _ => unreachable!("_toml_parse should always receive exactly one argument"),
2952    }
2953}
2954
2955#[mq_macros::mq_fn(name = "_cbor_parse", params = Fixed(1))]
2956fn _cbor_parse_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
2957    match args.as_mut_slice() {
2958        [RuntimeValue::String(s)] => {
2959            let bytes = base64::engine::general_purpose::STANDARD
2960                .decode(s.as_bytes())
2961                .map_err(|e| Error::Runtime(format!("Failed to decode base64: {}", e)))?;
2962            let value: ciborium::Value = ciborium::from_reader(bytes.as_slice())
2963                .map_err(|e| Error::Runtime(format!("Failed to parse CBOR: {}", e)))?;
2964            Ok(value.into())
2965        }
2966        [RuntimeValue::Bytes(b)] => {
2967            let value: ciborium::Value = ciborium::from_reader(b.as_slice())
2968                .map_err(|e| Error::Runtime(format!("Failed to parse CBOR: {}", e)))?;
2969            Ok(value.into())
2970        }
2971        [a] => Err(Error::InvalidTypes(ident.to_string(), vec![std::mem::take(a)])),
2972        _ => unreachable!("_cbor_parse should always receive exactly one argument"),
2973    }
2974}
2975
2976#[mq_macros::mq_fn(name = "_hcl_parse", params = Fixed(1))]
2977fn _hcl_parse_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
2978    match args.as_mut_slice() {
2979        [RuntimeValue::String(s)] => {
2980            let value: serde_json::Value =
2981                hcl::from_str(s).map_err(|e| Error::Runtime(format!("Failed to parse HCL: {}", e)))?;
2982            Ok(value.into())
2983        }
2984        [a] => Err(Error::InvalidTypes(ident.to_string(), vec![std::mem::take(a)])),
2985        _ => unreachable!("_hcl_parse should always receive exactly one argument"),
2986    }
2987}
2988
2989#[mq_macros::mq_fn(name = "_hcl_stringify", params = Fixed(1))]
2990fn _hcl_stringify_impl(_ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
2991    match args.as_mut_slice() {
2992        [value] => {
2993            let json_value = std::mem::take(value).to_json_value();
2994            let s =
2995                hcl::to_string(&json_value).map_err(|e| Error::Runtime(format!("Failed to serialize HCL: {}", e)))?;
2996            Ok(RuntimeValue::String(s))
2997        }
2998        _ => unreachable!("_hcl_stringify should always receive exactly one argument"),
2999    }
3000}
3001
3002#[mq_macros::mq_fn(name = "_cbor_stringify", params = Fixed(1))]
3003fn _cbor_stringify_impl(_: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
3004    match args.as_mut_slice() {
3005        [value] => {
3006            let cbor_value = std::mem::take(value).to_cbor_value();
3007            let mut buf = Vec::new();
3008            ciborium::into_writer(&cbor_value, &mut buf)
3009                .map_err(|e| Error::Runtime(format!("Failed to serialize CBOR: {}", e)))?;
3010            Ok(RuntimeValue::Bytes(buf))
3011        }
3012        _ => unreachable!("_cbor_stringify should always receive exactly one argument"),
3013    }
3014}
3015
3016#[mq_macros::mq_fn(name = "_xml_parse", params = Fixed(1))]
3017fn _xml_parse_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
3018    match args.as_mut_slice() {
3019        [RuntimeValue::String(xml_str)] => {
3020            let mut reader = quick_xml::Reader::from_str(xml_str);
3021            reader.config_mut().trim_text(true);
3022            let mut buf = Vec::new();
3023            #[allow(clippy::type_complexity)]
3024            let mut stack: Vec<(String, BTreeMap<Ident, RuntimeValue>, Vec<RuntimeValue>, Option<String>)> = Vec::new();
3025            let mut root: Option<RuntimeValue> = None;
3026
3027            let parse_attrs = |e: &quick_xml::events::BytesStart<'_>, reader: &quick_xml::Reader<&[u8]>| {
3028                let mut attrs = BTreeMap::new();
3029                for attr in e.attributes() {
3030                    let attr = attr.map_err(|e| Error::Runtime(format!("XML attribute error: {}", e)))?;
3031                    let key = String::from_utf8_lossy(attr.key.as_ref()).to_string();
3032                    let value = attr
3033                        .decoded_and_normalized_value(XmlVersion::default(), reader.decoder())
3034                        .map_err(|e| Error::Runtime(format!("XML attribute value error: {}", e)))?
3035                        .to_string();
3036                    attrs.insert(Ident::new(&key), RuntimeValue::String(value));
3037                }
3038                Ok::<_, Error>(attrs)
3039            };
3040
3041            loop {
3042                match reader.read_event_into(&mut buf) {
3043                    Ok(quick_xml::events::Event::Start(e)) => {
3044                        let tag = String::from_utf8_lossy(e.name().as_ref()).to_string();
3045                        let attrs = parse_attrs(&e, &reader)?;
3046                        stack.push((tag, attrs, Vec::new(), None));
3047                    }
3048                    Ok(quick_xml::events::Event::End(e)) => {
3049                        let end_tag = String::from_utf8_lossy(e.name().as_ref()).to_string();
3050                        let (tag, attrs, children, text) = stack.pop().ok_or_else(|| {
3051                            Error::Runtime(format!(
3052                                "XML parse error at position {}: unexpected closing tag </{}>",
3053                                reader.buffer_position(),
3054                                end_tag
3055                            ))
3056                        })?;
3057
3058                        if tag != end_tag {
3059                            return Err(Error::Runtime(format!(
3060                                "XML parse error at position {}: mismatched closing tag: expected </{}> but found </{}>",
3061                                reader.buffer_position(),
3062                                tag,
3063                                end_tag
3064                            )));
3065                        }
3066
3067                        let mut dict = BTreeMap::new();
3068                        dict.insert(Ident::new("tag"), RuntimeValue::String(tag));
3069                        dict.insert(Ident::new("attributes"), RuntimeValue::Dict(attrs));
3070                        dict.insert(Ident::new("children"), RuntimeValue::Array(children));
3071                        dict.insert(
3072                            Ident::new("text"),
3073                            text.map(RuntimeValue::String).unwrap_or(RuntimeValue::NONE),
3074                        );
3075                        let element = RuntimeValue::Dict(dict);
3076
3077                        if let Some(parent) = stack.last_mut() {
3078                            parent.2.push(element);
3079                        } else {
3080                            root = Some(element);
3081                            break;
3082                        }
3083                    }
3084                    Ok(quick_xml::events::Event::Empty(e)) => {
3085                        let tag = String::from_utf8_lossy(e.name().as_ref()).to_string();
3086                        let attrs = parse_attrs(&e, &reader)?;
3087                        let mut dict = BTreeMap::new();
3088                        dict.insert(Ident::new("tag"), RuntimeValue::String(tag));
3089                        dict.insert(Ident::new("attributes"), RuntimeValue::Dict(attrs));
3090                        dict.insert(Ident::new("children"), RuntimeValue::EMPTY_ARRAY);
3091                        dict.insert(Ident::new("text"), RuntimeValue::NONE);
3092                        let element = RuntimeValue::Dict(dict);
3093
3094                        if let Some(parent) = stack.last_mut() {
3095                            parent.2.push(element);
3096                        } else {
3097                            root = Some(element);
3098                            break;
3099                        }
3100                    }
3101                    Ok(quick_xml::events::Event::Text(e)) => {
3102                        if let Some(parent) = stack.last_mut() {
3103                            let text = reader
3104                                .decoder()
3105                                .decode(e.as_ref())
3106                                .map_err(|e| Error::Runtime(format!("XML text error: {}", e)))?
3107                                .to_string();
3108
3109                            if !text.is_empty() {
3110                                match &mut parent.3 {
3111                                    Some(t) => t.push_str(&text),
3112                                    None => parent.3 = Some(text),
3113                                }
3114                            }
3115                        }
3116                    }
3117                    Ok(quick_xml::events::Event::CData(e)) => {
3118                        if let Some(parent) = stack.last_mut() {
3119                            let text = reader
3120                                .decoder()
3121                                .decode(e.as_ref())
3122                                .map_err(|e| Error::Runtime(format!("XML CDATA error: {}", e)))?
3123                                .to_string();
3124                            match &mut parent.3 {
3125                                Some(t) => t.push_str(&text),
3126                                None => parent.3 = Some(text),
3127                            }
3128                        }
3129                    }
3130                    Ok(quick_xml::events::Event::Eof) => break,
3131                    Err(e) => {
3132                        return Err(Error::Runtime(format!(
3133                            "XML parse error at position {}: {}",
3134                            reader.buffer_position(),
3135                            e
3136                        )));
3137                    }
3138                    _ => (),
3139                }
3140                buf.clear();
3141            }
3142
3143            Ok(root.unwrap_or(RuntimeValue::NONE))
3144        }
3145        [a] => Err(Error::InvalidTypes(ident.to_string(), vec![std::mem::take(a)])),
3146        _ => unreachable!("_xml_parse should always receive exactly one argument"),
3147    }
3148}
3149
3150#[mq_macros::mq_fn(name = "set_variable", params = Fixed(2))]
3151fn set_variable_impl(
3152    ident: &Ident,
3153    value: &RuntimeValue,
3154    mut args: Args,
3155    env: &SharedEnv,
3156) -> Result<RuntimeValue, Error> {
3157    match args.as_mut_slice() {
3158        [RuntimeValue::Symbol(var_ident), v] => {
3159            #[cfg(not(feature = "sync"))]
3160            {
3161                env.borrow_mut().define(std::mem::take(var_ident), std::mem::take(v));
3162            }
3163
3164            #[cfg(feature = "sync")]
3165            {
3166                env.write()
3167                    .unwrap()
3168                    .define(std::mem::take(var_ident), std::mem::take(v));
3169            }
3170
3171            Ok(value.clone())
3172        }
3173        [RuntimeValue::String(var_name), v] => {
3174            #[cfg(not(feature = "sync"))]
3175            {
3176                env.borrow_mut().define(Ident::new(var_name), std::mem::take(v));
3177            }
3178
3179            #[cfg(feature = "sync")]
3180            {
3181                env.write().unwrap().define(Ident::new(var_name), std::mem::take(v));
3182            }
3183
3184            Ok(value.clone())
3185        }
3186        [a, b] => Err(Error::InvalidTypes(
3187            ident.to_string(),
3188            vec![std::mem::take(a), std::mem::take(b)],
3189        )),
3190        _ => unreachable!("set_variable should always receive exactly two arguments"),
3191    }
3192}
3193
3194#[mq_macros::mq_fn(name = "get_variable", params = Fixed(1))]
3195fn get_variable_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, env: &SharedEnv) -> Result<RuntimeValue, Error> {
3196    match args.as_mut_slice() {
3197        [RuntimeValue::Symbol(var_name)] => {
3198            #[cfg(not(feature = "sync"))]
3199            {
3200                env.borrow().resolve(std::mem::take(var_name)).map_err(Into::into)
3201            }
3202
3203            #[cfg(feature = "sync")]
3204            {
3205                env.read()
3206                    .unwrap()
3207                    .resolve(std::mem::take(var_name))
3208                    .map_err(Into::into)
3209            }
3210        }
3211        [RuntimeValue::String(var_name)] => {
3212            #[cfg(not(feature = "sync"))]
3213            {
3214                env.borrow().resolve(Ident::new(var_name)).map_err(Into::into)
3215            }
3216
3217            #[cfg(feature = "sync")]
3218            {
3219                env.read().unwrap().resolve(Ident::new(var_name)).map_err(Into::into)
3220            }
3221        }
3222        [a] => Err(Error::InvalidTypes(ident.to_string(), vec![std::mem::take(a)])),
3223        _ => unreachable!("get_variable should always receive exactly one argument"),
3224    }
3225}
3226
3227#[mq_macros::mq_fn(name = "is_debug_mode", params = None)]
3228fn is_debug_mode_impl(_: &Ident, _: &RuntimeValue, _: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
3229    #[cfg(feature = "debugger")]
3230    {
3231        Ok(RuntimeValue::TRUE)
3232    }
3233    #[cfg(not(feature = "debugger"))]
3234    {
3235        Ok(RuntimeValue::FALSE)
3236    }
3237}
3238
3239// AST related built-ins
3240#[mq_macros::mq_fn(name = "_ast_get_args", params = Fixed(1))]
3241fn _ast_get_args_impl(_: &Ident, _: &RuntimeValue, args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
3242    match args.as_slice() {
3243        [RuntimeValue::Ast(ast)] => match &*ast.expr {
3244            ast::Expr::Call(_, args) | ast::Expr::CallDynamic(_, args) => Ok(args
3245                .iter()
3246                .map(|arg| RuntimeValue::Ast(Shared::clone(arg)))
3247                .collect::<Vec<_>>()
3248                .into()),
3249            _ => Ok(RuntimeValue::NONE),
3250        },
3251        _ => Ok(RuntimeValue::NONE),
3252    }
3253}
3254
3255#[mq_macros::mq_fn(name = "_ast_to_code", params = Fixed(1))]
3256fn _ast_to_code_impl(_: &Ident, _: &RuntimeValue, args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
3257    match args.as_slice() {
3258        [RuntimeValue::Ast(ast)] => Ok(ast.to_code().into()),
3259        [a] => Ok(a.to_string().into()),
3260        _ => Ok(RuntimeValue::NONE),
3261    }
3262}
3263
3264#[mq_macros::mq_fn(name = "shift_left", params = Fixed(2))]
3265fn shift_left_impl(_: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
3266    match args.as_mut_slice() {
3267        [RuntimeValue::Number(v), RuntimeValue::Number(n)] => v
3268            .to_int()
3269            .checked_shl(n.value() as u32)
3270            .map(|result| RuntimeValue::Number(result.into()))
3271            .ok_or_else(|| Error::Runtime("Shift amount is too large".to_string())),
3272        [RuntimeValue::String(v), RuntimeValue::Number(n)] => {
3273            let shift_amount = n.to_int().max(0) as usize;
3274            let shifted: String = v.chars().skip(shift_amount).collect();
3275            Ok(RuntimeValue::String(shifted))
3276        }
3277        [RuntimeValue::Array(arr), v] => {
3278            arr.push(std::mem::take(v));
3279            Ok(RuntimeValue::Array(std::mem::take(arr)))
3280        }
3281        [RuntimeValue::Markdown(node, selector), RuntimeValue::Number(n)] => {
3282            if let mq_markdown::Node::Heading(heading) = &mut **node {
3283                let shift_amount = n.to_int().max(0).min(u8::MAX as i64) as u8;
3284
3285                heading.depth = heading.depth.saturating_sub(shift_amount).max(1);
3286                Ok(mq_markdown::Node::Heading(std::mem::take(heading)).into())
3287            } else {
3288                Ok(RuntimeValue::Markdown(std::mem::take(node), selector.take()))
3289            }
3290        }
3291        [RuntimeValue::None, _] => Ok(RuntimeValue::NONE),
3292        [a, b] => Err(Error::InvalidTypes(
3293            constants::builtins::SHIFT_LEFT.to_string(),
3294            vec![std::mem::take(a), std::mem::take(b)],
3295        )),
3296        _ => unreachable!("shift_left should always receive exactly two arguments"),
3297    }
3298}
3299
3300#[mq_macros::mq_fn(name = "shift_right", params = Fixed(2))]
3301fn shift_right_impl(_: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
3302    match args.as_mut_slice() {
3303        [RuntimeValue::Number(v), RuntimeValue::Number(n)] => v
3304            .to_int()
3305            .checked_shr(n.value() as u32)
3306            .map(|result| RuntimeValue::Number(result.into()))
3307            .ok_or_else(|| Error::Runtime("Shift amount is too large".to_string())),
3308        [RuntimeValue::String(v), RuntimeValue::Number(n)] => {
3309            let shift_amount = n.value() as usize;
3310            let char_len = v.chars().count();
3311            if shift_amount >= char_len {
3312                Ok(RuntimeValue::String(String::new()))
3313            } else {
3314                let keep = char_len - shift_amount;
3315                let result: String = v.chars().take(keep).collect();
3316                Ok(RuntimeValue::String(result))
3317            }
3318        }
3319        [v, RuntimeValue::Array(arr)] => {
3320            arr.insert(0, std::mem::take(v));
3321            Ok(RuntimeValue::Array(std::mem::take(arr)))
3322        }
3323        [RuntimeValue::Markdown(node, selector), RuntimeValue::Number(n)] => {
3324            if let mq_markdown::Node::Heading(heading) = &mut **node {
3325                let shift_amount = n.to_int().max(0).min(u8::MAX as i64) as u8;
3326
3327                if heading.depth + shift_amount <= 6 {
3328                    heading.depth += shift_amount;
3329                }
3330                Ok(mq_markdown::Node::Heading(std::mem::take(heading)).into())
3331            } else {
3332                Ok(RuntimeValue::Markdown(std::mem::take(node), selector.take()))
3333            }
3334        }
3335        [RuntimeValue::None, _] => Ok(RuntimeValue::NONE),
3336        [a, b] => Err(Error::InvalidTypes(
3337            constants::builtins::SHIFT_RIGHT.to_string(),
3338            vec![std::mem::take(a), std::mem::take(b)],
3339        )),
3340        _ => unreachable!("shift_right should always receive exactly two arguments"),
3341    }
3342}
3343
3344fn build_char_inline_diff(s1: &str, s2: &str) -> (Vec<RuntimeValue>, Vec<RuntimeValue>) {
3345    let char_diff = TextDiff::from_chars(s1, s2);
3346    let mut del_inline: Vec<RuntimeValue> = Vec::new();
3347    let mut ins_inline: Vec<RuntimeValue> = Vec::new();
3348    for c in char_diff.iter_all_changes() {
3349        let val = RuntimeValue::String(c.value().to_string());
3350        match c.tag() {
3351            ChangeTag::Delete => {
3352                let mut m = BTreeMap::new();
3353                m.insert(Ident::new("tag"), RuntimeValue::String("delete".into()));
3354                m.insert(Ident::new("value"), val);
3355                del_inline.push(RuntimeValue::Dict(m));
3356            }
3357            ChangeTag::Insert => {
3358                let mut m = BTreeMap::new();
3359                m.insert(Ident::new("tag"), RuntimeValue::String("insert".into()));
3360                m.insert(Ident::new("value"), val);
3361                ins_inline.push(RuntimeValue::Dict(m));
3362            }
3363            ChangeTag::Equal => {
3364                for inline in [&mut del_inline, &mut ins_inline] {
3365                    let mut m = BTreeMap::new();
3366                    m.insert(Ident::new("tag"), RuntimeValue::String("equal".into()));
3367                    m.insert(Ident::new("value"), RuntimeValue::String(c.value().to_string()));
3368                    inline.push(RuntimeValue::Dict(m));
3369                }
3370            }
3371        }
3372    }
3373    (del_inline, ins_inline)
3374}
3375
3376#[mq_macros::mq_fn(name = "_diff", params = Fixed(2))]
3377fn _diff_impl(_: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
3378    match args.as_mut_slice() {
3379        [RuntimeValue::Array(a1), RuntimeValue::Array(a2)] => {
3380            let a1_debug: Vec<String> = a1.iter().map(|v| format!("{:?}", v)).collect();
3381            let a2_debug: Vec<String> = a2.iter().map(|v| format!("{:?}", v)).collect();
3382            let a1_slices: Vec<&str> = a1_debug.iter().map(|s| s.as_str()).collect();
3383            let a2_slices: Vec<&str> = a2_debug.iter().map(|s| s.as_str()).collect();
3384            let diff = TextDiff::from_slices(&a1_slices, &a2_slices);
3385            let changes: Vec<_> = diff.iter_all_changes().collect();
3386            let mut result = Vec::new();
3387            let mut i = 0;
3388            while i < changes.len() {
3389                if changes[i].tag() == ChangeTag::Delete
3390                    && i + 1 < changes.len()
3391                    && changes[i + 1].tag() == ChangeTag::Insert
3392                {
3393                    let old_idx = changes[i].old_index().unwrap();
3394                    let new_idx = changes[i + 1].new_index().unwrap();
3395                    let old_val = &a1[old_idx];
3396                    let new_val = &a2[new_idx];
3397                    if let (RuntimeValue::String(s1), RuntimeValue::String(s2)) = (old_val, new_val) {
3398                        let (del_inline, ins_inline) = build_char_inline_diff(s1.as_str(), s2.as_str());
3399                        let mut del_map = BTreeMap::new();
3400                        del_map.insert(Ident::new("tag"), RuntimeValue::String("delete".into()));
3401                        del_map.insert(Ident::new("value"), old_val.clone());
3402                        del_map.insert(Ident::new("inline"), RuntimeValue::Array(del_inline));
3403                        result.push(RuntimeValue::Dict(del_map));
3404                        let mut ins_map = BTreeMap::new();
3405                        ins_map.insert(Ident::new("tag"), RuntimeValue::String("insert".into()));
3406                        ins_map.insert(Ident::new("value"), new_val.clone());
3407                        ins_map.insert(Ident::new("inline"), RuntimeValue::Array(ins_inline));
3408                        result.push(RuntimeValue::Dict(ins_map));
3409                    } else {
3410                        let mut del_map = BTreeMap::new();
3411                        del_map.insert(Ident::new("tag"), RuntimeValue::String("delete".into()));
3412                        del_map.insert(Ident::new("value"), old_val.clone());
3413                        result.push(RuntimeValue::Dict(del_map));
3414                        let mut ins_map = BTreeMap::new();
3415                        ins_map.insert(Ident::new("tag"), RuntimeValue::String("insert".into()));
3416                        ins_map.insert(Ident::new("value"), new_val.clone());
3417                        result.push(RuntimeValue::Dict(ins_map));
3418                    }
3419                    i += 2;
3420                } else {
3421                    let tag_str = match changes[i].tag() {
3422                        ChangeTag::Equal => "equal",
3423                        ChangeTag::Delete => "delete",
3424                        ChangeTag::Insert => "insert",
3425                    };
3426                    let value = match changes[i].tag() {
3427                        ChangeTag::Equal | ChangeTag::Delete => a1[changes[i].old_index().unwrap()].clone(),
3428                        ChangeTag::Insert => a2[changes[i].new_index().unwrap()].clone(),
3429                    };
3430                    let mut map = BTreeMap::new();
3431                    map.insert(Ident::new("tag"), RuntimeValue::String(tag_str.into()));
3432                    map.insert(Ident::new("value"), value);
3433                    result.push(RuntimeValue::Dict(map));
3434                    i += 1;
3435                }
3436            }
3437            Ok(RuntimeValue::Array(result))
3438        }
3439        [a1, a2] => {
3440            let s1 = a1.to_string();
3441            let s2 = a2.to_string();
3442            let line_diff = TextDiff::from_lines(&s1, &s2);
3443            let changes: Vec<_> = line_diff.iter_all_changes().collect();
3444            let mut result = Vec::new();
3445            let mut i = 0;
3446            while i < changes.len() {
3447                if changes[i].tag() == ChangeTag::Delete
3448                    && i + 1 < changes.len()
3449                    && changes[i + 1].tag() == ChangeTag::Insert
3450                {
3451                    let old_val = changes[i].value().trim_end_matches('\n');
3452                    let new_val = changes[i + 1].value().trim_end_matches('\n');
3453                    let (del_inline, ins_inline) = build_char_inline_diff(old_val, new_val);
3454                    let mut del_map = BTreeMap::new();
3455                    del_map.insert(Ident::new("tag"), RuntimeValue::String("delete".into()));
3456                    del_map.insert(Ident::new("value"), RuntimeValue::String(old_val.to_string()));
3457                    del_map.insert(Ident::new("inline"), RuntimeValue::Array(del_inline));
3458                    result.push(RuntimeValue::Dict(del_map));
3459                    let mut ins_map = BTreeMap::new();
3460                    ins_map.insert(Ident::new("tag"), RuntimeValue::String("insert".into()));
3461                    ins_map.insert(Ident::new("value"), RuntimeValue::String(new_val.to_string()));
3462                    ins_map.insert(Ident::new("inline"), RuntimeValue::Array(ins_inline));
3463                    result.push(RuntimeValue::Dict(ins_map));
3464                    i += 2;
3465                } else {
3466                    let tag_str = match changes[i].tag() {
3467                        ChangeTag::Equal => "equal",
3468                        ChangeTag::Delete => "delete",
3469                        ChangeTag::Insert => "insert",
3470                    };
3471                    let val = changes[i].value().trim_end_matches('\n').to_string();
3472                    let mut map = BTreeMap::new();
3473                    map.insert(Ident::new("tag"), RuntimeValue::String(tag_str.into()));
3474                    map.insert(Ident::new("value"), RuntimeValue::String(val));
3475                    result.push(RuntimeValue::Dict(map));
3476                    i += 1;
3477                }
3478            }
3479            Ok(RuntimeValue::Array(result))
3480        }
3481        _ => unreachable!("_diff should receive exactly two arguments, both arrays or both non-arrays"),
3482    }
3483}
3484
3485#[mq_macros::mq_fn(name = "basename", params = Fixed(1))]
3486fn basename_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
3487    match args.as_mut_slice() {
3488        [RuntimeValue::String(s)] => Ok(RuntimeValue::String(path::basename(s))),
3489        [a] => Err(Error::InvalidTypes(ident.to_string(), vec![std::mem::take(a)])),
3490        _ => unreachable!("basename should always receive exactly one argument"),
3491    }
3492}
3493
3494#[mq_macros::mq_fn(name = "dirname", params = Fixed(1))]
3495fn dirname_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
3496    match args.as_mut_slice() {
3497        [RuntimeValue::String(s)] => Ok(RuntimeValue::String(path::dirname(s))),
3498        [a] => Err(Error::InvalidTypes(ident.to_string(), vec![std::mem::take(a)])),
3499        _ => unreachable!("dirname should always receive exactly one argument"),
3500    }
3501}
3502
3503#[mq_macros::mq_fn(name = "extname", params = Fixed(1))]
3504fn extname_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
3505    match args.as_mut_slice() {
3506        [RuntimeValue::String(s)] => Ok(RuntimeValue::String(path::extname(s))),
3507        [a] => Err(Error::InvalidTypes(ident.to_string(), vec![std::mem::take(a)])),
3508        _ => unreachable!("extname should always receive exactly one argument"),
3509    }
3510}
3511
3512#[mq_macros::mq_fn(name = "stem", params = Fixed(1))]
3513fn stem_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
3514    match args.as_mut_slice() {
3515        [RuntimeValue::String(s)] => Ok(RuntimeValue::String(path::stem(s))),
3516        [a] => Err(Error::InvalidTypes(ident.to_string(), vec![std::mem::take(a)])),
3517        _ => unreachable!("stem should always receive exactly one argument"),
3518    }
3519}
3520
3521#[mq_macros::mq_fn(name = "path_join", params = Fixed(2))]
3522fn path_join_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
3523    match args.as_mut_slice() {
3524        [RuntimeValue::String(base), RuntimeValue::String(component)] => {
3525            path::path_join(base, component).map(RuntimeValue::String)
3526        }
3527        [a, b] => Err(Error::InvalidTypes(
3528            ident.to_string(),
3529            vec![std::mem::take(a), std::mem::take(b)],
3530        )),
3531        _ => unreachable!("path_join should always receive exactly two arguments"),
3532    }
3533}
3534
3535#[cfg(feature = "file-io")]
3536#[mq_macros::mq_fn(name = "read_file", params = Fixed(1))]
3537fn read_file_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
3538    match args.as_mut_slice() {
3539        [RuntimeValue::String(path)] => match std::fs::read_to_string(&path) {
3540            Ok(content) => Ok(RuntimeValue::String(content)),
3541            Err(e) => Err(Error::Runtime(format!("Failed to read file {}: {}", path, e))),
3542        },
3543        [a] => Err(Error::InvalidTypes(ident.to_string(), vec![std::mem::take(a)])),
3544        _ => unreachable!("read_file should always receive exactly one argument"),
3545    }
3546}
3547
3548#[cfg(feature = "file-io")]
3549#[mq_macros::mq_fn(name = "file_exists", params = Fixed(1))]
3550fn file_exists_impl(ident: &Ident, _: &RuntimeValue, mut args: Args, _: &SharedEnv) -> Result<RuntimeValue, Error> {
3551    match args.as_mut_slice() {
3552        [RuntimeValue::String(path)] => Ok(std::path::Path::new(path).exists().into()),
3553        [a] => Err(Error::InvalidTypes(ident.to_string(), vec![std::mem::take(a)])),
3554        _ => unreachable!("file_exists should always receive exactly one argument"),
3555    }
3556}
3557
3558const fn fnv1a_hash_64(s: &str) -> u64 {
3559    const FNV_OFFSET_BASIS_64: u64 = 14695981039346656037;
3560    const FNV_PRIME_64: u64 = 1099511628211;
3561
3562    let bytes = s.as_bytes();
3563    let mut hash = FNV_OFFSET_BASIS_64;
3564    let mut i = 0;
3565    while i < bytes.len() {
3566        hash ^= bytes[i] as u64;
3567        hash = hash.wrapping_mul(FNV_PRIME_64);
3568        i += 1;
3569    }
3570    hash
3571}
3572
3573pub fn get_builtin_functions(name: &Ident) -> Option<&'static BuiltinFunction> {
3574    name.resolve_with(get_builtin_functions_by_str)
3575}
3576
3577mq_macros::builtin_dispatch! {
3578    PARTIAL,
3579    HALT,
3580    ERROR,
3581    PRINT,
3582    STDERR,
3583    TYPE,
3584    ARRAY,
3585    FLATTEN,
3586    CONVERT,
3587    FROM_DATE,
3588    TO_DATE,
3589    NOW,
3590    GMTIME,
3591    LOCALTIME,
3592    MKTIME,
3593    STRFTIME,
3594    DATE_ADD,
3595    DATE_DIFF,
3596    BASE64,
3597    BASE64D,
3598    BASE64URL,
3599    BASE64URLD,
3600    MD5,
3601    SHA256,
3602    SHA512,
3603    MIN,
3604    MAX,
3605    FROM_HTML,
3606    TO_HTML,
3607    TO_MARKDOWN_STRING,
3608    TO_STRING,
3609    TO_NUMBER,
3610    TO_ARRAY,
3611    TO_BYTES,
3612    FROM_HEX,
3613    TO_HEX,
3614    UTF8,
3615    XOR,
3616    BAND,
3617    BOR,
3618    BNOT,
3619    PACK,
3620    UNPACK,
3621    URL_ENCODE,
3622    TO_TEXT,
3623    ENDS_WITH,
3624    STARTS_WITH,
3625    REGEX_MATCH,
3626    IS_REGEX_MATCH,
3627    IS_NOT_REGEX_MATCH,
3628    CAPTURE,
3629    DOWNCASE,
3630    GSUB,
3631    REPLACE,
3632    REPEAT,
3633    EXPLODE,
3634    IMPLODE,
3635    TRIM,
3636    LTRIM,
3637    RTRIM,
3638    UPCASE,
3639    UPDATE,
3640    SLICE,
3641    POW,
3642    LN,
3643    LOG10,
3644    SQRT,
3645    EXP,
3646    INDEX,
3647    LEN,
3648    UTF8BYTELEN,
3649    RINDEX,
3650    RANGE,
3651    DEL,
3652    JOIN,
3653    REVERSE,
3654    SORT,
3655    _SORT_BY_IMPL,
3656    COMPACT,
3657    SPLIT,
3658    UNIQ,
3659    CEIL,
3660    FLOOR,
3661    ROUND,
3662    TRUNC,
3663    ABS,
3664    EQ,
3665    NE,
3666    GT,
3667    GTE,
3668    LT,
3669    LTE,
3670    ADD,
3671    SUB,
3672    DIV,
3673    MUL,
3674    MOD,
3675    AND,
3676    OR,
3677    NOT,
3678    ATTR,
3679    SET_ATTR,
3680    TO_CODE,
3681    TO_CODE_INLINE,
3682    TO_H,
3683    TO_HR,
3684    TO_LINK,
3685    TO_IMAGE,
3686    TO_MATH,
3687    TO_MATH_INLINE,
3688    TO_MD_NAME,
3689    SET_LIST_ORDERED,
3690    TO_STRONG,
3691    TO_EM,
3692    TO_MD_TEXT,
3693    TO_MD_LIST,
3694    TO_MD_TABLE_ROW,
3695    TO_MD_TABLE_CELL,
3696    GET_TITLE,
3697    GET_URL,
3698    SET_CHECK,
3699    SET_REF,
3700    SET_CODE_BLOCK_LANG,
3701    DICT,
3702    GET,
3703    SET,
3704    KEYS,
3705    VALUES,
3706    ENTRIES,
3707    INSERT,
3708    NEGATE,
3709    INTERN,
3710    NAN,
3711    IS_NAN,
3712    INFINITE,
3713    COALESCE,
3714    INPUT,
3715    ALL_SYMBOLS,
3716    TO_MARKDOWN,
3717    TO_MDX,
3718    _GET_MARKDOWN_POSITION,
3719    _CSV_PARSE,
3720    _JSON_PARSE,
3721    _YAML_PARSE,
3722    _TOON_PARSE,
3723    _TOML_PARSE,
3724    _CBOR_PARSE,
3725    _CBOR_STRINGIFY,
3726    _HCL_PARSE,
3727    _HCL_STRINGIFY,
3728    _XML_PARSE,
3729    SET_VARIABLE,
3730    GET_VARIABLE,
3731    IS_DEBUG_MODE,
3732    _AST_GET_ARGS,
3733    _AST_TO_CODE,
3734    SHIFT_LEFT,
3735    SHIFT_RIGHT,
3736    _DIFF,
3737    BASENAME,
3738    DIRNAME,
3739    EXTNAME,
3740    STEM,
3741    PATH_JOIN,
3742    #[cfg(feature = "file-io")]
3743    READ_FILE,
3744    #[cfg(feature = "file-io")]
3745    FILE_EXISTS,
3746}
3747
3748#[derive(Clone, Debug)]
3749pub struct BuiltinSelectorDoc {
3750    pub description: &'static str,
3751    pub params: &'static [&'static str],
3752}
3753
3754pub static BUILTIN_SELECTOR_DOC: LazyLock<FxHashMap<SmolStr, BuiltinSelectorDoc>> = LazyLock::new(|| {
3755    let mut map = FxHashMap::with_capacity_and_hasher(100, FxBuildHasher);
3756
3757    map.insert(
3758        SmolStr::new(".h"),
3759        BuiltinSelectorDoc {
3760            description: "Selects a heading node with the specified depth.",
3761            params: &[],
3762        },
3763    );
3764
3765    map.insert(
3766        SmolStr::new(".text"),
3767        BuiltinSelectorDoc {
3768            description: "Selects a text node.",
3769            params: &[],
3770        },
3771    );
3772
3773    map.insert(
3774        SmolStr::new(".h1"),
3775        BuiltinSelectorDoc {
3776            description: "Selects a heading node with the 1 depth.",
3777            params: &[],
3778        },
3779    );
3780
3781    map.insert(
3782        SmolStr::new(".h2"),
3783        BuiltinSelectorDoc {
3784            description: "Selects a heading node with the 2 depth.",
3785            params: &[],
3786        },
3787    );
3788
3789    map.insert(
3790        SmolStr::new(".h3"),
3791        BuiltinSelectorDoc {
3792            description: "Selects a heading node with the 3 depth.",
3793            params: &[],
3794        },
3795    );
3796
3797    map.insert(
3798        SmolStr::new(".h4"),
3799        BuiltinSelectorDoc {
3800            description: "Selects a heading node with the 4 depth.",
3801            params: &[],
3802        },
3803    );
3804
3805    map.insert(
3806        SmolStr::new(".h5"),
3807        BuiltinSelectorDoc {
3808            description: "Selects a heading node with the 5 depth.",
3809            params: &[],
3810        },
3811    );
3812
3813    map.insert(
3814        SmolStr::new(".h6"),
3815        BuiltinSelectorDoc {
3816            description: "Selects a heading node with the 6 depth.",
3817            params: &[],
3818        },
3819    );
3820
3821    map.insert(
3822        SmolStr::new(".code"),
3823        BuiltinSelectorDoc {
3824            description: "Selects a code block node with the specified language.",
3825            params: &[],
3826        },
3827    );
3828
3829    map.insert(
3830        SmolStr::new(".code_inline"),
3831        BuiltinSelectorDoc {
3832            description: "Selects an inline code node.",
3833            params: &[],
3834        },
3835    );
3836
3837    map.insert(
3838        SmolStr::new(".inline_math"),
3839        BuiltinSelectorDoc {
3840            description: "Selects an inline math node.",
3841            params: &[],
3842        },
3843    );
3844
3845    map.insert(
3846        SmolStr::new(".strong"),
3847        BuiltinSelectorDoc {
3848            description: "Selects a strong (bold) node.",
3849            params: &[],
3850        },
3851    );
3852
3853    map.insert(
3854        SmolStr::new(".emphasis"),
3855        BuiltinSelectorDoc {
3856            description: "Selects an emphasis (italic) node.",
3857            params: &[],
3858        },
3859    );
3860
3861    map.insert(
3862        SmolStr::new(".delete"),
3863        BuiltinSelectorDoc {
3864            description: "Selects a delete (strikethrough) node.",
3865            params: &[],
3866        },
3867    );
3868
3869    map.insert(
3870        SmolStr::new(".link"),
3871        BuiltinSelectorDoc {
3872            description: "Selects a link node.",
3873            params: &[],
3874        },
3875    );
3876
3877    map.insert(
3878        SmolStr::new(".link_ref"),
3879        BuiltinSelectorDoc {
3880            description: "Selects a link reference node.",
3881            params: &[],
3882        },
3883    );
3884
3885    map.insert(
3886        SmolStr::new(".image"),
3887        BuiltinSelectorDoc {
3888            description: "Selects an image node.",
3889            params: &[],
3890        },
3891    );
3892
3893    map.insert(
3894        SmolStr::new(".heading"),
3895        BuiltinSelectorDoc {
3896            description: "Selects a heading node with the specified depth.",
3897            params: &[],
3898        },
3899    );
3900
3901    map.insert(
3902        SmolStr::new(".horizontal_rule"),
3903        BuiltinSelectorDoc {
3904            description: "Selects a horizontal rule node.",
3905            params: &[],
3906        },
3907    );
3908
3909    map.insert(
3910        SmolStr::new(".blockquote"),
3911        BuiltinSelectorDoc {
3912            description: "Selects a blockquote node.",
3913            params: &[],
3914        },
3915    );
3916
3917    map.insert(
3918        SmolStr::new(".[][]"),
3919        BuiltinSelectorDoc {
3920            description: "Selects a table cell node with the specified row and column.",
3921            params: &["row", "column"],
3922        },
3923    );
3924
3925    map.insert(
3926        SmolStr::new(".table"),
3927        BuiltinSelectorDoc {
3928            description: "Selects a table cell node with the specified row and column.",
3929            params: &["row", "column"],
3930        },
3931    );
3932
3933    map.insert(
3934        SmolStr::new(".table_align"),
3935        BuiltinSelectorDoc {
3936            description: "Selects a table align node.",
3937            params: &[],
3938        },
3939    );
3940
3941    map.insert(
3942        SmolStr::new(".html"),
3943        BuiltinSelectorDoc {
3944            description: "Selects an HTML node.",
3945            params: &[],
3946        },
3947    );
3948
3949    map.insert(
3950        SmolStr::new(".<>"),
3951        BuiltinSelectorDoc {
3952            description: "Selects an HTML node.",
3953            params: &[],
3954        },
3955    );
3956
3957    map.insert(
3958        SmolStr::new(".footnote"),
3959        BuiltinSelectorDoc {
3960            description: "Selects a footnote node.",
3961            params: &[],
3962        },
3963    );
3964
3965    map.insert(
3966        SmolStr::new(".mdx_jsx_flow_element"),
3967        BuiltinSelectorDoc {
3968            description: "Selects an MDX JSX flow element node.",
3969            params: &[],
3970        },
3971    );
3972
3973    map.insert(
3974        SmolStr::new(".list"),
3975        BuiltinSelectorDoc {
3976            description: "Selects a list node with the specified index and checked state.",
3977            params: &["indent", "checked"],
3978        },
3979    );
3980
3981    map.insert(
3982        SmolStr::new(".[]"),
3983        BuiltinSelectorDoc {
3984            description: "Selects a list node with the specified index and checked state.",
3985            params: &["indent", "checked"],
3986        },
3987    );
3988
3989    map.insert(
3990        SmolStr::new(".mdx_js_esm"),
3991        BuiltinSelectorDoc {
3992            description: "Selects an MDX JS ESM node.",
3993            params: &[],
3994        },
3995    );
3996
3997    map.insert(
3998        SmolStr::new(".toml"),
3999        BuiltinSelectorDoc {
4000            description: "Selects a TOML node.",
4001            params: &[],
4002        },
4003    );
4004
4005    map.insert(
4006        SmolStr::new(".yaml"),
4007        BuiltinSelectorDoc {
4008            description: "Selects a YAML node.",
4009            params: &[],
4010        },
4011    );
4012
4013    map.insert(
4014        SmolStr::new(".break"),
4015        BuiltinSelectorDoc {
4016            description: "Selects a break node.",
4017            params: &[],
4018        },
4019    );
4020
4021    map.insert(
4022        SmolStr::new(".mdx_text_expression"),
4023        BuiltinSelectorDoc {
4024            description: "Selects an MDX text expression node.",
4025            params: &[],
4026        },
4027    );
4028
4029    map.insert(
4030        SmolStr::new(".footnote_ref"),
4031        BuiltinSelectorDoc {
4032            description: "Selects a footnote reference node.",
4033            params: &[],
4034        },
4035    );
4036
4037    map.insert(
4038        SmolStr::new(".image_ref"),
4039        BuiltinSelectorDoc {
4040            description: "Selects an image reference node.",
4041            params: &[],
4042        },
4043    );
4044
4045    map.insert(
4046        SmolStr::new(".mdx_jsx_text_element"),
4047        BuiltinSelectorDoc {
4048            description: "Selects an MDX JSX text element node.",
4049            params: &[],
4050        },
4051    );
4052
4053    map.insert(
4054        SmolStr::new(".math"),
4055        BuiltinSelectorDoc {
4056            description: "Selects a math node.",
4057            params: &[],
4058        },
4059    );
4060
4061    map.insert(
4062        SmolStr::new(".math_inline"),
4063        BuiltinSelectorDoc {
4064            description: "Selects a math inline node.",
4065            params: &[],
4066        },
4067    );
4068
4069    map.insert(
4070        SmolStr::new(".mdx_flow_expression"),
4071        BuiltinSelectorDoc {
4072            description: "Selects an MDX flow expression node.",
4073            params: &[],
4074        },
4075    );
4076
4077    map.insert(
4078        SmolStr::new(".definition"),
4079        BuiltinSelectorDoc {
4080            description: "Selects a definition node.",
4081            params: &[],
4082        },
4083    );
4084
4085    map.insert(
4086        SmolStr::new(".task"),
4087        BuiltinSelectorDoc {
4088            description: "Selects a task list node.",
4089            params: &[],
4090        },
4091    );
4092
4093    map.insert(
4094        SmolStr::new(".todo"),
4095        BuiltinSelectorDoc {
4096            description: "Selects a todo item in the task list node.",
4097            params: &[],
4098        },
4099    );
4100
4101    map.insert(
4102        SmolStr::new(".done"),
4103        BuiltinSelectorDoc {
4104            description: "Selects a done item in the task list node.",
4105            params: &[],
4106        },
4107    );
4108
4109    map
4110});
4111
4112pub static INTERNAL_FUNCTION_DOC: LazyLock<FxHashMap<SmolStr, BuiltinFunctionDoc>> = LazyLock::new(|| {
4113    let mut map = FxHashMap::default();
4114
4115    map.insert(
4116            SmolStr::new("_sort_by_impl"),
4117            BuiltinFunctionDoc{
4118                description: "Internal implementation of sort_by functionality that sorts arrays of arrays using the first element as the key.",
4119                params: &[],
4120            },
4121        );
4122    map.insert(
4123            SmolStr::new("_get_markdown_position"),
4124            BuiltinFunctionDoc {
4125            description: "Internal function to get the position information of a markdown node, returning row and column data if available.",
4126            params: &["markdown_node"],
4127            },
4128        );
4129    map.insert(
4130        SmolStr::new("is_debug_mode"),
4131        BuiltinFunctionDoc {
4132            description: "Checks if the runtime is currently in debug mode, returning true if a debugger is attached.",
4133            params: &[],
4134        },
4135    );
4136    map.insert(
4137        SmolStr::new("_ast_get_args"),
4138        BuiltinFunctionDoc {
4139            description: "Internal function to extract arguments from an AST call expression, returning an array of arguments to their AST nodes.",
4140            params: &["ast_node"],
4141        },
4142    );
4143    map.insert(
4144        SmolStr::new("_ast_to_code"),
4145        BuiltinFunctionDoc {
4146            description: "Internal function to convert an AST node back to its source code representation as a string.",
4147            params: &["ast_node"],
4148        },
4149    );
4150    map.insert(
4151        SmolStr::new("_csv_parse"),
4152        BuiltinFunctionDoc {
4153            description: "Parses a CSV string into an array of arrays, using the specified delimiter and header options.",
4154            params: &["csv_string", "delimiter", "has_header"],
4155        },
4156    );
4157    map.insert(
4158        SmolStr::new("_xml_parse"),
4159        BuiltinFunctionDoc {
4160            description: "Parses an XML string and returns the corresponding data structure.",
4161            params: &["xml_string"],
4162        },
4163    );
4164    map.insert(
4165        SmolStr::new("_json_parse"),
4166        BuiltinFunctionDoc {
4167            description: "Parses a JSON string into a data structure.",
4168            params: &["json_string"],
4169        },
4170    );
4171    map.insert(
4172        SmolStr::new("_yaml_parse"),
4173        BuiltinFunctionDoc {
4174            description: "Parses a YAML string into a data structure.",
4175            params: &["yaml_string"],
4176        },
4177    );
4178    map.insert(
4179        SmolStr::new("_toon_parse"),
4180        BuiltinFunctionDoc {
4181            description: "Parses a TOON string into a data structure.",
4182            params: &["toon_string"],
4183        },
4184    );
4185    map.insert(
4186        SmolStr::new("_toml_parse"),
4187        BuiltinFunctionDoc {
4188            description: "Parses a TOML string into a data structure.",
4189            params: &["toml_string"],
4190        },
4191    );
4192    map.insert(
4193        SmolStr::new("_cbor_parse"),
4194        BuiltinFunctionDoc {
4195            description: "Parses a base64-encoded CBOR string or raw bytes into a data structure.",
4196            params: &["input"],
4197        },
4198    );
4199    map.insert(
4200        SmolStr::new("_cbor_stringify"),
4201        BuiltinFunctionDoc {
4202            description: "Serializes a value to CBOR bytes.",
4203            params: &["value"],
4204        },
4205    );
4206    map.insert(
4207        SmolStr::new("_hcl_parse"),
4208        BuiltinFunctionDoc {
4209            description: "Parses an HCL string into a data structure.",
4210            params: &["hcl_string"],
4211        },
4212    );
4213    map.insert(
4214        SmolStr::new("_hcl_stringify"),
4215        BuiltinFunctionDoc {
4216            description: "Serializes a value to an HCL string.",
4217            params: &["value"],
4218        },
4219    );
4220    map.insert(
4221        SmolStr::new("_diff"),
4222        BuiltinFunctionDoc {
4223            description: "Internal function to compute the difference between two values, returning an array of changes.",
4224            params: &["value1", "value2"],
4225        },
4226    );
4227
4228    map
4229});
4230
4231#[derive(Clone, Debug)]
4232pub struct BuiltinFunctionDoc {
4233    pub description: &'static str,
4234    pub params: &'static [&'static str],
4235}
4236
4237pub static BUILTIN_FUNCTION_DOC: LazyLock<FxHashMap<SmolStr, BuiltinFunctionDoc>> = LazyLock::new(|| {
4238    let mut map = FxHashMap::with_capacity_and_hasher(110, FxBuildHasher);
4239
4240    map.insert(
4241        SmolStr::new("halt"),
4242        BuiltinFunctionDoc {
4243            description: "Terminates the program with the given exit code.",
4244            params: &["exit_code"],
4245        },
4246    );
4247    map.insert(
4248        SmolStr::new("error"),
4249        BuiltinFunctionDoc {
4250            description: "Raises a user-defined error with the specified message.",
4251            params: &["message"],
4252        },
4253    );
4254    map.insert(
4255        SmolStr::new("exp"),
4256        BuiltinFunctionDoc {
4257            description: "Returns the exponential (e^x) of the given number.",
4258            params: &["number"],
4259        },
4260    );
4261    map.insert(
4262        SmolStr::new("assert"),
4263        BuiltinFunctionDoc {
4264            description: "Asserts that two values are equal, returns the value if true, otherwise raises an error.",
4265            params: &["value1", "value2"],
4266        },
4267    );
4268    map.insert(
4269        SmolStr::new("print"),
4270        BuiltinFunctionDoc {
4271            description: "Prints a message to standard output and returns the current value.",
4272            params: &["message"],
4273        },
4274    );
4275    map.insert(
4276        SmolStr::new("stderr"),
4277        BuiltinFunctionDoc {
4278            description: "Prints a message to standard error and returns the current value.",
4279            params: &["message"],
4280        },
4281    );
4282    map.insert(
4283        SmolStr::new("type"),
4284        BuiltinFunctionDoc {
4285            description: "Returns the type of the given value.",
4286            params: &["value"],
4287        },
4288    );
4289    map.insert(
4290        SmolStr::new("ln"),
4291        BuiltinFunctionDoc {
4292            description: "Returns the natural logarithm (base e) of the given number.",
4293            params: &["number"],
4294        },
4295    );
4296    map.insert(
4297        SmolStr::new("log10"),
4298        BuiltinFunctionDoc {
4299            description: "Returns the base-10 logarithm of the given number.",
4300            params: &["number"],
4301        },
4302    );
4303    map.insert(
4304        SmolStr::new(constants::builtins::ARRAY),
4305        BuiltinFunctionDoc {
4306            description: "Creates an array from the given values.",
4307            params: &["values"],
4308        },
4309    );
4310    map.insert(
4311        SmolStr::new("flatten"),
4312        BuiltinFunctionDoc {
4313            description: "Flattens a nested array into a single level array.",
4314            params: &["array"],
4315        },
4316    );
4317    map.insert(
4318        SmolStr::new("from_date"),
4319        BuiltinFunctionDoc {
4320            description: "Converts a date string to a timestamp.",
4321            params: &["date_str"],
4322        },
4323    );
4324    map.insert(
4325        SmolStr::new("to_date"),
4326        BuiltinFunctionDoc {
4327            description: "Converts a timestamp to a date string with the given format.",
4328            params: &["timestamp", "format"],
4329        },
4330    );
4331    map.insert(
4332        SmolStr::new("now"),
4333        BuiltinFunctionDoc {
4334            description: "Returns the current timestamp.",
4335            params: &[],
4336        },
4337    );
4338    map.insert(
4339        SmolStr::new("gmtime"),
4340        BuiltinFunctionDoc {
4341            description: "Converts Unix timestamp (seconds since epoch) to broken-down UTC time array [year, mon (0-11), mday, hour, min, sec, wday (0=Sun), yday (0-365)].",
4342            params: &["timestamp"],
4343        },
4344    );
4345    map.insert(
4346        SmolStr::new("localtime"),
4347        BuiltinFunctionDoc {
4348            description: "Converts Unix timestamp (seconds since epoch) to broken-down local time array [year, mon (0-11), mday, hour, min, sec, wday (0=Sun), yday (0-365)].",
4349            params: &["timestamp"],
4350        },
4351    );
4352    map.insert(
4353        SmolStr::new("mktime"),
4354        BuiltinFunctionDoc {
4355            description: "Converts broken-down UTC time array [year, mon (0-11), mday, hour, min, sec, wday, yday] to Unix timestamp (seconds since epoch).",
4356            params: &["time_array"],
4357        },
4358    );
4359    map.insert(
4360        SmolStr::new("strftime"),
4361        BuiltinFunctionDoc {
4362            description: "Formats a Unix timestamp (seconds) as a date string using the given strftime format (e.g. \"%Y-%m-%d\").",
4363            params: &["timestamp", "format"],
4364        },
4365    );
4366    map.insert(
4367        SmolStr::new("date_add"),
4368        BuiltinFunctionDoc {
4369            description: "Adds n units to a broken-down time array and returns a new array. Units: \"seconds\", \"minutes\", \"hours\", \"days\", \"weeks\", \"months\", \"years\". Month/year arithmetic is calendar-aware.",
4370            params: &["array", "n", "unit"],
4371        },
4372    );
4373    map.insert(
4374        SmolStr::new("date_diff"),
4375        BuiltinFunctionDoc {
4376            description: "Returns the difference (array2 - array1) in the given unit. Units: \"seconds\", \"minutes\", \"hours\", \"days\", \"weeks\".",
4377            params: &["array1", "array2", "unit"],
4378        },
4379    );
4380    map.insert(
4381        SmolStr::new("base64"),
4382        BuiltinFunctionDoc {
4383            description: "Encodes the given string to base64.",
4384            params: &["input"],
4385        },
4386    );
4387    map.insert(
4388        SmolStr::new("base64d"),
4389        BuiltinFunctionDoc {
4390            description: "Decodes the given base64 string.",
4391            params: &["input"],
4392        },
4393    );
4394    map.insert(
4395        SmolStr::new("base64url"),
4396        BuiltinFunctionDoc {
4397            description: "Encodes the given string to URL-safe base64.",
4398            params: &["input"],
4399        },
4400    );
4401    map.insert(
4402        SmolStr::new("base64urld"),
4403        BuiltinFunctionDoc {
4404            description: "Decodes the given URL-safe base64 string.",
4405            params: &["input"],
4406        },
4407    );
4408    map.insert(
4409        SmolStr::new("min"),
4410        BuiltinFunctionDoc {
4411            description: "Returns the minimum of two values.",
4412            params: &["value1", "value2"],
4413        },
4414    );
4415    map.insert(
4416        SmolStr::new("max"),
4417        BuiltinFunctionDoc {
4418            description: "Returns the maximum of two values.",
4419            params: &["value1", "value2"],
4420        },
4421    );
4422    map.insert(
4423        SmolStr::new("from_html"),
4424        BuiltinFunctionDoc {
4425            description: "Converts the given HTML string to Markdown.",
4426            params: &["html"],
4427        },
4428    );
4429    map.insert(
4430        SmolStr::new("to_html"),
4431        BuiltinFunctionDoc {
4432            description: "Converts the given markdown string to HTML.",
4433            params: &["markdown"],
4434        },
4435    );
4436    map.insert(
4437        SmolStr::new("to_string"),
4438        BuiltinFunctionDoc {
4439            description: "Converts the given value to a string.",
4440            params: &["value"],
4441        },
4442    );
4443    map.insert(
4444        SmolStr::new("to_markdown_string"),
4445        BuiltinFunctionDoc {
4446            description: "Converts the given value(s) to a markdown string representation.",
4447            params: &["value"],
4448        },
4449    );
4450    map.insert(
4451        SmolStr::new("to_number"),
4452        BuiltinFunctionDoc {
4453            description: "Converts the given value to a number.",
4454            params: &["value"],
4455        },
4456    );
4457    map.insert(
4458        SmolStr::new("to_array"),
4459        BuiltinFunctionDoc {
4460            description: "Converts the given value to an array.",
4461            params: &["value"],
4462        },
4463    );
4464    map.insert(
4465        SmolStr::new("md5"),
4466        BuiltinFunctionDoc {
4467            description: "Computes the MD5 hash of a string or bytes and returns a lowercase hex string.",
4468            params: &["input"],
4469        },
4470    );
4471    map.insert(
4472        SmolStr::new("sha256"),
4473        BuiltinFunctionDoc {
4474            description: "Computes the SHA-256 hash of a string or bytes and returns a lowercase hex string.",
4475            params: &["input"],
4476        },
4477    );
4478    map.insert(
4479        SmolStr::new("sha512"),
4480        BuiltinFunctionDoc {
4481            description: "Computes the SHA-512 hash of a string or bytes and returns a lowercase hex string.",
4482            params: &["input"],
4483        },
4484    );
4485    map.insert(
4486        SmolStr::new("to_bytes"),
4487        BuiltinFunctionDoc {
4488            description: "Converts a string (UTF-8), array of numbers, or bytes to raw bytes.",
4489            params: &["value"],
4490        },
4491    );
4492    map.insert(
4493        SmolStr::new("from_hex"),
4494        BuiltinFunctionDoc {
4495            description: "Parses a hex string into raw bytes.",
4496            params: &["hex_string"],
4497        },
4498    );
4499    map.insert(
4500        SmolStr::new("to_hex"),
4501        BuiltinFunctionDoc {
4502            description: "Encodes raw bytes as a lowercase hex string.",
4503            params: &["bytes"],
4504        },
4505    );
4506    map.insert(
4507        SmolStr::new("utf8"),
4508        BuiltinFunctionDoc {
4509            description: "Decodes bytes as a UTF-8 string, returning an error if the bytes are not valid UTF-8.",
4510            params: &["bytes"],
4511        },
4512    );
4513    map.insert(
4514        SmolStr::new("xor"),
4515        BuiltinFunctionDoc {
4516            description: "Computes the bitwise XOR of two byte arrays of equal length.",
4517            params: &["bytes1", "bytes2"],
4518        },
4519    );
4520    map.insert(
4521        SmolStr::new("band"),
4522        BuiltinFunctionDoc {
4523            description: "Computes the bitwise AND of two byte arrays of equal length.",
4524            params: &["bytes1", "bytes2"],
4525        },
4526    );
4527    map.insert(
4528        SmolStr::new("bor"),
4529        BuiltinFunctionDoc {
4530            description: "Computes the bitwise OR of two byte arrays of equal length.",
4531            params: &["bytes1", "bytes2"],
4532        },
4533    );
4534    map.insert(
4535        SmolStr::new("bnot"),
4536        BuiltinFunctionDoc {
4537            description: "Computes the bitwise NOT (complement) of a byte array.",
4538            params: &["bytes"],
4539        },
4540    );
4541    map.insert(
4542        SmolStr::new("pack"),
4543        BuiltinFunctionDoc {
4544            description: "Packs a number into bytes using the given format. Supported formats: u8, i8, u16be/le, i16be/le, u32be/le, i32be/le, u64be/le, i64be/le, f32be/le, f64be/le.",
4545            params: &["format", "value"],
4546        },
4547    );
4548    map.insert(
4549        SmolStr::new("unpack"),
4550        BuiltinFunctionDoc {
4551            description: "Unpacks a number from bytes using the given format. Supported formats: u8, i8, u16be/le, i16be/le, u32be/le, i32be/le, u64be/le, i64be/le, f32be/le, f64be/le.",
4552            params: &["format", "bytes"],
4553        },
4554    );
4555    map.insert(
4556        SmolStr::new("url_encode"),
4557        BuiltinFunctionDoc {
4558            description: "URL-encodes the given string.",
4559            params: &["input"],
4560        },
4561    );
4562    map.insert(
4563        SmolStr::new("to_text"),
4564        BuiltinFunctionDoc {
4565            description: "Converts the given markdown node to plain text.",
4566            params: &["markdown"],
4567        },
4568    );
4569    map.insert(
4570        SmolStr::new("ends_with"),
4571        BuiltinFunctionDoc {
4572            description: "Checks if the given string or byte array ends with the specified suffix.",
4573            params: &["value", "suffix"],
4574        },
4575    );
4576    map.insert(
4577        SmolStr::new("starts_with"),
4578        BuiltinFunctionDoc {
4579            description: "Checks if the given string or byte array starts with the specified prefix.",
4580            params: &["value", "prefix"],
4581        },
4582    );
4583    map.insert(
4584        SmolStr::new("regex_match"),
4585        BuiltinFunctionDoc {
4586            description: "Finds all matches of the given pattern in the string.",
4587            params: &["string", "pattern"],
4588        },
4589    );
4590    map.insert(
4591        SmolStr::new("is_regex_match"),
4592        BuiltinFunctionDoc {
4593            description: "Checks if the given pattern matches the string.",
4594            params: &["string", "pattern"],
4595        },
4596    );
4597    map.insert(
4598        SmolStr::new("is_not_regex_match"),
4599        BuiltinFunctionDoc {
4600            description: "Checks if the given pattern does not match the string.",
4601            params: &["string", "pattern"],
4602        },
4603    );
4604    map.insert(
4605        SmolStr::new("downcase"),
4606        BuiltinFunctionDoc {
4607            description: "Converts the given string to lowercase.",
4608            params: &["input"],
4609        },
4610    );
4611    map.insert(
4612        SmolStr::new("gsub"),
4613        BuiltinFunctionDoc {
4614            description: "Replaces all occurrences matching a regular expression pattern with the replacement string.",
4615            params: &["from", "pattern", "to"],
4616        },
4617    );
4618    map.insert(
4619        SmolStr::new("replace"),
4620        BuiltinFunctionDoc {
4621            description: "Replaces all occurrences of a substring with another substring.",
4622            params: &["from", "pattern", "to"],
4623        },
4624    );
4625    map.insert(
4626        SmolStr::new("repeat"),
4627        BuiltinFunctionDoc {
4628            description: "Repeats the given string a specified number of times.",
4629            params: &["string", "count"],
4630        },
4631    );
4632    map.insert(
4633        SmolStr::new("explode"),
4634        BuiltinFunctionDoc {
4635            description: "Splits the given string into an array of characters.",
4636            params: &["string"],
4637        },
4638    );
4639    map.insert(
4640        SmolStr::new("implode"),
4641        BuiltinFunctionDoc {
4642            description: "Joins an array of characters into a string.",
4643            params: &["array"],
4644        },
4645    );
4646    map.insert(
4647        SmolStr::new("trim"),
4648        BuiltinFunctionDoc {
4649            description: "Trims whitespace from both ends of the given string.",
4650            params: &["input"],
4651        },
4652    );
4653    map.insert(
4654        SmolStr::new("ltrim"),
4655        BuiltinFunctionDoc {
4656            description: "Trims whitespace from the left end of the given string.",
4657            params: &["input"],
4658        },
4659    );
4660    map.insert(
4661        SmolStr::new("rtrim"),
4662        BuiltinFunctionDoc {
4663            description: "Trims whitespace from the right end of the given string.",
4664            params: &["input"],
4665        },
4666    );
4667    map.insert(
4668        SmolStr::new("upcase"),
4669        BuiltinFunctionDoc {
4670            description: "Converts the given string to uppercase.",
4671            params: &["input"],
4672        },
4673    );
4674    map.insert(
4675        SmolStr::new(constants::builtins::SLICE),
4676        BuiltinFunctionDoc {
4677            description: "Extracts a substring from the given string.",
4678            params: &["string", "start", "end"],
4679        },
4680    );
4681    map.insert(
4682        SmolStr::new("update"),
4683        BuiltinFunctionDoc {
4684            description: "Update the value with specified value.",
4685            params: &["target_value", "source_value"],
4686        },
4687    );
4688    map.insert(
4689        SmolStr::new("pow"),
4690        BuiltinFunctionDoc {
4691            description: "Raises the base to the power of the exponent.",
4692            params: &["base", "exponent"],
4693        },
4694    );
4695    map.insert(
4696        SmolStr::new("index"),
4697        BuiltinFunctionDoc {
4698            description: "Finds the first occurrence of a substring or byte subsequence. Returns -1 if not found.",
4699            params: &["value", "needle"],
4700        },
4701    );
4702    map.insert(
4703        SmolStr::new("len"),
4704        BuiltinFunctionDoc {
4705            description: "Returns the length of the given string or array.",
4706            params: &["value"],
4707        },
4708    );
4709    map.insert(
4710        SmolStr::new("rindex"),
4711        BuiltinFunctionDoc {
4712            description: "Finds the last occurrence of a substring or byte subsequence. Returns -1 if not found.",
4713            params: &["value", "needle"],
4714        },
4715    );
4716    map.insert(
4717        SmolStr::new("join"),
4718        BuiltinFunctionDoc {
4719            description: "Joins the elements of an array into a string with the given separator.",
4720            params: &["array", "separator"],
4721        },
4722    );
4723    map.insert(
4724        SmolStr::new("reverse"),
4725        BuiltinFunctionDoc {
4726            description: "Reverses the given string or array.",
4727            params: &["value"],
4728        },
4729    );
4730    map.insert(
4731        SmolStr::new("sort"),
4732        BuiltinFunctionDoc {
4733            description: "Sorts the elements of the given array.",
4734            params: &["array"],
4735        },
4736    );
4737    map.insert(
4738        SmolStr::new("compact"),
4739        BuiltinFunctionDoc {
4740            description: "Removes None values from the given array.",
4741            params: &["array"],
4742        },
4743    );
4744    map.insert(
4745        SmolStr::new("convert"),
4746        BuiltinFunctionDoc {
4747            description: "Converts the input value to the specified format. Supported formats: base64, html, text, uri, heading (#, ##, etc.), blockquote (>), list item (-), or link (URL).",
4748            params: &["input", "format"],
4749        },
4750    );
4751    map.insert(
4752        SmolStr::new("split"),
4753        BuiltinFunctionDoc {
4754            description: "Splits the given string by the specified separator.",
4755            params: &["string", "separator"],
4756        },
4757    );
4758    map.insert(
4759        SmolStr::new("sqrt"),
4760        BuiltinFunctionDoc {
4761            description: "Returns the square root of the given number.",
4762            params: &["number"],
4763        },
4764    );
4765    map.insert(
4766        SmolStr::new("uniq"),
4767        BuiltinFunctionDoc {
4768            description: "Removes duplicate elements from the given array.",
4769            params: &["array"],
4770        },
4771    );
4772    map.insert(
4773        SmolStr::new(constants::builtins::EQ),
4774        BuiltinFunctionDoc {
4775            description: "Checks if two values are equal.",
4776            params: &["value1", "value2"],
4777        },
4778    );
4779    map.insert(
4780        SmolStr::new(constants::builtins::NE),
4781        BuiltinFunctionDoc {
4782            description: "Checks if two values are not equal.",
4783            params: &["value1", "value2"],
4784        },
4785    );
4786    map.insert(
4787        SmolStr::new(constants::builtins::GT),
4788        BuiltinFunctionDoc {
4789            description: "Checks if the first value is greater than the second value.",
4790            params: &["value1", "value2"],
4791        },
4792    );
4793    map.insert(
4794        SmolStr::new(constants::builtins::GTE),
4795        BuiltinFunctionDoc {
4796            description: "Checks if the first value is greater than or equal to the second value.",
4797            params: &["value1", "value2"],
4798        },
4799    );
4800    map.insert(
4801        SmolStr::new(constants::builtins::LT),
4802        BuiltinFunctionDoc {
4803            description: "Checks if the first value is less than the second value.",
4804            params: &["value1", "value2"],
4805        },
4806    );
4807    map.insert(
4808        SmolStr::new(constants::builtins::LTE),
4809        BuiltinFunctionDoc {
4810            description: "Checks if the first value is less than or equal to the second value.",
4811            params: &["value1", "value2"],
4812        },
4813    );
4814    map.insert(
4815        SmolStr::new(constants::builtins::ADD),
4816        BuiltinFunctionDoc {
4817            description: "Adds two values.",
4818            params: &["value1", "value2"],
4819        },
4820    );
4821    map.insert(
4822        SmolStr::new(constants::builtins::SUB),
4823        BuiltinFunctionDoc {
4824            description: "Subtracts the second value from the first value.",
4825            params: &["value1", "value2"],
4826        },
4827    );
4828    map.insert(
4829        SmolStr::new(constants::builtins::DIV),
4830        BuiltinFunctionDoc {
4831            description: "Divides the first value by the second value.",
4832            params: &["value1", "value2"],
4833        },
4834    );
4835    map.insert(
4836        SmolStr::new(constants::builtins::MUL),
4837        BuiltinFunctionDoc {
4838            description: "Multiplies two values.",
4839            params: &["value1", "value2"],
4840        },
4841    );
4842    map.insert(
4843        SmolStr::new(constants::builtins::MOD),
4844        BuiltinFunctionDoc {
4845            description: "Calculates the remainder of the division of the first value by the second value.",
4846            params: &["value1", "value2"],
4847        },
4848    );
4849    map.insert(
4850        SmolStr::new("and"),
4851        BuiltinFunctionDoc {
4852            description: "Performs a logical AND operation on two boolean values.",
4853            params: &["value1", "value2"],
4854        },
4855    );
4856    map.insert(
4857        SmolStr::new("or"),
4858        BuiltinFunctionDoc {
4859            description: "Performs a logical OR operation on two boolean values.",
4860            params: &["value1", "value2"],
4861        },
4862    );
4863    map.insert(
4864        SmolStr::new(constants::builtins::NOT),
4865        BuiltinFunctionDoc {
4866            description: "Performs a logical NOT operation on a boolean value.",
4867            params: &["value"],
4868        },
4869    );
4870
4871    map.insert(
4872        SmolStr::new("round"),
4873        BuiltinFunctionDoc {
4874            description: "Rounds the given number to the nearest integer.",
4875            params: &["number"],
4876        },
4877    );
4878    map.insert(
4879        SmolStr::new("trunc"),
4880        BuiltinFunctionDoc {
4881            description: "Truncates the given number to an integer by removing the fractional part.",
4882            params: &["number"],
4883        },
4884    );
4885    map.insert(
4886        SmolStr::new("ceil"),
4887        BuiltinFunctionDoc {
4888            description: "Rounds the given number up to the nearest integer.",
4889            params: &["number"],
4890        },
4891    );
4892    map.insert(
4893        SmolStr::new(constants::builtins::FLOOR),
4894        BuiltinFunctionDoc {
4895            description: "Rounds the given number down to the nearest integer.",
4896            params: &["number"],
4897        },
4898    );
4899    map.insert(
4900        SmolStr::new("del"),
4901        BuiltinFunctionDoc {
4902            description: "Deletes the element at the specified index in the array or string.",
4903            params: &["array_or_string", "index"],
4904        },
4905    );
4906    map.insert(
4907        SmolStr::new("abs"),
4908        BuiltinFunctionDoc {
4909            description: "Returns the absolute value of the given number.",
4910            params: &["number"],
4911        },
4912    );
4913    map.insert(
4914        SmolStr::new(constants::builtins::ATTR),
4915        BuiltinFunctionDoc {
4916            description: "Retrieves the value of the specified attribute from a markdown node.",
4917            params: &["markdown", "attribute"],
4918        },
4919    );
4920    map.insert(
4921        SmolStr::new("set_attr"),
4922        BuiltinFunctionDoc {
4923            description: "Sets the value of the specified attribute on a markdown node.",
4924            params: &["markdown", "attribute", "value"],
4925        },
4926    );
4927    map.insert(
4928        SmolStr::new("to_md_name"),
4929        BuiltinFunctionDoc {
4930            description: "Returns the name of the given markdown node.",
4931            params: &["markdown"],
4932        },
4933    );
4934    map.insert(
4935        SmolStr::new("set_list_ordered"),
4936        BuiltinFunctionDoc {
4937            description: "Sets the ordered property of a markdown list node.",
4938            params: &["list", "ordered"],
4939        },
4940    );
4941    map.insert(
4942        SmolStr::new("to_md_text"),
4943        BuiltinFunctionDoc {
4944            description: "Creates a markdown text node with the given value.",
4945            params: &["value"],
4946        },
4947    );
4948    map.insert(
4949        SmolStr::new("to_image"),
4950        BuiltinFunctionDoc {
4951            description: "Creates a markdown image node with the given URL, alt text, and title.",
4952            params: &["url", "alt", "title"],
4953        },
4954    );
4955    map.insert(
4956        SmolStr::new("to_code"),
4957        BuiltinFunctionDoc {
4958            description: "Creates a markdown code block with the given value and language.",
4959            params: &["value", "language"],
4960        },
4961    );
4962    map.insert(
4963        SmolStr::new("to_code_inline"),
4964        BuiltinFunctionDoc {
4965            description: "Creates an inline markdown code node with the given value.",
4966            params: &["value"],
4967        },
4968    );
4969    map.insert(
4970        SmolStr::new("to_h"),
4971        BuiltinFunctionDoc {
4972            description: "Creates a markdown heading node with the given value and depth.",
4973            params: &["value", "depth"],
4974        },
4975    );
4976    map.insert(
4977        SmolStr::new("to_math"),
4978        BuiltinFunctionDoc {
4979            description: "Creates a markdown math block with the given value.",
4980            params: &["value"],
4981        },
4982    );
4983    map.insert(
4984        SmolStr::new("to_math_inline"),
4985        BuiltinFunctionDoc {
4986            description: "Creates an inline markdown math node with the given value.",
4987            params: &["value"],
4988        },
4989    );
4990    map.insert(
4991        SmolStr::new("to_strong"),
4992        BuiltinFunctionDoc {
4993            description: "Creates a markdown strong (bold) node with the given value.",
4994            params: &["value"],
4995        },
4996    );
4997    map.insert(
4998        SmolStr::new("to_em"),
4999        BuiltinFunctionDoc {
5000            description: "Creates a markdown emphasis (italic) node with the given value.",
5001            params: &["value"],
5002        },
5003    );
5004    map.insert(
5005        SmolStr::new("to_hr"),
5006        BuiltinFunctionDoc {
5007            description: "Creates a markdown horizontal rule node.",
5008            params: &[],
5009        },
5010    );
5011    map.insert(
5012        SmolStr::new("to_link"),
5013        BuiltinFunctionDoc {
5014            description: "Creates a markdown link node  with the given  url and title.",
5015            params: &["url", "value", "title"],
5016        },
5017    );
5018    map.insert(
5019        SmolStr::new("to_md_list"),
5020        BuiltinFunctionDoc {
5021            description: "Creates a markdown list node with the given value and indent level.",
5022            params: &["value", "indent"],
5023        },
5024    );
5025    map.insert(
5026        SmolStr::new("to_md_table_row"),
5027        BuiltinFunctionDoc {
5028            description: "Creates a markdown table row node with the given values.",
5029            params: &["cells"],
5030        },
5031    );
5032    map.insert(
5033        SmolStr::new("to_md_table_cell"),
5034        BuiltinFunctionDoc {
5035            description: "Creates a markdown table cell node with the given value at the specified row and column.",
5036            params: &["value", "row", "column"],
5037        },
5038    );
5039
5040    map.insert(
5041        SmolStr::new("get_title"),
5042        BuiltinFunctionDoc {
5043            description: "Returns the title of a markdown node.",
5044            params: &["node"],
5045        },
5046    );
5047    map.insert(
5048        SmolStr::new("get_url"),
5049        BuiltinFunctionDoc {
5050            description: "Returns the url of a markdown node.",
5051            params: &["node"],
5052        },
5053    );
5054    map.insert(
5055        SmolStr::new("set_check"),
5056        BuiltinFunctionDoc {
5057            description: "Creates a markdown list node with the given checked state.",
5058            params: &["list", "checked"],
5059        },
5060    );
5061    map.insert(
5062            SmolStr::new("set_ref"),
5063            BuiltinFunctionDoc {
5064            description: "Sets the reference identifier for markdown nodes that support references (e.g., Definition, LinkRef, ImageRef, Footnote, FootnoteRef).",
5065            params: &["node", "reference_id"],
5066            },
5067        );
5068    map.insert(
5069        SmolStr::new("set_code_block_lang"),
5070        BuiltinFunctionDoc {
5071            description: "Sets the language of a markdown code block node.",
5072            params: &["code_block", "language"],
5073        },
5074    );
5075    map.insert(
5076        SmolStr::new(constants::builtins::DICT),
5077        BuiltinFunctionDoc {
5078            description: "Creates a new, empty dict.",
5079            params: &[],
5080        },
5081    );
5082    map.insert(
5083        SmolStr::new(constants::builtins::GET),
5084        BuiltinFunctionDoc {
5085            description: "Retrieves a value from a dict by its key. Returns None if the key is not found.",
5086            params: &["obj", "key"],
5087        },
5088    );
5089    map.insert(
5090            SmolStr::new("set"),
5091            BuiltinFunctionDoc {
5092                description: "Sets a key-value pair in a dict. If the key exists, its value is updated. Returns the modified map.",
5093                params: &["obj", "key", "value"],
5094            },
5095        );
5096    map.insert(
5097        SmolStr::new("keys"),
5098        BuiltinFunctionDoc {
5099            description: "Returns an array of keys from the dict.",
5100            params: &["dict"],
5101        },
5102    );
5103    map.insert(
5104        SmolStr::new("values"),
5105        BuiltinFunctionDoc {
5106            description: "Returns an array of values from the dict.",
5107            params: &["dict"],
5108        },
5109    );
5110    map.insert(
5111        SmolStr::new("entries"),
5112        BuiltinFunctionDoc {
5113            description: "Returns an array of key-value pairs from the dict as arrays.",
5114            params: &["dict"],
5115        },
5116    );
5117    map.insert(
5118        SmolStr::new(constants::builtins::RANGE),
5119        BuiltinFunctionDoc {
5120            description: "Creates an array from start to end with an optional step.",
5121            params: &["start", "end", "step"],
5122        },
5123    );
5124    map.insert(
5125            SmolStr::new("insert"),
5126            BuiltinFunctionDoc {
5127            description: "Inserts a value into an array or string at the specified index, or into a dict with the specified key.",
5128            params: &["target", "index_or_key", "value"],
5129            },
5130        );
5131
5132    #[cfg(feature = "file-io")]
5133    map.insert(
5134        SmolStr::new("read_file"),
5135        BuiltinFunctionDoc {
5136            description: "Reads the contents of a file at the given path and returns it as a string.",
5137            params: &["path"],
5138        },
5139    );
5140    #[cfg(feature = "file-io")]
5141    map.insert(
5142        SmolStr::new("file_exists"),
5143        BuiltinFunctionDoc {
5144            description: "Checks if a file exists at the given path.",
5145            params: &["path"],
5146        },
5147    );
5148
5149    map.insert(
5150        SmolStr::new("basename"),
5151        BuiltinFunctionDoc {
5152            description: "Returns the final component of a path string (e.g. \"file.txt\" from \"/a/b/file.txt\").",
5153            params: &["path"],
5154        },
5155    );
5156    map.insert(
5157        SmolStr::new("dirname"),
5158        BuiltinFunctionDoc {
5159            description: "Returns the parent directory of a path string (e.g. \"/a/b\" from \"/a/b/file.txt\"). Returns \".\" if the path has no parent.",
5160            params: &["path"],
5161        },
5162    );
5163    map.insert(
5164        SmolStr::new("extname"),
5165        BuiltinFunctionDoc {
5166            description: "Returns the extension of a file path including the leading dot (e.g. \".txt\" from \"file.txt\"). Returns an empty string if there is no extension.",
5167            params: &["path"],
5168        },
5169    );
5170    map.insert(
5171        SmolStr::new("stem"),
5172        BuiltinFunctionDoc {
5173            description: "Returns the file name without the extension (e.g. \"file\" from \"/a/b/file.txt\").",
5174            params: &["path"],
5175        },
5176    );
5177    map.insert(
5178        SmolStr::new("path_join"),
5179        BuiltinFunctionDoc {
5180            description: "Joins a base path with a component path and returns the resulting path string (e.g. path_join(\"/a/b\", \"c.txt\") → \"/a/b/c.txt\").",
5181            params: &["base", "component"],
5182        },
5183    );
5184    map.insert(
5185        SmolStr::new("negate"),
5186        BuiltinFunctionDoc {
5187            description: "Returns the negation of the given number.",
5188            params: &["number"],
5189        },
5190    );
5191    map.insert(
5192        SmolStr::new("intern"),
5193        BuiltinFunctionDoc {
5194            description: "Interns the given string, returning a canonical reference for efficient comparison.",
5195            params: &["string"],
5196        },
5197    );
5198    map.insert(
5199        SmolStr::new("nan"),
5200        BuiltinFunctionDoc {
5201            description: "Returns a Not-a-Number (NaN) value.",
5202            params: &[],
5203        },
5204    );
5205    map.insert(
5206        SmolStr::new("infinite"),
5207        BuiltinFunctionDoc {
5208            description: "Returns an infinite number value.",
5209            params: &[],
5210        },
5211    );
5212    map.insert(
5213        SmolStr::new("coalesce"),
5214        BuiltinFunctionDoc {
5215            description: "Returns the first non-None value from the two provided arguments.",
5216            params: &["value1", "value2"],
5217        },
5218    );
5219    map.insert(
5220        SmolStr::new("input"),
5221        BuiltinFunctionDoc {
5222            description: "Reads a line from standard input and returns it as a string.",
5223            params: &[],
5224        },
5225    );
5226    map.insert(
5227        SmolStr::new("all_symbols"),
5228        BuiltinFunctionDoc {
5229            description: "Returns an array of all interned symbols.",
5230            params: &[],
5231        },
5232    );
5233    map.insert(
5234        SmolStr::new("to_markdown"),
5235        BuiltinFunctionDoc {
5236            description: "Parses a markdown string and returns an array of markdown nodes.",
5237            params: &["markdown_string"],
5238        },
5239    );
5240    map.insert(
5241        SmolStr::new("to_mdx"),
5242        BuiltinFunctionDoc {
5243            description: "Parses an MDX string and returns an array of MDX nodes.",
5244            params: &["mdx_string"],
5245        },
5246    );
5247    map.insert(
5248        SmolStr::new("set_variable"),
5249        BuiltinFunctionDoc {
5250            description: "Sets a symbol or variable in the current environment with the given value.",
5251            params: &["symbol_or_string", "value"],
5252        },
5253    );
5254    map.insert(
5255        SmolStr::new("get_variable"),
5256        BuiltinFunctionDoc {
5257            description: "Retrieves the value of a symbol or variable from the current environment.",
5258            params: &["symbol_or_string"],
5259        },
5260    );
5261    map.insert(
5262        SmolStr::new(constants::builtins::BREAKPOINT),
5263        BuiltinFunctionDoc {
5264            description: "Sets a breakpoint for debugging; execution will pause at this point if a debugger is attached.",
5265            params: &[],
5266            },
5267    );
5268    map.insert(
5269        SmolStr::new("capture"),
5270        BuiltinFunctionDoc {
5271            description: "Captures named groups from the given string based on the specified regular expression pattern and returns them as a dictionary keyed by group names.",
5272            params: &["string", "pattern"],
5273        },
5274    );
5275    map.insert(
5276        SmolStr::new(constants::builtins::SHIFT_LEFT),
5277        BuiltinFunctionDoc {
5278            description: "Performs a left shift operation on the given value: for numbers, this is a bitwise left shift by the specified number of positions; for strings, this removes characters from the start; for Markdown headings, this increases the heading level accordingly.",
5279            params: &["value", "shift_amount"],
5280        },
5281    );
5282    map.insert(
5283        SmolStr::new(constants::builtins::SHIFT_RIGHT),
5284        BuiltinFunctionDoc {
5285            description: "Performs a bitwise right shift on numbers, slices characters from the end of strings, and adjusts Markdown heading levels when applied to headings, using the given shift amount.",
5286            params: &["value", "shift_amount"],
5287        },
5288    );
5289    map.insert(
5290        SmolStr::new("partial"),
5291        BuiltinFunctionDoc {
5292            description: "Creates a new function by partially applying the given arguments to the specified function.",
5293            params: &["function", "arg1", "arg2", "..."],
5294        },
5295    );
5296
5297    map
5298});
5299
5300#[derive(Error, Debug, PartialEq)]
5301pub enum Error {
5302    #[error("")]
5303    InvalidBase64String(#[from] base64::DecodeError),
5304    #[error("")]
5305    NotDefined(FunctionName),
5306    #[error("")]
5307    InvalidDefinition(String),
5308    #[error("")]
5309    InvalidDateTimeFormat(String),
5310    #[error("")]
5311    InvalidTypes(FunctionName, ErrorArgs),
5312    #[error("")]
5313    InvalidNumberOfArguments(FunctionName, u8, u8),
5314    #[error("")]
5315    InvalidRegularExpression(String),
5316    #[error("")]
5317    Runtime(String),
5318    #[error("")]
5319    ZeroDivision,
5320    #[error("")]
5321    UserDefined(String),
5322    #[error("")]
5323    AssignToImmutable(String),
5324    #[error("")]
5325    UndefinedVariable(String),
5326    #[error("")]
5327    InvalidConvert(String),
5328}
5329
5330impl From<env::EnvError> for Error {
5331    fn from(e: env::EnvError) -> Self {
5332        match e {
5333            env::EnvError::InvalidDefinition(name) => Error::InvalidDefinition(name),
5334            env::EnvError::AssignToImmutable(name) => Error::AssignToImmutable(name),
5335            env::EnvError::UndefinedVariable(name) => Error::UndefinedVariable(name),
5336        }
5337    }
5338}
5339
5340impl Error {
5341    #[cold]
5342    pub fn to_runtime_error(
5343        &self,
5344        node: ast::Node,
5345        token_arena: Shared<SharedCell<Arena<Shared<Token>>>>,
5346    ) -> RuntimeError {
5347        match self {
5348            Error::UserDefined(message) => RuntimeError::UserDefined {
5349                message: message.to_owned(),
5350                token: (*get_token(token_arena, node.token_id)).clone(),
5351            },
5352            Error::InvalidBase64String(e) => {
5353                RuntimeError::InvalidBase64String((*get_token(token_arena, node.token_id)).clone(), e.to_string())
5354            }
5355            Error::NotDefined(name) => {
5356                RuntimeError::NotDefined((*get_token(token_arena, node.token_id)).clone(), name.clone())
5357            }
5358            Error::InvalidDefinition(a) => {
5359                RuntimeError::InvalidDefinition((*get_token(token_arena, node.token_id)).clone(), a.clone())
5360            }
5361            Error::InvalidDateTimeFormat(msg) => {
5362                RuntimeError::DateTimeFormatError((*get_token(token_arena, node.token_id)).clone(), msg.clone())
5363            }
5364            Error::InvalidTypes(name, args) => RuntimeError::InvalidTypes {
5365                token: (*get_token(token_arena, node.token_id)).clone(),
5366                name: name.clone(),
5367                args: args.iter().map(|o| o.name().into()).collect::<Vec<_>>(),
5368            },
5369            Error::InvalidNumberOfArguments(name, expected, got) => RuntimeError::InvalidNumberOfArguments {
5370                token: (*get_token(token_arena, node.token_id)).clone(),
5371                name: name.clone(),
5372                expected: *expected,
5373                actual: *got,
5374            },
5375            Error::InvalidRegularExpression(regex) => {
5376                RuntimeError::InvalidRegularExpression((*get_token(token_arena, node.token_id)).clone(), regex.clone())
5377            }
5378            Error::Runtime(msg) => RuntimeError::Runtime((*get_token(token_arena, node.token_id)).clone(), msg.clone()),
5379            Error::ZeroDivision => RuntimeError::ZeroDivision((*get_token(token_arena, node.token_id)).clone()),
5380            Error::AssignToImmutable(name) => {
5381                RuntimeError::AssignToImmutable((*get_token(token_arena, node.token_id)).clone(), name.clone())
5382            }
5383            Error::UndefinedVariable(name) => {
5384                RuntimeError::UndefinedVariable((*get_token(token_arena, node.token_id)).clone(), name.clone())
5385            }
5386            Error::InvalidConvert(format) => {
5387                RuntimeError::InvalidConvert((*get_token(token_arena, node.token_id)).clone(), format.clone())
5388            }
5389        }
5390    }
5391}
5392#[inline(always)]
5393pub fn eval_builtin(
5394    runtime_value: &RuntimeValue,
5395    ident: &Ident,
5396    args: Args,
5397    env: &Shared<SharedCell<Env>>,
5398) -> Result<RuntimeValue, Error> {
5399    get_builtin_functions(ident).map_or_else(
5400        || Err(Error::NotDefined(ident.to_string())),
5401        |f| {
5402            let args_len = args.len() as u8;
5403            if f.num_params.is_valid(args_len) {
5404                (f.func)(ident, runtime_value, args, env)
5405            } else if f.num_params.is_missing_one_params(args_len) {
5406                let mut new_args = Args::with_capacity(args.len() + 1);
5407                new_args.push(runtime_value.clone());
5408                new_args.extend(args);
5409                (f.func)(ident, runtime_value, new_args, env)
5410            } else {
5411                Err(Error::InvalidNumberOfArguments(
5412                    ident.to_string(),
5413                    f.num_params.to_num(),
5414                    args_len,
5415                ))
5416            }
5417        },
5418    )
5419}
5420
5421fn collect_depth_values(args: &[RuntimeValue]) -> Vec<u8> {
5422    args.iter()
5423        .flat_map(|arg| match arg {
5424            RuntimeValue::Number(n) => vec![n.value() as u8],
5425            RuntimeValue::Array(arr) => arr
5426                .iter()
5427                .filter_map(|v| {
5428                    if let RuntimeValue::Number(n) = v {
5429                        Some(n.value() as u8)
5430                    } else {
5431                        None
5432                    }
5433                })
5434                .collect(),
5435            _ => vec![],
5436        })
5437        .collect()
5438}
5439
5440fn collect_runtime_values(args: &[RuntimeValue]) -> Vec<RuntimeValue> {
5441    args.iter()
5442        .flat_map(|arg| match arg {
5443            RuntimeValue::Number(n) => vec![(*n).into()],
5444            RuntimeValue::Array(arr) => arr
5445                .iter()
5446                .filter_map(|v| {
5447                    if let RuntimeValue::Number(n) = v {
5448                        Some((*n).into())
5449                    } else {
5450                        None
5451                    }
5452                })
5453                .collect(),
5454            _ => vec![],
5455        })
5456        .collect()
5457}
5458
5459fn collect_string_values(args: &[RuntimeValue]) -> Vec<String> {
5460    args.iter()
5461        .flat_map(|arg| match arg {
5462            RuntimeValue::String(s) => vec![s.clone()],
5463            RuntimeValue::Array(arr) => arr
5464                .iter()
5465                .filter_map(|v| {
5466                    if let RuntimeValue::String(s) = v {
5467                        Some(s.clone())
5468                    } else {
5469                        None
5470                    }
5471                })
5472                .collect(),
5473            _ => vec![],
5474        })
5475        .collect()
5476}
5477
5478/// Evaluates a selector with runtime arguments against a markdown node.
5479///
5480/// Supports filtered matching for selectors that accept arguments:
5481/// - `Heading`: filters by depth using numeric or range args (e.g. `.h(1..2)`, `.h(1, 2)`)
5482/// - `Code`: filters by language using string args (e.g. `.code("rust")`)
5483/// - `List`: filters by list item index using a numeric arg (e.g. `.[v]` where `v` evaluates to an index)
5484/// - `Table`: filters table cells by positional args where `args[0]` is the row and `args[1]` is the
5485///   column; a `None`/[`RuntimeValue::None`] value in either position acts as a wildcard matching any
5486///   row or column respectively (e.g. `.[v][]` matches row `v` of any column, `.[][v]` matches column
5487///   `v` of any row)
5488/// - All other selectors fall back to [`eval_selector`].
5489pub fn eval_selector_with_args(node: &mq_markdown::Node, selector: &Selector, args: &[RuntimeValue]) -> RuntimeValue {
5490    if args.is_empty() {
5491        return eval_selector(node, selector);
5492    }
5493
5494    let is_match = match selector {
5495        Selector::Heading(_) => {
5496            let depths = collect_depth_values(args);
5497
5498            if depths.is_empty() {
5499                return eval_selector(node, selector);
5500            }
5501
5502            if let mq_markdown::Node::Heading(mq_markdown::Heading { depth, .. }) = node {
5503                depths.contains(depth)
5504            } else {
5505                false
5506            }
5507        }
5508        Selector::Code => {
5509            let langs = collect_string_values(args);
5510
5511            if langs.is_empty() {
5512                return eval_selector(node, selector);
5513            }
5514
5515            if let mq_markdown::Node::Code(mq_markdown::Code { lang, .. }) = node {
5516                let node_lang = lang.as_deref().unwrap_or("");
5517                langs.iter().any(|l| l == node_lang)
5518            } else {
5519                false
5520            }
5521        }
5522        Selector::List(..) => {
5523            let indices = collect_runtime_values(args);
5524
5525            if indices.is_empty() {
5526                return eval_selector(node, selector);
5527            }
5528
5529            if let mq_markdown::Node::List(mq_markdown::List { index: list_index, .. }) = node {
5530                indices.iter().any(|i| match i {
5531                    RuntimeValue::Number(n) => *list_index == n.value() as usize,
5532                    _ => false,
5533                })
5534            } else {
5535                false
5536            }
5537        }
5538        Selector::Table(..) => {
5539            if args.is_empty() {
5540                return eval_selector(node, selector);
5541            }
5542
5543            match node {
5544                mq_markdown::Node::TableCell(mq_markdown::TableCell { column, row, .. }) => {
5545                    let matches_pos = |spec: Option<&RuntimeValue>, actual: usize| -> bool {
5546                        match spec {
5547                            None | Some(RuntimeValue::None) => true,
5548                            Some(RuntimeValue::Number(n)) => actual == n.value() as usize,
5549                            _ => false,
5550                        }
5551                    };
5552                    matches_pos(args.first(), *row) && matches_pos(args.get(1), *column)
5553                }
5554                _ => false,
5555            }
5556        }
5557        _ => return eval_selector(node, selector),
5558    };
5559
5560    if is_match {
5561        RuntimeValue::new_markdown(node.clone())
5562    } else {
5563        RuntimeValue::NONE
5564    }
5565}
5566
5567pub fn eval_selector(node: &mq_markdown::Node, selector: &Selector) -> RuntimeValue {
5568    let is_match = match selector {
5569        Selector::Code => node.is_code(None),
5570        Selector::InlineCode => node.is_inline_code(),
5571        Selector::InlineMath => node.is_inline_math(),
5572        Selector::Strong => node.is_strong(),
5573        Selector::Emphasis => node.is_emphasis(),
5574        Selector::Delete => node.is_delete(),
5575        Selector::Link => node.is_link(),
5576        Selector::LinkRef => node.is_link_ref(),
5577        Selector::WikiLink => node.is_wikilink(),
5578        Selector::Callout => node.is_callout(),
5579        Selector::Embed => node.is_embed(),
5580        Selector::Image => node.is_image(),
5581        Selector::Heading(depth) => node.is_heading(*depth),
5582        Selector::HorizontalRule => node.is_horizontal_rule(),
5583        Selector::Blockquote => node.is_blockquote(),
5584        Selector::Table(row, column) => match node {
5585            mq_markdown::Node::TableCell(mq_markdown::TableCell {
5586                column: column2,
5587                row: row2,
5588                ..
5589            }) => match (row, column) {
5590                (Some(r), Some(c)) => r == row2 && c == column2,
5591                (Some(r), None) => r == row2,
5592                (None, Some(c)) => c == column2,
5593                (None, None) => true,
5594            },
5595            mq_markdown::Node::TableAlign(_) if row.is_none() && column.is_none() => true,
5596            _ => false,
5597        },
5598        Selector::TableAlign => node.is_table_align(),
5599        Selector::Html => node.is_html(),
5600        Selector::Footnote => node.is_footnote(),
5601        Selector::MdxJsxFlowElement => node.is_mdx_jsx_flow_element(),
5602        Selector::List(index, checked) => match node {
5603            mq_markdown::Node::List(mq_markdown::List {
5604                index: list_index,
5605                checked: list_checked,
5606                ..
5607            }) => match index {
5608                Some(i) => i == list_index && checked == list_checked,
5609                None => true,
5610            },
5611            _ => false,
5612        },
5613        Selector::Task => matches!(
5614            node,
5615            mq_markdown::Node::List(mq_markdown::List { checked: Some(_), .. })
5616        ),
5617        Selector::Todo => matches!(
5618            node,
5619            mq_markdown::Node::List(mq_markdown::List {
5620                checked: Some(false),
5621                ..
5622            })
5623        ),
5624        Selector::Done => matches!(
5625            node,
5626            mq_markdown::Node::List(mq_markdown::List {
5627                checked: Some(true),
5628                ..
5629            })
5630        ),
5631        Selector::MdxJsEsm => node.is_mdx_js_esm(),
5632        Selector::Text => node.is_text(),
5633        Selector::Toml => node.is_toml(),
5634        Selector::Yaml => node.is_yaml(),
5635        Selector::Break => node.is_break(),
5636        Selector::MdxTextExpression => node.is_mdx_text_expression(),
5637        Selector::FootnoteRef => node.is_footnote_ref(),
5638        Selector::ImageRef => node.is_image_ref(),
5639        Selector::MdxJsxTextElement => node.is_mdx_jsx_text_element(),
5640        Selector::Math => node.is_math(),
5641        Selector::MdxFlowExpression => node.is_mdx_flow_expression(),
5642        Selector::Definition => node.is_definition(),
5643        Selector::Attr(_) => false, // Attribute selectors don't match nodes directly
5644        Selector::Recursive => return eval_recursive_selector(node),
5645        Selector::Property(_) => false,
5646    };
5647
5648    if is_match {
5649        RuntimeValue::new_markdown(node.clone())
5650    } else {
5651        RuntimeValue::NONE
5652    }
5653}
5654
5655fn extract_recursive_node(node: &mq_markdown::Node) -> Vec<mq_markdown::Node> {
5656    let mut children = vec![];
5657
5658    for child in node.children().into_iter() {
5659        children.extend(extract_recursive_node(&child));
5660        children.push(child);
5661    }
5662
5663    children
5664}
5665
5666/// Evaluates the recursive selector and returns all descendant nodes.
5667fn eval_recursive_selector(node: &mq_markdown::Node) -> RuntimeValue {
5668    RuntimeValue::Array(
5669        extract_recursive_node(node)
5670            .into_iter()
5671            .map(RuntimeValue::new_markdown)
5672            .collect(),
5673    )
5674}
5675
5676fn repeat(value: &mut RuntimeValue, n: usize) -> Result<RuntimeValue, Error> {
5677    match &*value {
5678        RuntimeValue::String(s) => {
5679            let total_size = s.len().saturating_mul(n);
5680            if total_size > MAX_RANGE_SIZE {
5681                return Err(Error::Runtime(format!(
5682                    "string repeat size {} exceeds maximum allowed size of {}",
5683                    total_size, MAX_RANGE_SIZE
5684                )));
5685            }
5686            Ok(s.repeat(n).into())
5687        }
5688        node @ RuntimeValue::Markdown(_, _) => {
5689            if let Some(md) = node.markdown_node() {
5690                let total_size = md.value().len().saturating_mul(n);
5691                if total_size > MAX_RANGE_SIZE {
5692                    return Err(Error::Runtime(format!(
5693                        "markdown repeat size {} exceeds maximum allowed size of {}",
5694                        total_size, MAX_RANGE_SIZE
5695                    )));
5696                }
5697                Ok(node.update_markdown_value(md.value().repeat(n).as_str()))
5698            } else {
5699                Ok(RuntimeValue::NONE)
5700            }
5701        }
5702        RuntimeValue::Array(array) => {
5703            if n == 0 {
5704                return Ok(RuntimeValue::EMPTY_ARRAY);
5705            }
5706
5707            let total_size = array.len().saturating_mul(n);
5708            if total_size > MAX_RANGE_SIZE {
5709                return Err(Error::Runtime(format!(
5710                    "array repeat size {} exceeds maximum allowed size of {}",
5711                    total_size, MAX_RANGE_SIZE
5712                )));
5713            }
5714
5715            let mut repeated_array = Vec::with_capacity(total_size);
5716            for _ in 0..n {
5717                repeated_array.extend_from_slice(array);
5718            }
5719            Ok(RuntimeValue::Array(repeated_array))
5720        }
5721        RuntimeValue::Bytes(b) => {
5722            if n == 0 {
5723                return Ok(RuntimeValue::Bytes(vec![]));
5724            }
5725            let total_size = b.len().saturating_mul(n);
5726            if total_size > MAX_RANGE_SIZE {
5727                return Err(Error::Runtime(format!(
5728                    "bytes repeat size {} exceeds maximum allowed size of {}",
5729                    total_size, MAX_RANGE_SIZE
5730                )));
5731            }
5732            let mut repeated = Vec::with_capacity(total_size);
5733            for _ in 0..n {
5734                repeated.extend_from_slice(b);
5735            }
5736            Ok(RuntimeValue::Bytes(repeated))
5737        }
5738        RuntimeValue::None => Ok(RuntimeValue::NONE),
5739        _ => Err(Error::InvalidTypes(
5740            constants::builtins::MUL.to_string(),
5741            vec![std::mem::take(value), RuntimeValue::Number(n.into())],
5742        )),
5743    }
5744}
5745
5746#[cfg(test)]
5747mod tests {
5748    use std::collections::BTreeMap;
5749
5750    use mq_markdown::Node;
5751    use rstest::rstest;
5752
5753    use super::*;
5754
5755    #[rstest]
5756    #[case("type", vec![RuntimeValue::String("test".into())], Ok(RuntimeValue::String("string".into())))]
5757    #[case("len", vec![RuntimeValue::String("test".into())], Ok(RuntimeValue::Number(4.into())))]
5758    #[case("abs", vec![RuntimeValue::Number((-10).into())], Ok(RuntimeValue::Number(10.into())))]
5759    #[case("ceil", vec![RuntimeValue::Number(3.2.into())], Ok(RuntimeValue::Number(4.0.into())))]
5760    #[case("floor", vec![RuntimeValue::Number(3.8.into())], Ok(RuntimeValue::Number(3.0.into())))]
5761    #[case("round", vec![RuntimeValue::Number(3.5.into())], Ok(RuntimeValue::Number(4.0.into())))]
5762    #[case("add", vec![RuntimeValue::Number(3.0.into()), RuntimeValue::Number(2.0.into())], Ok(RuntimeValue::Number(5.0.into())))]
5763    #[case("sub", vec![RuntimeValue::Number(5.0.into()), RuntimeValue::Number(3.0.into())], Ok(RuntimeValue::Number(2.0.into())))]
5764    #[case("mul", vec![RuntimeValue::Number(4.0.into()), RuntimeValue::Number(2.0.into())], Ok(RuntimeValue::Number(8.0.into())))]
5765    #[case("div", vec![RuntimeValue::Number(8.0.into()), RuntimeValue::Number(2.0.into())], Ok(RuntimeValue::Number(4.0.into())))]
5766    #[case("eq", vec![RuntimeValue::String("test".into()), RuntimeValue::String("test".into())], Ok(RuntimeValue::Boolean(true)))]
5767    #[case("ne", vec![RuntimeValue::String("test".into()), RuntimeValue::String("different".into())], Ok(RuntimeValue::Boolean(true)))]
5768    fn test_eval_builtin(#[case] func_name: &str, #[case] args: Args, #[case] expected: Result<RuntimeValue, Error>) {
5769        let ident = Ident::new(func_name);
5770        assert_eq!(
5771            eval_builtin(
5772                &RuntimeValue::None,
5773                &ident,
5774                args,
5775                &Shared::new(SharedCell::new(Env::default()))
5776            ),
5777            expected
5778        );
5779    }
5780
5781    #[rstest]
5782    #[case("div", vec![RuntimeValue::Number(1.0.into()), RuntimeValue::Number(0.0.into())], Error::ZeroDivision)]
5783    #[case("unknown_func", vec![RuntimeValue::Number(1.0.into())], Error::NotDefined("unknown_func".to_string()))]
5784    #[case("add", vec![], Error::InvalidNumberOfArguments("add".to_string(), 2, 0))]
5785    #[case("add", vec![RuntimeValue::Boolean(true), RuntimeValue::Number(1.0.into())],
5786        Error::InvalidTypes("add".to_string(), vec![RuntimeValue::Boolean(true), RuntimeValue::Number(1.0.into())]))]
5787    fn test_eval_builtin_errors(#[case] func_name: &str, #[case] args: Args, #[case] expected_error: Error) {
5788        let ident = Ident::new(func_name);
5789        let result = eval_builtin(
5790            &RuntimeValue::None,
5791            &ident,
5792            args,
5793            &Shared::new(SharedCell::new(Env::default())),
5794        );
5795        assert!(result.is_err());
5796        assert_eq!(result.unwrap_err(), expected_error);
5797    }
5798
5799    #[test]
5800    fn test_gmtime_epoch() {
5801        // Unix epoch (0) → 1970-01-01T00:00:00 UTC (Thursday)
5802        // format: [year, mon(0-11), mday, hour, min, sec, wday(0=Sun), yday(0-365)]
5803        let ident = Ident::new("gmtime");
5804        let args = vec![RuntimeValue::Number(0.into())];
5805        let result = eval_builtin(
5806            &RuntimeValue::None,
5807            &ident,
5808            args,
5809            &Shared::new(SharedCell::new(Env::default())),
5810        )
5811        .unwrap();
5812        assert_eq!(
5813            result,
5814            RuntimeValue::Array(vec![
5815                RuntimeValue::Number(1970.into()), // year
5816                RuntimeValue::Number(0.into()),    // mon (Jan=0)
5817                RuntimeValue::Number(1.into()),    // mday
5818                RuntimeValue::Number(0.into()),    // hour
5819                RuntimeValue::Number(0.into()),    // min
5820                RuntimeValue::Number(0.into()),    // sec
5821                RuntimeValue::Number(4.into()),    // wday (Thu=4)
5822                RuntimeValue::Number(0.into()),    // yday
5823            ])
5824        );
5825    }
5826
5827    #[test]
5828    fn test_gmtime_known_date() {
5829        // 2024-01-01T00:00:00 UTC = 1704067200 seconds
5830        let ident = Ident::new("gmtime");
5831        let args = vec![RuntimeValue::Number(1704067200_i64.into())];
5832        let result = eval_builtin(
5833            &RuntimeValue::None,
5834            &ident,
5835            args,
5836            &Shared::new(SharedCell::new(Env::default())),
5837        )
5838        .unwrap();
5839        assert_eq!(
5840            result,
5841            RuntimeValue::Array(vec![
5842                RuntimeValue::Number(2024.into()), // year
5843                RuntimeValue::Number(0.into()),    // mon (Jan=0)
5844                RuntimeValue::Number(1.into()),    // mday
5845                RuntimeValue::Number(0.into()),    // hour
5846                RuntimeValue::Number(0.into()),    // min
5847                RuntimeValue::Number(0.into()),    // sec
5848                RuntimeValue::Number(1.into()),    // wday (Mon=1)
5849                RuntimeValue::Number(0.into()),    // yday
5850            ])
5851        );
5852    }
5853
5854    #[rstest]
5855    #[case(0, 0)]
5856    #[case(1704067200_i64, 1704067200_i64)]
5857    #[case(1718454645_i64, 1718454645_i64)]
5858    fn test_mktime_roundtrip(#[case] secs: i64, #[case] expected: i64) {
5859        let env = Shared::new(SharedCell::new(Env::default()));
5860        let gmtime_ident = Ident::new("gmtime");
5861        let mktime_ident = Ident::new("mktime");
5862
5863        let arr = eval_builtin(
5864            &RuntimeValue::None,
5865            &gmtime_ident,
5866            vec![RuntimeValue::Number(secs.into())],
5867            &env,
5868        )
5869        .unwrap();
5870        let result = eval_builtin(&RuntimeValue::None, &mktime_ident, vec![arr], &env).unwrap();
5871        assert_eq!(result, RuntimeValue::Number(expected.into()));
5872    }
5873
5874    #[rstest]
5875    #[case(1704067200_i64, "%Y-%m-%d", "2024-01-01")]
5876    #[case(0_i64, "%Y-%m-%dT%H:%M:%S", "1970-01-01T00:00:00")]
5877    #[case(1704067200_i64, "%Y", "2024")]
5878    fn test_strftime(#[case] ts: i64, #[case] fmt: &str, #[case] expected: &str) {
5879        let ident = Ident::new("strftime");
5880        let args = vec![RuntimeValue::Number(ts.into()), RuntimeValue::String(fmt.into())];
5881        let result = eval_builtin(
5882            &RuntimeValue::None,
5883            &ident,
5884            args,
5885            &Shared::new(SharedCell::new(Env::default())),
5886        )
5887        .unwrap();
5888        assert_eq!(result, RuntimeValue::String(expected.into()));
5889    }
5890
5891    fn gmtime_array(secs: i64) -> RuntimeValue {
5892        let env = Shared::new(SharedCell::new(Env::default()));
5893        eval_builtin(
5894            &RuntimeValue::None,
5895            &Ident::new("gmtime"),
5896            vec![RuntimeValue::Number(secs.into())],
5897            &env,
5898        )
5899        .unwrap()
5900    }
5901
5902    // date_add: simple durations
5903    #[rstest]
5904    #[case(1704067200_i64, 60, "seconds", 1704067260_i64)]
5905    #[case(1704067200_i64, 5, "minutes", 1704067500_i64)]
5906    #[case(1704067200_i64, 2, "hours", 1704074400_i64)]
5907    #[case(1704067200_i64, 1, "days", 1704153600_i64)]
5908    #[case(1704067200_i64, -1,  "days",    1703980800_i64)]
5909    #[case(1704067200_i64, 1, "weeks", 1704672000_i64)]
5910    fn test_date_add_duration(#[case] base: i64, #[case] n: i64, #[case] unit: &str, #[case] expected_secs: i64) {
5911        let env = Shared::new(SharedCell::new(Env::default()));
5912        let arr = gmtime_array(base);
5913        let result = eval_builtin(
5914            &RuntimeValue::None,
5915            &Ident::new("date_add"),
5916            vec![arr, RuntimeValue::Number(n.into()), RuntimeValue::String(unit.into())],
5917            &env,
5918        )
5919        .unwrap();
5920        // convert result array back to timestamp via mktime and compare
5921        let ts = eval_builtin(&RuntimeValue::None, &Ident::new("mktime"), vec![result], &env).unwrap();
5922        assert_eq!(ts, RuntimeValue::Number(expected_secs.into()));
5923    }
5924
5925    // date_add: calendar-aware month/year arithmetic
5926    #[test]
5927    fn test_date_add_months_end_of_month() {
5928        // 2024-01-31 + 1 month = 2024-02-29 (leap year)
5929        let env = Shared::new(SharedCell::new(Env::default()));
5930        let arr = gmtime_array(1706659200); // 2024-01-31T00:00:00Z
5931        let result = eval_builtin(
5932            &RuntimeValue::None,
5933            &Ident::new("date_add"),
5934            vec![
5935                arr,
5936                RuntimeValue::Number(1.into()),
5937                RuntimeValue::String("months".into()),
5938            ],
5939            &env,
5940        )
5941        .unwrap();
5942        // 2024-02-29T00:00:00Z = 1709164800
5943        let ts = eval_builtin(&RuntimeValue::None, &Ident::new("mktime"), vec![result], &env).unwrap();
5944        assert_eq!(ts, RuntimeValue::Number(1709164800_i64.into()));
5945    }
5946
5947    #[test]
5948    fn test_date_add_years() {
5949        // 2024-02-29 + 1 year = 2025-02-28 (non-leap year clamps)
5950        let env = Shared::new(SharedCell::new(Env::default()));
5951        let arr = gmtime_array(1709164800); // 2024-02-29T00:00:00Z
5952        let result = eval_builtin(
5953            &RuntimeValue::None,
5954            &Ident::new("date_add"),
5955            vec![
5956                arr,
5957                RuntimeValue::Number(1.into()),
5958                RuntimeValue::String("years".into()),
5959            ],
5960            &env,
5961        )
5962        .unwrap();
5963        // 2025-02-28T00:00:00Z = 1740700800
5964        let ts = eval_builtin(&RuntimeValue::None, &Ident::new("mktime"), vec![result], &env).unwrap();
5965        assert_eq!(ts, RuntimeValue::Number(1740700800_i64.into()));
5966    }
5967
5968    #[test]
5969    fn test_date_add_invalid_unit() {
5970        let env = Shared::new(SharedCell::new(Env::default()));
5971        let arr = gmtime_array(0);
5972        let result = eval_builtin(
5973            &RuntimeValue::None,
5974            &Ident::new("date_add"),
5975            vec![
5976                arr,
5977                RuntimeValue::Number(1.into()),
5978                RuntimeValue::String("centuries".into()),
5979            ],
5980            &env,
5981        );
5982        assert!(matches!(result, Err(Error::Runtime(_))));
5983    }
5984
5985    // date_diff: difference in various units
5986    #[rstest]
5987    #[case(1704067200_i64, 1704153600_i64, "seconds", 86400_i64)]
5988    #[case(1704067200_i64, 1704153600_i64, "minutes", 1440_i64)]
5989    #[case(1704067200_i64, 1704153600_i64, "hours", 24_i64)]
5990    #[case(1704067200_i64, 1704153600_i64, "days", 1_i64)]
5991    #[case(1704067200_i64, 1704672000_i64, "weeks", 1_i64)]
5992    #[case(1704153600_i64, 1704067200_i64, "seconds", -86400_i64)]
5993    fn test_date_diff(#[case] base1: i64, #[case] base2: i64, #[case] unit: &str, #[case] expected: i64) {
5994        let env = Shared::new(SharedCell::new(Env::default()));
5995        let arr1 = gmtime_array(base1);
5996        let arr2 = gmtime_array(base2);
5997        let result = eval_builtin(
5998            &RuntimeValue::None,
5999            &Ident::new("date_diff"),
6000            vec![arr1, arr2, RuntimeValue::String(unit.into())],
6001            &env,
6002        )
6003        .unwrap();
6004        assert_eq!(result, RuntimeValue::Number(expected.into()));
6005    }
6006
6007    #[test]
6008    fn test_date_diff_invalid_unit() {
6009        let env = Shared::new(SharedCell::new(Env::default()));
6010        let arr = gmtime_array(0);
6011        let result = eval_builtin(
6012            &RuntimeValue::None,
6013            &Ident::new("date_diff"),
6014            vec![arr.clone(), arr, RuntimeValue::String("months".into())],
6015            &env,
6016        );
6017        assert!(matches!(result, Err(Error::Runtime(_))));
6018    }
6019
6020    #[test]
6021    fn test_gmtime_invalid_type() {
6022        let ident = Ident::new("gmtime");
6023        let args = vec![RuntimeValue::String("not a number".into())];
6024        let result = eval_builtin(
6025            &RuntimeValue::None,
6026            &ident,
6027            args,
6028            &Shared::new(SharedCell::new(Env::default())),
6029        );
6030        assert!(matches!(result, Err(Error::InvalidTypes(_, _))));
6031    }
6032
6033    #[test]
6034    fn test_mktime_invalid_input() {
6035        let ident = Ident::new("mktime");
6036        let args = vec![RuntimeValue::String("not an array".into())];
6037        let result = eval_builtin(
6038            &RuntimeValue::None,
6039            &ident,
6040            args,
6041            &Shared::new(SharedCell::new(Env::default())),
6042        );
6043        assert!(matches!(result, Err(Error::InvalidTypes(_, _))));
6044    }
6045
6046    #[test]
6047    fn test_date_add_malformed_array_error_prefix() {
6048        let env = Shared::new(SharedCell::new(Env::default()));
6049        let bad_arr = RuntimeValue::Array(vec![RuntimeValue::String("x".into()); 8]);
6050        let result = eval_builtin(
6051            &RuntimeValue::None,
6052            &Ident::new("date_add"),
6053            vec![
6054                bad_arr,
6055                RuntimeValue::Number(1.into()),
6056                RuntimeValue::String("days".into()),
6057            ],
6058            &env,
6059        );
6060        match result {
6061            Err(Error::Runtime(msg)) => assert!(msg.starts_with("date_add:"), "expected date_add prefix, got: {msg}"),
6062            other => panic!("expected Runtime error, got: {other:?}"),
6063        }
6064    }
6065
6066    #[test]
6067    fn test_date_diff_malformed_array_error_prefix() {
6068        let env = Shared::new(SharedCell::new(Env::default()));
6069        let bad_arr = RuntimeValue::Array(vec![RuntimeValue::String("x".into()); 8]);
6070        let result = eval_builtin(
6071            &RuntimeValue::None,
6072            &Ident::new("date_diff"),
6073            vec![bad_arr.clone(), bad_arr, RuntimeValue::String("days".into())],
6074            &env,
6075        );
6076        match result {
6077            Err(Error::Runtime(msg)) => assert!(msg.starts_with("date_diff:"), "expected date_diff prefix, got: {msg}"),
6078            other => panic!("expected Runtime error, got: {other:?}"),
6079        }
6080    }
6081
6082    #[test]
6083    fn test_implicit_first_arg() {
6084        let ident = Ident::new("starts_with");
6085        let first_arg = RuntimeValue::String("hello world".into());
6086        let args = vec![RuntimeValue::String("hello".into())];
6087
6088        let result = eval_builtin(&first_arg, &ident, args, &Shared::new(SharedCell::new(Env::default())));
6089        assert_eq!(result, Ok(RuntimeValue::Boolean(true)));
6090    }
6091
6092    #[rstest]
6093    #[case::code(
6094        Node::Code(mq_markdown::Code { value: "test".into(), lang: Some("rust".into()), fence: true, meta: None, position: None }),
6095        Selector::Code,
6096        true
6097    )]
6098    #[case::inline_code(
6099        Node::CodeInline(mq_markdown::CodeInline { value: "test".into(), position: None }),
6100        Selector::InlineCode,
6101        true
6102    )]
6103    #[case::inline_math(
6104        Node::MathInline(mq_markdown::MathInline { value: "test".into(), position: None }),
6105        Selector::InlineMath,
6106        true
6107    )]
6108    #[case::strong(
6109        Node::Strong(mq_markdown::Strong { values: vec!["test".to_string().into()], position: None }),
6110        Selector::Strong,
6111        true
6112    )]
6113    #[case::emphasis(
6114        Node::Emphasis(mq_markdown::Emphasis{ values: vec!["test".to_string().into()], position: None }),
6115        Selector::Emphasis,
6116        true
6117    )]
6118    #[case::delete(
6119        Node::Delete(mq_markdown::Delete{ values: vec!["test".to_string().into()], position: None }),
6120        Selector::Delete,
6121        true
6122    )]
6123    #[case::link(
6124        Node::Link(mq_markdown::Link { url: mq_markdown::Url::new("https://example.com".into()), values: Vec::new(), title: None, position: None }),
6125        Selector::Link,
6126        true
6127    )]
6128    #[case::heading_matching_depth(
6129        Node::Heading(mq_markdown::Heading { depth: 2, values: vec!["test".to_string().into()], position: None }),
6130        Selector::Heading(Some(2)),
6131        true
6132    )]
6133    #[case::heading_wrong_depth(
6134        Node::Heading(mq_markdown::Heading { depth: 2, values: vec!["test".to_string().into()], position: None }),
6135        Selector::Heading(Some(3)),
6136        false
6137    )]
6138    #[case::table_cell_with_matching_row_col(
6139        Node::TableCell(mq_markdown::TableCell { row: 1, column: 2, values: vec!["test".to_string().into()], position: None }),
6140        Selector::Table(Some(1), Some(2)),
6141        true
6142    )]
6143    #[case::table_cell_with_wrong_row(
6144        Node::TableCell(mq_markdown::TableCell { row: 1, column: 2, values: vec!["test".to_string().into()], position: None }),
6145        Selector::Table(Some(2), Some(2)),
6146        false
6147    )]
6148    #[case::table_cell_with_only_row(
6149        Node::TableCell(mq_markdown::TableCell { row: 1, column: 2, values: vec!["test".to_string().into()], position: None }),
6150        Selector::Table(Some(1), None),
6151        true
6152    )]
6153    #[case::table_header_with_no_row_col(
6154        Node::TableAlign(mq_markdown::TableAlign { align: vec![], position: None }),
6155        Selector::Table(None, None),
6156        true
6157    )]
6158    #[case::table_header_with_only_row(
6159        Node::TableAlign(mq_markdown::TableAlign { align: vec![], position: None }),
6160        Selector::Table(Some(2), None),
6161        false
6162    )]
6163    #[case::table_header_with_only_col(
6164        Node::TableAlign(mq_markdown::TableAlign { align: vec![], position: None }),
6165        Selector::Table(None, Some(3)),
6166        false
6167    )]
6168    #[case::table_header_with_row_col(
6169        Node::TableAlign(mq_markdown::TableAlign { align: vec![], position: None }),
6170        Selector::Table(Some(1), Some(1)),
6171        false
6172    )]
6173    #[case::list_with_matching_index_checked(
6174        Node::List(mq_markdown::List { values: vec!["test".to_string().into()], ordered: false, index: 1, level: 1, checked: Some(true), position: None }),
6175        Selector::List(Some(1), Some(true)),
6176        true
6177    )]
6178    #[case::list_with_wrong_index(
6179        Node::List(mq_markdown::List { values: vec!["test".to_string().into()], ordered: false, index: 1, level: 1, checked: Some(true), position: None }),
6180        Selector::List(Some(2), Some(true)),
6181        false
6182    )]
6183    #[case::list_without_index(
6184        Node::List(mq_markdown::List { values: vec!["test".to_string().into()], ordered: false, index: 1, level: 1, checked: Some(true), position: None }),
6185        Selector::List(None, None),
6186        true
6187    )]
6188    #[case::task_list(
6189        Node::List(mq_markdown::List { values: vec!["test".to_string().into()], ordered: false, index: 1, level: 1, checked: Some(true), position: None }),
6190        Selector::Task,
6191        true
6192    )]
6193    #[case::task_list(
6194        Node::List(mq_markdown::List { values: vec!["test".to_string().into()], ordered: false, index: 1, level: 1, checked: Some(false), position: None }),
6195        Selector::Task,
6196        true
6197    )]
6198    #[case::task_list(
6199        Node::List(mq_markdown::List { values: vec!["test".to_string().into()], ordered: false, index: 1, level: 1, checked: None, position: None }),
6200        Selector::Task,
6201        false
6202    )]
6203    #[case::todo_list(
6204        Node::List(mq_markdown::List { values: vec!["test".to_string().into()], ordered: false, index: 1, level: 1, checked: Some(false), position: None }),
6205        Selector::Todo,
6206        true
6207    )]
6208    #[case::todo_list(
6209        Node::List(mq_markdown::List { values: vec!["test".to_string().into()], ordered: false, index: 1, level: 1, checked: Some(true), position: None }),
6210        Selector::Todo,
6211        false
6212    )]
6213    #[case::todo_list(
6214        Node::List(mq_markdown::List { values: vec!["test".to_string().into()], ordered: false, index: 1, level: 1, checked: None, position: None }),
6215        Selector::Todo,
6216        false
6217    )]
6218    #[case::done_list(
6219        Node::List(mq_markdown::List { values: vec!["test".to_string().into()], ordered: false, index: 1, level: 1, checked: Some(true), position: None }),
6220        Selector::Done,
6221        true
6222    )]
6223    #[case::done_list(
6224        Node::List(mq_markdown::List { values: vec!["test".to_string().into()], ordered: false, index: 1, level: 1, checked: Some(false), position: None }),
6225        Selector::Done,
6226        false
6227    )]
6228    #[case::done_list(
6229        Node::List(mq_markdown::List { values: vec!["test".to_string().into()], ordered: false, index: 1, level: 1, checked: None, position: None }),
6230        Selector::Done,
6231        false
6232    )]
6233    #[case::text(
6234        Node::Text(mq_markdown::Text { value: "test".into(), position: None }),
6235        Selector::Text,
6236        true
6237    )]
6238    #[case::html(
6239        Node::Html(mq_markdown::Html { value: "<div>test</div>".into(), position: None }),
6240        Selector::Html,
6241        true
6242    )]
6243    #[case::yaml(
6244        Node::Yaml(mq_markdown::Yaml { value: "test".into(), position: None }),
6245        Selector::Yaml,
6246        true
6247    )]
6248    #[case::toml(
6249        Node::Toml(mq_markdown::Toml { value: "test".into(), position: None }),
6250        Selector::Toml,
6251        true
6252    )]
6253    #[case::break_(
6254        Node::Break(mq_markdown::Break{position: None}),
6255        Selector::Break,
6256        true
6257    )]
6258    #[case::image(
6259        Node::Image(mq_markdown::Image { alt: "".to_string(), url: "".to_string(), title: None, position: None }),
6260        Selector::Image,
6261        true
6262    )]
6263    #[case::image_ref(
6264        Node::ImageRef(mq_markdown::ImageRef{ alt: "".to_string(), ident: "".to_string(), label: None, position: None }),
6265        Selector::ImageRef,
6266        true
6267    )]
6268    #[case::footnote(
6269        Node::Footnote(mq_markdown::Footnote{ident: "".to_string(), values: vec!["test".to_string().into()], position: None}),
6270        Selector::Footnote,
6271        true
6272    )]
6273    #[case::footnote_ref(
6274        Node::FootnoteRef(mq_markdown::FootnoteRef{ident: "".to_string(), label: None, position: None}),
6275        Selector::FootnoteRef,
6276        true
6277    )]
6278    #[case::math(
6279        Node::Math(mq_markdown::Math { value: "E=mc^2".into(), position: None }),
6280        Selector::Math,
6281        true
6282    )]
6283    #[case::horizontal_rule(
6284        Node::HorizontalRule(mq_markdown::HorizontalRule{ position: None }),
6285        Selector::HorizontalRule,
6286        true
6287    )]
6288    #[case::blockquote(
6289        Node::Blockquote(mq_markdown::Blockquote{ values: vec!["test".to_string().into()], position: None }),
6290        Selector::Blockquote,
6291        true
6292    )]
6293    #[case::definition(
6294        Node::Definition(mq_markdown::Definition { ident: "id".to_string(), url: mq_markdown::Url::new("url".into()), label: None, title: None, position: None }),
6295        Selector::Definition,
6296        true
6297    )]
6298    #[case::mdx_jsx_flow_element(
6299        Node::MdxJsxFlowElement(mq_markdown::MdxJsxFlowElement { name: Some("div".to_string()), attributes: Vec::new(), children: Vec::new(), position: None }),
6300        Selector::MdxJsxFlowElement,
6301        true
6302    )]
6303    #[case::mdx_flow_expression(
6304        Node::MdxFlowExpression(mq_markdown::MdxFlowExpression{ value: "value".into(), position: None }),
6305        Selector::MdxFlowExpression,
6306        true
6307    )]
6308    #[case::mdx_text_expression(
6309        Node::MdxTextExpression(mq_markdown::MdxTextExpression{ value: "value".into(), position: None }),
6310        Selector::MdxTextExpression,
6311        true
6312    )]
6313    #[case::mdx_js_esm(
6314        Node::MdxJsEsm(mq_markdown::MdxJsEsm{ value: "value".into(), position: None }),
6315        Selector::MdxJsEsm,
6316        true
6317    )]
6318    fn test_eval_selector(#[case] node: Node, #[case] selector: Selector, #[case] expected: bool) {
6319        assert_eq!(!eval_selector(&node, &selector).is_none(), expected);
6320    }
6321
6322    #[test]
6323    fn test_eval_recursive_selector_with_children() {
6324        let node = Node::Heading(mq_markdown::Heading {
6325            values: vec![
6326                Node::Text(mq_markdown::Text {
6327                    value: "hello".into(),
6328                    position: None,
6329                }),
6330                Node::Link(mq_markdown::Link {
6331                    url: mq_markdown::Url::new("url".into()),
6332                    title: None,
6333                    values: Vec::new(),
6334                    position: None,
6335                }),
6336            ],
6337            position: None,
6338            depth: 1,
6339        });
6340        let result = eval_selector(&node, &Selector::Recursive);
6341        assert_eq!(
6342            result,
6343            RuntimeValue::Array(vec![
6344                RuntimeValue::Markdown(
6345                    Box::new(Node::Text(mq_markdown::Text {
6346                        value: "hello".into(),
6347                        position: None,
6348                    })),
6349                    None
6350                ),
6351                RuntimeValue::Markdown(
6352                    Box::new(Node::Link(mq_markdown::Link {
6353                        url: mq_markdown::Url::new("url".into()),
6354                        title: None,
6355                        values: Vec::new(),
6356                        position: None,
6357                    })),
6358                    None
6359                ),
6360            ])
6361        );
6362    }
6363
6364    #[test]
6365    fn test_eval_recursive_selector_leaf_node() {
6366        let node = Node::Text(mq_markdown::Text {
6367            value: "leaf".into(),
6368            position: None,
6369        });
6370        let result = eval_selector(&node, &Selector::Recursive);
6371        assert_eq!(result, RuntimeValue::Array(vec![]));
6372    }
6373
6374    #[test]
6375    fn test_eval_recursive_selector_nested() {
6376        let inner_text = Node::Text(mq_markdown::Text {
6377            value: "nested".into(),
6378            position: None,
6379        });
6380        let heading = Node::Heading(mq_markdown::Heading {
6381            values: vec![inner_text.clone()],
6382            position: None,
6383            depth: 2,
6384        });
6385        let node = Node::Blockquote(mq_markdown::Blockquote {
6386            values: vec![heading.clone()],
6387            position: None,
6388        });
6389        let result = eval_selector(&node, &Selector::Recursive);
6390        assert_eq!(
6391            result,
6392            RuntimeValue::Array(vec![
6393                RuntimeValue::new_markdown(inner_text),
6394                RuntimeValue::new_markdown(heading),
6395            ])
6396        );
6397    }
6398
6399    #[rstest]
6400    #[case(ParamNum::None, 0, true)]
6401    #[case(ParamNum::None, 1, false)]
6402    #[case(ParamNum::Fixed(2), 2, true)]
6403    #[case(ParamNum::Fixed(2), 1, false)]
6404    #[case(ParamNum::Fixed(2), 3, false)]
6405    #[case(ParamNum::Range(1, 3), 1, true)]
6406    #[case(ParamNum::Range(1, 3), 2, true)]
6407    #[case(ParamNum::Range(1, 3), 3, true)]
6408    #[case(ParamNum::Range(1, 3), 0, false)]
6409    #[case(ParamNum::Range(1, 3), 4, false)]
6410    fn test_param_num_is_valid(#[case] param_num: ParamNum, #[case] num_args: u8, #[case] expected: bool) {
6411        assert_eq!(param_num.is_valid(num_args), expected);
6412    }
6413
6414    #[rstest]
6415    #[case(ParamNum::None, 0)]
6416    #[case(ParamNum::Fixed(2), 2)]
6417    #[case(ParamNum::Range(1, 3), 1)]
6418    fn test_param_num_to_num(#[case] param_num: ParamNum, #[case] expected: u8) {
6419        assert_eq!(param_num.to_num(), expected);
6420    }
6421
6422    #[rstest]
6423    #[case(ParamNum::None, 0, false)]
6424    #[case(ParamNum::Fixed(2), 1, true)]
6425    #[case(ParamNum::Fixed(2), 0, false)]
6426    #[case(ParamNum::Range(1, 3), 0, true)]
6427    #[case(ParamNum::Range(1, 3), 1, false)]
6428    fn test_param_num_is_missing_one_params(#[case] param_num: ParamNum, #[case] num_args: u8, #[case] expected: bool) {
6429        assert_eq!(param_num.is_missing_one_params(num_args), expected);
6430    }
6431
6432    // Tests for Dict functions
6433    #[rstest]
6434    #[case(
6435        BTreeMap::from([("a".into(), RuntimeValue::Number(1.0.into())), ("b".into(), RuntimeValue::Number(2.0.into()))]),
6436        BTreeMap::from([("c".into(), RuntimeValue::Number(3.0.into()))]),
6437        BTreeMap::from([("a".into(), RuntimeValue::Number(1.0.into())), ("b".into(), RuntimeValue::Number(2.0.into())), ("c".into(), RuntimeValue::Number(3.0.into()))]),
6438    )]
6439    #[case(
6440        BTreeMap::from([("a".into(), RuntimeValue::Number(1.0.into()))]),
6441        BTreeMap::from([("a".into(), RuntimeValue::Number(99.0.into())), ("b".into(), RuntimeValue::Number(2.0.into()))]),
6442        BTreeMap::from([("a".into(), RuntimeValue::Number(99.0.into())), ("b".into(), RuntimeValue::Number(2.0.into()))]),
6443    )]
6444    #[case(
6445        BTreeMap::new(),
6446        BTreeMap::from([("x".into(), RuntimeValue::String("hello".into()))]),
6447        BTreeMap::from([("x".into(), RuntimeValue::String("hello".into()))]),
6448    )]
6449    #[case(
6450        BTreeMap::from([("x".into(), RuntimeValue::String("hello".into()))]),
6451        BTreeMap::new(),
6452        BTreeMap::from([("x".into(), RuntimeValue::String("hello".into()))]),
6453    )]
6454    fn test_eval_builtin_add_dict(
6455        #[case] d1: BTreeMap<Ident, RuntimeValue>,
6456        #[case] d2: BTreeMap<Ident, RuntimeValue>,
6457        #[case] expected: BTreeMap<Ident, RuntimeValue>,
6458    ) {
6459        let ident = Ident::new("add");
6460        let result = eval_builtin(
6461            &RuntimeValue::None,
6462            &ident,
6463            vec![RuntimeValue::Dict(d1), RuntimeValue::Dict(d2)],
6464            &Shared::new(SharedCell::new(Env::default())),
6465        );
6466        assert_eq!(result, Ok(RuntimeValue::Dict(expected)));
6467    }
6468
6469    #[test]
6470    fn test_eval_builtin_new_dict() {
6471        let ident = Ident::new("dict");
6472        let result = eval_builtin(
6473            &RuntimeValue::None,
6474            &ident,
6475            vec![],
6476            &Shared::new(SharedCell::new(Env::default())),
6477        );
6478        assert!(result.is_ok());
6479        let map_val = result.unwrap();
6480        match map_val {
6481            RuntimeValue::Dict(map) => {
6482                assert_eq!(map.len(), 0);
6483            }
6484            _ => panic!("Expected Dict, got {:?}", map_val),
6485        }
6486
6487        let result = eval_builtin(
6488            &RuntimeValue::None,
6489            &ident,
6490            vec![RuntimeValue::Array(vec![
6491                RuntimeValue::String("key".into()),
6492                RuntimeValue::String("value".into()),
6493            ])],
6494            &Shared::new(SharedCell::new(Env::default())),
6495        );
6496        assert_eq!(
6497            result,
6498            Ok(RuntimeValue::Dict(BTreeMap::from([(
6499                "key".into(),
6500                RuntimeValue::String("value".into())
6501            )])))
6502        );
6503    }
6504
6505    #[test]
6506    fn test_eval_builtin_set_dict() {
6507        let ident_set = Ident::new("set");
6508        let initial_map = RuntimeValue::new_dict();
6509
6510        let args1 = vec![
6511            initial_map.clone(),
6512            RuntimeValue::String("name".into()),
6513            RuntimeValue::String("Jules".into()),
6514        ];
6515        let result1 = eval_builtin(
6516            &RuntimeValue::None,
6517            &ident_set,
6518            args1,
6519            &Shared::new(SharedCell::new(Env::default())),
6520        );
6521        assert!(result1.is_ok());
6522        let map_val1 = result1.unwrap();
6523        match &map_val1 {
6524            RuntimeValue::Dict(map) => {
6525                assert_eq!(map.len(), 1);
6526                assert_eq!(
6527                    map.get(&Ident::new("name")),
6528                    Some(&RuntimeValue::String("Jules".into()))
6529                );
6530            }
6531            _ => panic!("Expected Dict, got {:?}", map_val1),
6532        }
6533
6534        let args2 = vec![
6535            map_val1.clone(),
6536            RuntimeValue::String("age".into()),
6537            RuntimeValue::Number(30.into()),
6538        ];
6539        let result2 = eval_builtin(
6540            &RuntimeValue::None,
6541            &ident_set,
6542            args2,
6543            &Shared::new(SharedCell::new(Env::default())),
6544        );
6545        assert!(result2.is_ok());
6546        let map_val2 = result2.unwrap();
6547        match &map_val2 {
6548            RuntimeValue::Dict(map) => {
6549                assert_eq!(map.len(), 2);
6550                assert_eq!(
6551                    map.get(&Ident::new("name")),
6552                    Some(&RuntimeValue::String("Jules".into()))
6553                );
6554                assert_eq!(map.get(&Ident::new("age")), Some(&RuntimeValue::Number(30.into())));
6555            }
6556            _ => panic!("Expected Dict, got {:?}", map_val2),
6557        }
6558
6559        let args3 = vec![
6560            map_val2.clone(),
6561            RuntimeValue::String("name".into()),
6562            RuntimeValue::String("Vincent".into()),
6563        ];
6564        let result3 = eval_builtin(
6565            &RuntimeValue::None,
6566            &ident_set,
6567            args3,
6568            &Shared::new(SharedCell::new(Env::default())),
6569        );
6570        assert!(result3.is_ok());
6571        let map_val3 = result3.unwrap();
6572        match &map_val3 {
6573            RuntimeValue::Dict(map) => {
6574                assert_eq!(map.len(), 2);
6575                assert_eq!(
6576                    map.get(&Ident::new("name")),
6577                    Some(&RuntimeValue::String("Vincent".into()))
6578                );
6579                assert_eq!(map.get(&Ident::new("age")), Some(&RuntimeValue::Number(30.into())));
6580            }
6581            _ => panic!("Expected Dict, got {:?}", map_val3),
6582        }
6583
6584        let mut nested_map_data = BTreeMap::default();
6585        nested_map_data.insert(Ident::new("level"), RuntimeValue::Number(2.into()));
6586        let nested_map: RuntimeValue = nested_map_data.into();
6587        let args4 = vec![
6588            map_val3.clone(),
6589            RuntimeValue::String("nested".into()),
6590            nested_map.clone(),
6591        ];
6592        let result4 = eval_builtin(
6593            &RuntimeValue::None,
6594            &ident_set,
6595            args4,
6596            &Shared::new(SharedCell::new(Env::default())),
6597        );
6598        assert!(result4.is_ok());
6599        match result4.unwrap() {
6600            RuntimeValue::Dict(map) => {
6601                assert_eq!(map.len(), 3);
6602                assert_eq!(map.get(&Ident::new("nested")), Some(&nested_map));
6603            }
6604            _ => panic!("Expected Dict"),
6605        }
6606
6607        let args_err1 = vec![
6608            RuntimeValue::String("not_a_map".into()),
6609            RuntimeValue::String("key".into()),
6610            RuntimeValue::String("value".into()),
6611        ];
6612        let result_err1 = eval_builtin(
6613            &RuntimeValue::None,
6614            &ident_set,
6615            args_err1,
6616            &Shared::new(SharedCell::new(Env::default())),
6617        );
6618        assert_eq!(
6619            result_err1,
6620            Err(Error::InvalidTypes(
6621                "set".to_string(),
6622                vec![
6623                    RuntimeValue::String("not_a_map".into()),
6624                    RuntimeValue::String("key".into()),
6625                    RuntimeValue::String("value".into())
6626                ]
6627            ))
6628        );
6629
6630        let args_err2 = vec![
6631            initial_map.clone(),
6632            RuntimeValue::Number(123.into()),
6633            RuntimeValue::String("value".into()),
6634        ];
6635        let result_err2 = eval_builtin(
6636            &RuntimeValue::None,
6637            &ident_set,
6638            args_err2,
6639            &Shared::new(SharedCell::new(Env::default())),
6640        );
6641        assert_eq!(
6642            result_err2,
6643            Err(Error::InvalidTypes(
6644                "set".to_string(),
6645                vec![
6646                    initial_map.clone(),
6647                    RuntimeValue::Number(123.into()),
6648                    RuntimeValue::String("value".into())
6649                ]
6650            ))
6651        );
6652    }
6653
6654    #[test]
6655    fn test_eval_builtin_get_map() {
6656        let ident_get = Ident::new("get");
6657        let mut map_data = BTreeMap::default();
6658        map_data.insert("name".into(), RuntimeValue::String("Jules".into()));
6659        map_data.insert("age".into(), RuntimeValue::Number(30.into()));
6660        let map_val: RuntimeValue = map_data.into();
6661
6662        let args1 = vec![map_val.clone(), RuntimeValue::String("name".into())];
6663        let result1 = eval_builtin(
6664            &RuntimeValue::None,
6665            &ident_get,
6666            args1,
6667            &Shared::new(SharedCell::new(Env::default())),
6668        );
6669        assert_eq!(result1, Ok(RuntimeValue::String("Jules".into())));
6670
6671        let args2 = vec![map_val.clone(), RuntimeValue::String("location".into())];
6672        let result2 = eval_builtin(
6673            &RuntimeValue::None,
6674            &ident_get,
6675            args2,
6676            &Shared::new(SharedCell::new(Env::default())),
6677        );
6678        assert_eq!(result2, Ok(RuntimeValue::None));
6679
6680        let args_err1 = vec![
6681            RuntimeValue::String("not_a_map".into()),
6682            RuntimeValue::String("key".into()),
6683        ];
6684        let result_err1 = eval_builtin(
6685            &RuntimeValue::None,
6686            &ident_get,
6687            args_err1,
6688            &Shared::new(SharedCell::new(Env::default())),
6689        );
6690        assert_eq!(
6691            result_err1,
6692            Err(Error::InvalidTypes(
6693                "get".to_string(),
6694                vec![
6695                    RuntimeValue::String("not_a_map".into()),
6696                    RuntimeValue::String("key".into())
6697                ]
6698            ))
6699        );
6700
6701        let args_err2 = vec![map_val.clone(), RuntimeValue::Number(123.into())];
6702        let result_err2 = eval_builtin(
6703            &RuntimeValue::None,
6704            &ident_get,
6705            args_err2,
6706            &Shared::new(SharedCell::new(Env::default())),
6707        );
6708        assert_eq!(
6709            result_err2,
6710            Err(Error::InvalidTypes(
6711                "get".to_string(),
6712                vec![map_val.clone(), RuntimeValue::Number(123.into())]
6713            ))
6714        );
6715    }
6716
6717    #[test]
6718    fn test_eval_builtin_keys_dict() {
6719        let ident_keys = Ident::new("keys");
6720        let empty_map = RuntimeValue::new_dict();
6721        let args1 = vec![empty_map.clone()];
6722        let result1 = eval_builtin(
6723            &RuntimeValue::None,
6724            &ident_keys,
6725            args1,
6726            &Shared::new(SharedCell::new(Env::default())),
6727        );
6728        assert_eq!(result1, Ok(RuntimeValue::Array(vec![])));
6729
6730        let mut map_data = BTreeMap::default();
6731        map_data.insert("name".into(), RuntimeValue::String("Jules".into()));
6732        map_data.insert("age".into(), RuntimeValue::Number(30.into()));
6733        let map_val: RuntimeValue = map_data.into();
6734        let args2 = vec![map_val.clone()];
6735        let result2 = eval_builtin(
6736            &RuntimeValue::None,
6737            &ident_keys,
6738            args2,
6739            &Shared::new(SharedCell::new(Env::default())),
6740        );
6741        assert!(result2.is_ok());
6742        match result2.unwrap() {
6743            RuntimeValue::Array(keys_array) => {
6744                assert_eq!(keys_array.len(), 2);
6745                let keys_str: Vec<String> = keys_array
6746                    .into_iter()
6747                    .map(|k| match k {
6748                        RuntimeValue::String(s) => s,
6749                        _ => panic!("Expected string key"),
6750                    })
6751                    .collect();
6752                assert_eq!(keys_str, vec!["name".to_string(), "age".to_string()]);
6753            }
6754            _ => panic!("Expected Array of keys"),
6755        }
6756
6757        let args_err1 = vec![RuntimeValue::String("not_a_map".into())];
6758        let result_err1 = eval_builtin(
6759            &RuntimeValue::None,
6760            &ident_keys,
6761            args_err1,
6762            &Shared::new(SharedCell::new(Env::default())),
6763        );
6764        assert_eq!(
6765            result_err1,
6766            Err(Error::InvalidTypes(
6767                "keys".to_string(),
6768                vec![RuntimeValue::String("not_a_map".into())]
6769            ))
6770        );
6771
6772        let args_err2 = vec![map_val.clone(), RuntimeValue::String("extra".into())];
6773        let result_err2 = eval_builtin(
6774            &RuntimeValue::None,
6775            &ident_keys,
6776            args_err2,
6777            &Shared::new(SharedCell::new(Env::default())),
6778        );
6779        assert_eq!(
6780            result_err2,
6781            Err(Error::InvalidNumberOfArguments("keys".to_string(), 1, 2))
6782        );
6783    }
6784
6785    #[test]
6786    fn test_eval_builtin_values_dict() {
6787        let ident_values = Ident::new("values");
6788        let empty_map = RuntimeValue::new_dict();
6789        let args1 = vec![empty_map.clone()];
6790        let result1 = eval_builtin(
6791            &RuntimeValue::None,
6792            &ident_values,
6793            args1,
6794            &Shared::new(SharedCell::new(Env::default())),
6795        );
6796        assert_eq!(result1, Ok(RuntimeValue::Array(vec![])));
6797
6798        let mut map_data = BTreeMap::default();
6799        map_data.insert("name".into(), RuntimeValue::String("Jules".into()));
6800        map_data.insert("age".into(), RuntimeValue::Number(30.into()));
6801        let map_val: RuntimeValue = map_data.into();
6802        let args2 = vec![map_val.clone()];
6803        let result2 = eval_builtin(
6804            &RuntimeValue::None,
6805            &ident_values,
6806            args2,
6807            &Shared::new(SharedCell::new(Env::default())),
6808        );
6809        assert!(result2.is_ok());
6810        match result2.unwrap() {
6811            RuntimeValue::Array(values_array) => {
6812                assert_eq!(values_array.len(), 2);
6813                assert!(values_array.contains(&RuntimeValue::String("Jules".into())));
6814                assert!(values_array.contains(&RuntimeValue::Number(30.into())));
6815            }
6816            _ => panic!("Expected Array of values"),
6817        }
6818
6819        let args_err1 = vec![RuntimeValue::String("not_a_map".into())];
6820        let result_err1 = eval_builtin(
6821            &RuntimeValue::None,
6822            &ident_values,
6823            args_err1,
6824            &Shared::new(SharedCell::new(Env::default())),
6825        );
6826        assert_eq!(
6827            result_err1,
6828            Err(Error::InvalidTypes(
6829                "values".to_string(),
6830                vec![RuntimeValue::String("not_a_map".into())]
6831            ))
6832        );
6833
6834        let args_err2 = vec![map_val.clone(), RuntimeValue::String("extra".into())];
6835        let result_err2 = eval_builtin(
6836            &RuntimeValue::None,
6837            &ident_values,
6838            args_err2,
6839            &Shared::new(SharedCell::new(Env::default())),
6840        );
6841        assert_eq!(
6842            result_err2,
6843            Err(Error::InvalidNumberOfArguments("values".to_string(), 1, 2))
6844        );
6845    }
6846
6847    #[rstest]
6848    #[case::excessively_large_range(0, 2_000_000, 1)]
6849    #[case::negative_step_large_range(10_000_000, 0, -1)]
6850    #[case::just_over_limit(0, 1_000_000, 1)]
6851    fn test_range_size_limit_exceeds(#[case] start: isize, #[case] end: isize, #[case] step: isize) {
6852        let result = generate_numeric_range(start, end, step);
6853        assert!(result.is_err());
6854        if let Err(Error::Runtime(msg)) = result {
6855            assert!(msg.contains("exceeds maximum allowed size"));
6856        } else {
6857            panic!("Expected Runtime error");
6858        }
6859    }
6860
6861    #[rstest]
6862    #[case::reasonable_range(0, 100, 1, 101)]
6863    #[case::exactly_at_limit(0, 999_999, 1, 1_000_000)]
6864    fn test_range_size_limit_success(
6865        #[case] start: isize,
6866        #[case] end: isize,
6867        #[case] step: isize,
6868        #[case] expected_len: usize,
6869    ) {
6870        let result = generate_numeric_range(start, end, step);
6871        assert!(result.is_ok());
6872        if let Ok(vec) = result {
6873            assert_eq!(vec.len(), expected_len);
6874        }
6875    }
6876
6877    #[rstest]
6878    #[case::unicode_max_range('\u{0000}', '\u{10FFFF}', Some(1))]
6879    fn test_char_range_size_limit_exceeds(#[case] start: char, #[case] end: char, #[case] step: Option<i32>) {
6880        let result = generate_char_range(start, end, step);
6881        assert!(result.is_err());
6882        if let Err(Error::Runtime(msg)) = result {
6883            assert!(msg.contains("exceeds maximum allowed size"));
6884        } else {
6885            panic!("Expected Runtime error");
6886        }
6887    }
6888
6889    #[rstest]
6890    #[case::reasonable_char_range('a', 'z', None, 26)]
6891    fn test_char_range_size_limit_success(
6892        #[case] start: char,
6893        #[case] end: char,
6894        #[case] step: Option<i32>,
6895        #[case] expected_len: usize,
6896    ) {
6897        let result = generate_char_range(start, end, step);
6898        assert!(result.is_ok());
6899        if let Ok(vec) = result {
6900            assert_eq!(vec.len(), expected_len);
6901        }
6902    }
6903
6904    #[rstest]
6905    #[case::excessively_large_array_repeat(
6906        vec![RuntimeValue::Number(1.into()), RuntimeValue::Number(2.into())],
6907        600_000,
6908        "array repeat size"
6909    )]
6910    #[case::just_over_limit(
6911        vec![RuntimeValue::Number(1.into())],
6912        1_000_001,
6913        "exceeds maximum allowed size"
6914    )]
6915    fn test_repeat_array_size_limit_exceeds(
6916        #[case] array: Vec<RuntimeValue>,
6917        #[case] n: usize,
6918        #[case] expected_msg: &str,
6919    ) {
6920        let mut value = RuntimeValue::Array(array);
6921        let result = repeat(&mut value, n);
6922        assert!(result.is_err());
6923        if let Err(Error::Runtime(msg)) = result {
6924            assert!(msg.contains(expected_msg));
6925        } else {
6926            panic!("Expected Runtime error for array repeat");
6927        }
6928    }
6929
6930    #[rstest]
6931    #[case::reasonable_array_repeat(
6932        vec![RuntimeValue::Number(1.into()), RuntimeValue::Number(2.into())],
6933        10,
6934        20
6935    )]
6936    #[case::exactly_at_limit(
6937        vec![RuntimeValue::Number(1.into())],
6938        1_000_000,
6939        1_000_000
6940    )]
6941    fn test_repeat_array_size_limit_success(
6942        #[case] array: Vec<RuntimeValue>,
6943        #[case] n: usize,
6944        #[case] expected_len: usize,
6945    ) {
6946        let mut value = RuntimeValue::Array(array);
6947        let result = repeat(&mut value, n);
6948        assert!(result.is_ok());
6949        if let Ok(RuntimeValue::Array(vec)) = result {
6950            assert_eq!(vec.len(), expected_len);
6951        } else {
6952            panic!("Expected successful array repeat");
6953        }
6954    }
6955
6956    #[rstest]
6957    #[case::excessively_large_string_repeat("test", 300_000, "string repeat size")]
6958    fn test_repeat_string_size_limit_exceeds(#[case] string: &str, #[case] n: usize, #[case] expected_msg: &str) {
6959        let mut value = RuntimeValue::String(string.to_string());
6960        let result = repeat(&mut value, n);
6961        assert!(result.is_err());
6962        if let Err(Error::Runtime(msg)) = result {
6963            assert!(msg.contains(expected_msg));
6964        } else {
6965            panic!("Expected Runtime error for string repeat");
6966        }
6967    }
6968
6969    #[rstest]
6970    #[case::reasonable_string_repeat("test", 10, 40)]
6971    fn test_repeat_string_size_limit_success(#[case] string: &str, #[case] n: usize, #[case] expected_len: usize) {
6972        let mut value = RuntimeValue::String(string.to_string());
6973        let result = repeat(&mut value, n);
6974        assert!(result.is_ok());
6975        if let Ok(RuntimeValue::String(s)) = result {
6976            assert_eq!(s.len(), expected_len);
6977        } else {
6978            panic!("Expected successful string repeat");
6979        }
6980    }
6981
6982    #[rstest]
6983    #[case::simple_no_header(
6984        "a,b,c\n1,2,3\n4,5,6",
6985        Ok(RuntimeValue::Array(vec![
6986            RuntimeValue::Array(vec![
6987                RuntimeValue::String("a".to_string()),
6988                RuntimeValue::String("b".to_string()),
6989                RuntimeValue::String("c".to_string()),
6990            ]),
6991            RuntimeValue::Array(vec![
6992                RuntimeValue::String("1".to_string()),
6993                RuntimeValue::String("2".to_string()),
6994                RuntimeValue::String("3".to_string()),
6995            ]),
6996            RuntimeValue::Array(vec![
6997                RuntimeValue::String("4".to_string()),
6998                RuntimeValue::String("5".to_string()),
6999                RuntimeValue::String("6".to_string()),
7000            ]),
7001        ]))
7002    )]
7003    #[case::single_row_no_header(
7004        "x,y",
7005        Ok(RuntimeValue::Array(vec![
7006            RuntimeValue::Array(vec![
7007                RuntimeValue::String("x".to_string()),
7008                RuntimeValue::String("y".to_string()),
7009            ]),
7010        ]))
7011    )]
7012    #[case::empty_no_header(
7013        "",
7014        Ok(RuntimeValue::Array(vec![]))
7015    )]
7016    fn test_csv_parse_no_header(#[case] csv: &str, #[case] expected: Result<RuntimeValue, Error>) {
7017        let ident = Ident::new("_csv_parse");
7018        let result = eval_builtin(
7019            &RuntimeValue::None,
7020            &ident,
7021            vec![RuntimeValue::String(csv.to_string())],
7022            &Shared::new(SharedCell::new(Env::default())),
7023        );
7024        assert_eq!(result, expected);
7025    }
7026
7027    #[rstest]
7028    #[case::simple_with_header(
7029        "name,age\nAlice,30\nBob,25",
7030        {
7031            let mut alice = BTreeMap::new();
7032            alice.insert(Ident::new("name"), RuntimeValue::String("Alice".to_string()));
7033            alice.insert(Ident::new("age"), RuntimeValue::String("30".to_string()));
7034            let mut bob = BTreeMap::new();
7035            bob.insert(Ident::new("name"), RuntimeValue::String("Bob".to_string()));
7036            bob.insert(Ident::new("age"), RuntimeValue::String("25".to_string()));
7037            Ok(RuntimeValue::Array(vec![
7038                RuntimeValue::Dict(alice),
7039                RuntimeValue::Dict(bob),
7040            ]))
7041        }
7042    )]
7043    #[case::single_row_with_header(
7044        "id,value\n1,hello",
7045        {
7046            let mut row = BTreeMap::new();
7047            row.insert(Ident::new("id"), RuntimeValue::String("1".to_string()));
7048            row.insert(Ident::new("value"), RuntimeValue::String("hello".to_string()));
7049            Ok(RuntimeValue::Array(vec![RuntimeValue::Dict(row)]))
7050        }
7051    )]
7052    #[case::quoted_fields_with_header(
7053        "name,note\n\"Doe, Jane\",\"says \"\"hi\"\"\"",
7054        {
7055            let mut row = BTreeMap::new();
7056            row.insert(Ident::new("name"), RuntimeValue::String("Doe, Jane".to_string()));
7057            row.insert(Ident::new("note"), RuntimeValue::String("says \"hi\"".to_string()));
7058            Ok(RuntimeValue::Array(vec![RuntimeValue::Dict(row)]))
7059        }
7060    )]
7061    fn test_csv_parse_with_header(#[case] csv: &str, #[case] expected: Result<RuntimeValue, Error>) {
7062        let ident = Ident::new("_csv_parse");
7063        let result = eval_builtin(
7064            &RuntimeValue::None,
7065            &ident,
7066            vec![
7067                RuntimeValue::String(csv.to_string()),
7068                RuntimeValue::String(",".to_string()),
7069                RuntimeValue::Boolean(true),
7070            ],
7071            &Shared::new(SharedCell::new(Env::default())),
7072        );
7073        assert_eq!(result, expected);
7074    }
7075
7076    #[rstest]
7077    #[case::tsv_no_header(
7078        "a\tb\tc\n1\t2\t3",
7079        "\t",
7080        false,
7081        Ok(RuntimeValue::Array(vec![
7082            RuntimeValue::Array(vec![
7083                RuntimeValue::String("a".to_string()),
7084                RuntimeValue::String("b".to_string()),
7085                RuntimeValue::String("c".to_string()),
7086            ]),
7087            RuntimeValue::Array(vec![
7088                RuntimeValue::String("1".to_string()),
7089                RuntimeValue::String("2".to_string()),
7090                RuntimeValue::String("3".to_string()),
7091            ]),
7092        ]))
7093    )]
7094    #[case::tsv_with_header(
7095        "name\tage\nAlice\t30",
7096        "\t",
7097        true,
7098        {
7099            let mut row = BTreeMap::new();
7100            row.insert(Ident::new("name"), RuntimeValue::String("Alice".to_string()));
7101            row.insert(Ident::new("age"), RuntimeValue::String("30".to_string()));
7102            Ok(RuntimeValue::Array(vec![RuntimeValue::Dict(row)]))
7103        }
7104    )]
7105    fn test_csv_parse_custom_delimiter(
7106        #[case] csv: &str,
7107        #[case] delimiter: &str,
7108        #[case] has_header: bool,
7109        #[case] expected: Result<RuntimeValue, Error>,
7110    ) {
7111        let ident = Ident::new("_csv_parse");
7112        let result = eval_builtin(
7113            &RuntimeValue::None,
7114            &ident,
7115            vec![
7116                RuntimeValue::String(csv.to_string()),
7117                RuntimeValue::String(delimiter.to_string()),
7118                RuntimeValue::Boolean(has_header),
7119            ],
7120            &Shared::new(SharedCell::new(Env::default())),
7121        );
7122        assert_eq!(result, expected);
7123    }
7124
7125    #[rstest]
7126    #[case::invalid_type_number(RuntimeValue::Number(42.into()))]
7127    #[case::invalid_type_bool(RuntimeValue::Boolean(false))]
7128    fn test_csv_parse_invalid_arg_type(#[case] invalid_arg: RuntimeValue) {
7129        let ident = Ident::new("_csv_parse");
7130        let result = eval_builtin(
7131            &RuntimeValue::None,
7132            &ident,
7133            vec![invalid_arg],
7134            &Shared::new(SharedCell::new(Env::default())),
7135        );
7136        assert!(result.is_err());
7137    }
7138
7139    #[rstest]
7140    #[case::simple_object(
7141        r#"{"key": "value"}"#,
7142        {
7143            let mut map = BTreeMap::new();
7144            map.insert(Ident::new("key"), RuntimeValue::String("value".to_string()));
7145            Ok(RuntimeValue::Dict(map))
7146        }
7147    )]
7148    #[case::array(
7149        r#"[1, 2, 3]"#,
7150        Ok(RuntimeValue::Array(vec![
7151            RuntimeValue::Number(1.into()),
7152            RuntimeValue::Number(2.into()),
7153            RuntimeValue::Number(3.into()),
7154        ]))
7155    )]
7156    #[case::nested(
7157        r#"{"a": [true, null], "b": {"c": 1.2}}"#,
7158        {
7159            let mut map = BTreeMap::new();
7160            map.insert(Ident::new("a"), RuntimeValue::Array(vec![
7161                RuntimeValue::Boolean(true),
7162                RuntimeValue::NONE,
7163            ]));
7164            let mut inner = BTreeMap::new();
7165            inner.insert(Ident::new("c"), RuntimeValue::Number(1.2.into()));
7166            map.insert(Ident::new("b"), RuntimeValue::Dict(inner));
7167            Ok(RuntimeValue::Dict(map))
7168        }
7169    )]
7170    #[case::string(r#""hello""#, Ok(RuntimeValue::String("hello".to_string())))]
7171    #[case::number(r#"42"#, Ok(RuntimeValue::Number(42.into())))]
7172    #[case::boolean(r#"false"#, Ok(RuntimeValue::Boolean(false)))]
7173    #[case::null(r#"null"#, Ok(RuntimeValue::NONE))]
7174    fn test_json_parse(#[case] json: &str, #[case] expected: Result<RuntimeValue, Error>) {
7175        let ident = Ident::new("_json_parse");
7176        let result = eval_builtin(
7177            &RuntimeValue::None,
7178            &ident,
7179            vec![RuntimeValue::String(json.to_string())],
7180            &Shared::new(SharedCell::new(Env::default())),
7181        );
7182        assert_eq!(result, expected);
7183    }
7184
7185    #[rstest]
7186    #[case::invalid_json(r#"{"key": "value""#)]
7187    #[case::invalid_type(RuntimeValue::Number(1.into()))]
7188    fn test_json_parse_error(#[case] input: impl Into<RuntimeValue>) {
7189        let ident = Ident::new("_json_parse");
7190        let arg: RuntimeValue = match input.into() {
7191            RuntimeValue::Number(n) => RuntimeValue::Number(n),
7192            s => RuntimeValue::String(s.to_string()),
7193        };
7194        let result = eval_builtin(
7195            &RuntimeValue::None,
7196            &ident,
7197            vec![arg],
7198            &Shared::new(SharedCell::new(Env::default())),
7199        );
7200        assert!(result.is_err());
7201    }
7202
7203    #[rstest]
7204    #[case::mapping(
7205        "key: value",
7206        {
7207            let mut map = BTreeMap::new();
7208            map.insert(Ident::new("key"), RuntimeValue::String("value".to_string()));
7209            Ok(RuntimeValue::Dict(map))
7210        }
7211    )]
7212    #[case::sequence(
7213        "- 1\n- 2\n- 3",
7214        Ok(RuntimeValue::Array(vec![
7215            RuntimeValue::Number(1.into()),
7216            RuntimeValue::Number(2.into()),
7217            RuntimeValue::Number(3.into()),
7218        ]))
7219    )]
7220    #[case::nested(
7221        "a:\n  b: 42",
7222        {
7223            let mut inner = BTreeMap::new();
7224            inner.insert(Ident::new("b"), RuntimeValue::Number(42.into()));
7225            let mut map = BTreeMap::new();
7226            map.insert(Ident::new("a"), RuntimeValue::Dict(inner));
7227            Ok(RuntimeValue::Dict(map))
7228        }
7229    )]
7230    #[case::boolean(
7231        "flag: true",
7232        {
7233            let mut map = BTreeMap::new();
7234            map.insert(Ident::new("flag"), RuntimeValue::Boolean(true));
7235            Ok(RuntimeValue::Dict(map))
7236        }
7237    )]
7238    #[case::null(
7239        "value: null",
7240        {
7241            let mut map = BTreeMap::new();
7242            map.insert(Ident::new("value"), RuntimeValue::NONE);
7243            Ok(RuntimeValue::Dict(map))
7244        }
7245    )]
7246    #[case::float(
7247        "ratio: 1.5",
7248        {
7249            let mut map = BTreeMap::new();
7250            map.insert(Ident::new("ratio"), RuntimeValue::Number(1.5.into()));
7251            Ok(RuntimeValue::Dict(map))
7252        }
7253    )]
7254    fn test_yaml_parse(#[case] yaml: &str, #[case] expected: Result<RuntimeValue, Error>) {
7255        let ident = Ident::new("_yaml_parse");
7256        let result = eval_builtin(
7257            &RuntimeValue::None,
7258            &ident,
7259            vec![RuntimeValue::String(yaml.to_string())],
7260            &Shared::new(SharedCell::new(Env::default())),
7261        );
7262        assert_eq!(result, expected);
7263    }
7264
7265    #[rstest]
7266    #[case::invalid_type(RuntimeValue::Number(1.into()))]
7267    fn test_yaml_parse_error(#[case] input: impl Into<RuntimeValue>) {
7268        let ident = Ident::new("_yaml_parse");
7269        let arg: RuntimeValue = match input.into() {
7270            RuntimeValue::Number(n) => RuntimeValue::Number(n),
7271            s => RuntimeValue::String(s.to_string()),
7272        };
7273        let result = eval_builtin(
7274            &RuntimeValue::None,
7275            &ident,
7276            vec![arg],
7277            &Shared::new(SharedCell::new(Env::default())),
7278        );
7279        assert!(result.is_err());
7280    }
7281
7282    #[rstest]
7283    #[case::simple_kv(
7284        "a: 1\nb: 2",
7285        {
7286            let mut map = BTreeMap::new();
7287            map.insert(Ident::new("a"), RuntimeValue::Number(1.into()));
7288            map.insert(Ident::new("b"), RuntimeValue::Number(2.into()));
7289            Ok(RuntimeValue::Dict(map))
7290        }
7291    )]
7292    #[case::nested_indent(
7293        "parent:\n  child: value",
7294        {
7295            let mut child_map = BTreeMap::new();
7296            child_map.insert(Ident::new("child"), RuntimeValue::String("value".to_string()));
7297            let mut parent_map = BTreeMap::new();
7298            parent_map.insert(Ident::new("parent"), RuntimeValue::Dict(child_map));
7299            Ok(RuntimeValue::Dict(parent_map))
7300        }
7301    )]
7302    #[case::tabular_data(
7303        "hikes[2]{id,name}:\n  1,Blue Lake\n  2,Ridge Trail",
7304        {
7305            let mut row1 = BTreeMap::new();
7306            row1.insert(Ident::new("id"), RuntimeValue::Number(1.into()));
7307            row1.insert(Ident::new("name"), RuntimeValue::String("Blue Lake".to_string()));
7308            let mut row2 = BTreeMap::new();
7309            row2.insert(Ident::new("id"), RuntimeValue::Number(2.into()));
7310            row2.insert(Ident::new("name"), RuntimeValue::String("Ridge Trail".to_string()));
7311            let mut map = BTreeMap::new();
7312            map.insert(Ident::new("hikes"), RuntimeValue::Array(vec![RuntimeValue::Dict(row1), RuntimeValue::Dict(row2)]));
7313            Ok(RuntimeValue::Dict(map))
7314        }
7315    )]
7316    #[case::inline_array(
7317        "items[3]: 1, 2, 3",
7318        {
7319            let mut map = BTreeMap::new();
7320            map.insert(Ident::new("items"), RuntimeValue::Array(vec![
7321                RuntimeValue::Number(1.into()),
7322                RuntimeValue::Number(2.into()),
7323                RuntimeValue::Number(3.into()),
7324            ]));
7325            Ok(RuntimeValue::Dict(map))
7326        }
7327    )]
7328    #[case::expanded_array(
7329        "items[2]:\n  - 1\n  - 2",
7330        {
7331            let mut map = BTreeMap::new();
7332            map.insert(Ident::new("items"), RuntimeValue::Array(vec![
7333                RuntimeValue::Number(1.into()),
7334                RuntimeValue::Number(2.into()),
7335            ]));
7336            Ok(RuntimeValue::Dict(map))
7337        }
7338    )]
7339    #[case::primitives(
7340        "s: \"string\"\nb: true\nn: null\nf: false",
7341        {
7342            let mut map = BTreeMap::new();
7343            map.insert(Ident::new("s"), RuntimeValue::String("string".to_string()));
7344            map.insert(Ident::new("b"), RuntimeValue::TRUE);
7345            map.insert(Ident::new("n"), RuntimeValue::NONE);
7346            map.insert(Ident::new("f"), RuntimeValue::FALSE);
7347            Ok(RuntimeValue::Dict(map))
7348        }
7349    )]
7350    fn test_toon_parse(#[case] toon: &str, #[case] expected: Result<RuntimeValue, Error>) {
7351        let ident = Ident::new("_toon_parse");
7352        let result = eval_builtin(
7353            &RuntimeValue::None,
7354            &ident,
7355            vec![RuntimeValue::String(toon.to_string())],
7356            &Shared::new(SharedCell::new(Env::default())),
7357        );
7358        assert_eq!(result, expected);
7359    }
7360
7361    #[rstest]
7362    #[case::simple_kv(
7363        "name = \"Alice\"\nage = 30",
7364        {
7365            let mut map = BTreeMap::new();
7366            map.insert(Ident::new("name"), RuntimeValue::String("Alice".to_string()));
7367            map.insert(Ident::new("age"), RuntimeValue::Number(30.into()));
7368            Ok(RuntimeValue::Dict(map))
7369        }
7370    )]
7371    #[case::boolean(
7372        "enabled = true\ndisabled = false",
7373        {
7374            let mut map = BTreeMap::new();
7375            map.insert(Ident::new("enabled"), RuntimeValue::Boolean(true));
7376            map.insert(Ident::new("disabled"), RuntimeValue::Boolean(false));
7377            Ok(RuntimeValue::Dict(map))
7378        }
7379    )]
7380    #[case::nested_table(
7381        "[server]\nhost = \"localhost\"\nport = 8080",
7382        {
7383            let mut inner = BTreeMap::new();
7384            inner.insert(Ident::new("host"), RuntimeValue::String("localhost".to_string()));
7385            inner.insert(Ident::new("port"), RuntimeValue::Number(8080.into()));
7386            let mut map = BTreeMap::new();
7387            map.insert(Ident::new("server"), RuntimeValue::Dict(inner));
7388            Ok(RuntimeValue::Dict(map))
7389        }
7390    )]
7391    #[case::array(
7392        "tags = [\"rust\", \"toml\"]",
7393        {
7394            let mut map = BTreeMap::new();
7395            map.insert(Ident::new("tags"), RuntimeValue::Array(vec![
7396                RuntimeValue::String("rust".to_string()),
7397                RuntimeValue::String("toml".to_string()),
7398            ]));
7399            Ok(RuntimeValue::Dict(map))
7400        }
7401    )]
7402    fn test_toml_parse(#[case] toml: &str, #[case] expected: Result<RuntimeValue, Error>) {
7403        let ident = Ident::new("_toml_parse");
7404        let result = eval_builtin(
7405            &RuntimeValue::None,
7406            &ident,
7407            vec![RuntimeValue::String(toml.to_string())],
7408            &Shared::new(SharedCell::new(Env::default())),
7409        );
7410        assert_eq!(result, expected);
7411    }
7412
7413    #[rstest]
7414    #[case::invalid_toml("name = ")]
7415    fn test_toml_parse_error(#[case] input: &str) {
7416        let ident = Ident::new("_toml_parse");
7417        let result = eval_builtin(
7418            &RuntimeValue::None,
7419            &ident,
7420            vec![RuntimeValue::String(input.to_string())],
7421            &Shared::new(SharedCell::new(Env::default())),
7422        );
7423        assert!(result.is_err());
7424    }
7425
7426    #[rstest]
7427    #[case::invalid_type(RuntimeValue::Number(1.into()))]
7428    fn test_toml_parse_invalid_type(#[case] input: RuntimeValue) {
7429        let ident = Ident::new("_toml_parse");
7430        let result = eval_builtin(
7431            &RuntimeValue::None,
7432            &ident,
7433            vec![input],
7434            &Shared::new(SharedCell::new(Env::default())),
7435        );
7436        assert!(result.is_err());
7437    }
7438
7439    #[rstest]
7440    #[case::simple_map(
7441        // {"name": "Alice", "age": 30}
7442        "omRuYW1lZUFsaWNlY2FnZRge",
7443        {
7444            let mut map = BTreeMap::new();
7445            map.insert(Ident::new("name"), RuntimeValue::String("Alice".to_string()));
7446            map.insert(Ident::new("age"), RuntimeValue::Number(30.into()));
7447            Ok(RuntimeValue::Dict(map))
7448        }
7449    )]
7450    fn test_cbor_parse(#[case] input: &str, #[case] expected: Result<RuntimeValue, Error>) {
7451        let ident = Ident::new("_cbor_parse");
7452        let result = eval_builtin(
7453            &RuntimeValue::None,
7454            &ident,
7455            vec![RuntimeValue::String(input.to_string())],
7456            &Shared::new(SharedCell::new(Env::default())),
7457        );
7458        assert_eq!(result, expected);
7459    }
7460
7461    #[rstest]
7462    #[case::invalid_base64("not-valid-base64!!!")]
7463    #[case::invalid_cbor("aGVsbG8=")]
7464    fn test_cbor_parse_error(#[case] input: &str) {
7465        let ident = Ident::new("_cbor_parse");
7466        let result = eval_builtin(
7467            &RuntimeValue::None,
7468            &ident,
7469            vec![RuntimeValue::String(input.to_string())],
7470            &Shared::new(SharedCell::new(Env::default())),
7471        );
7472        assert!(result.is_err());
7473    }
7474
7475    #[rstest]
7476    #[case::invalid_type(RuntimeValue::Number(1.into()))]
7477    fn test_cbor_parse_invalid_type(#[case] input: RuntimeValue) {
7478        let ident = Ident::new("_cbor_parse");
7479        let result = eval_builtin(
7480            &RuntimeValue::None,
7481            &ident,
7482            vec![input],
7483            &Shared::new(SharedCell::new(Env::default())),
7484        );
7485        assert!(result.is_err());
7486    }
7487
7488    #[rstest]
7489    #[case::simple_block(
7490        r#"resource "aws_instance" "example" { ami = "abc-123" }"#,
7491        {
7492            let mut instance = BTreeMap::new();
7493            instance.insert(Ident::new("ami"), RuntimeValue::String("abc-123".to_string()));
7494            let mut example = BTreeMap::new();
7495            example.insert(Ident::new("example"), RuntimeValue::Dict(instance));
7496            let mut resource = BTreeMap::new();
7497            resource.insert(Ident::new("aws_instance"), RuntimeValue::Dict(example));
7498            let mut map = BTreeMap::new();
7499            map.insert(Ident::new("resource"), RuntimeValue::Dict(resource));
7500            Ok(RuntimeValue::Dict(map))
7501        }
7502    )]
7503    fn test_hcl_parse(#[case] input: &str, #[case] expected: Result<RuntimeValue, Error>) {
7504        let ident = Ident::new("_hcl_parse");
7505        let result = eval_builtin(
7506            &RuntimeValue::None,
7507            &ident,
7508            vec![RuntimeValue::String(input.to_string())],
7509            &Shared::new(SharedCell::new(Env::default())),
7510        );
7511        assert_eq!(result, expected);
7512    }
7513
7514    #[rstest]
7515    #[case::invalid_type(RuntimeValue::Number(1.into()))]
7516    fn test_hcl_parse_invalid_type(#[case] input: RuntimeValue) {
7517        let ident = Ident::new("_hcl_parse");
7518        let result = eval_builtin(
7519            &RuntimeValue::None,
7520            &ident,
7521            vec![input],
7522            &Shared::new(SharedCell::new(Env::default())),
7523        );
7524        assert!(result.is_err());
7525    }
7526
7527    #[test]
7528    fn test_hcl_stringify_dict() {
7529        let ident = Ident::new("_hcl_stringify");
7530        let mut map = BTreeMap::new();
7531        map.insert(Ident::new("name"), RuntimeValue::String("Alice".to_string()));
7532        map.insert(Ident::new("age"), RuntimeValue::Number(30.into()));
7533        let input = RuntimeValue::Dict(map);
7534        let result = eval_builtin(
7535            &RuntimeValue::None,
7536            &ident,
7537            vec![input],
7538            &Shared::new(SharedCell::new(Env::default())),
7539        );
7540        assert!(result.is_ok());
7541        let s = result.unwrap().to_string();
7542        assert!(s.contains("name") && s.contains("Alice"));
7543        assert!(s.contains("age"));
7544    }
7545
7546    #[rstest]
7547    #[case::simple_map(
7548        // {"name": "Alice", "age": 30} encoded as CBOR then base64
7549        "omRuYW1lZUFsaWNlY2FnZRge",
7550        {
7551            let mut map = BTreeMap::new();
7552            map.insert(Ident::new("name"), RuntimeValue::String("Alice".to_string()));
7553            map.insert(Ident::new("age"), RuntimeValue::Number(30.into()));
7554            Ok(RuntimeValue::Dict(map))
7555        }
7556    )]
7557    fn test_cbor_stringify_roundtrip(#[case] base64_input: &str, #[case] expected: Result<RuntimeValue, Error>) {
7558        let env = Shared::new(SharedCell::new(Env::default()));
7559
7560        // parse
7561        let ident_parse = Ident::new("_cbor_parse");
7562        let parsed = eval_builtin(
7563            &RuntimeValue::None,
7564            &ident_parse,
7565            vec![RuntimeValue::String(base64_input.to_string())],
7566            &env,
7567        );
7568        assert!(parsed.is_ok());
7569        assert_eq!(parsed.as_ref().ok(), expected.as_ref().ok());
7570
7571        // stringify
7572        let ident_stringify = Ident::new("_cbor_stringify");
7573        let bytes_result = eval_builtin(&RuntimeValue::None, &ident_stringify, vec![parsed.unwrap()], &env);
7574        assert!(bytes_result.is_ok());
7575        assert!(matches!(bytes_result.unwrap(), RuntimeValue::Bytes(_)));
7576    }
7577
7578    #[test]
7579    fn test_cbor_parse_from_bytes() {
7580        // {"name": "Alice"} as raw CBOR bytes
7581        let cbor_bytes = base64::engine::general_purpose::STANDARD
7582            .decode("oWRuYW1lZUFsaWNl")
7583            .unwrap();
7584        let ident = Ident::new("_cbor_parse");
7585        let result = eval_builtin(
7586            &RuntimeValue::None,
7587            &ident,
7588            vec![RuntimeValue::Bytes(cbor_bytes)],
7589            &Shared::new(SharedCell::new(Env::default())),
7590        );
7591        assert!(result.is_ok());
7592        let mut expected = BTreeMap::new();
7593        expected.insert(Ident::new("name"), RuntimeValue::String("Alice".to_string()));
7594        assert_eq!(result.unwrap(), RuntimeValue::Dict(expected));
7595    }
7596
7597    #[test]
7598    fn test_base64_bytes_input() {
7599        let ident = Ident::new("base64");
7600        let bytes = vec![0x48u8, 0x65, 0x6c, 0x6c, 0x6f]; // "Hello"
7601        let result = eval_builtin(
7602            &RuntimeValue::None,
7603            &ident,
7604            vec![RuntimeValue::Bytes(bytes)],
7605            &Shared::new(SharedCell::new(Env::default())),
7606        );
7607        assert_eq!(result, Ok(RuntimeValue::String("SGVsbG8=".to_string())));
7608    }
7609
7610    #[rstest]
7611    #[case::string(
7612        RuntimeValue::String("hello".to_string()),
7613        Ok(RuntimeValue::Bytes(vec![0x68, 0x65, 0x6c, 0x6c, 0x6f]))
7614    )]
7615    #[case::empty_string(
7616        RuntimeValue::String("".to_string()),
7617        Ok(RuntimeValue::Bytes(vec![]))
7618    )]
7619    #[case::utf8_string(
7620        RuntimeValue::String("あ".to_string()),
7621        Ok(RuntimeValue::Bytes(vec![0xe3, 0x81, 0x82]))
7622    )]
7623    #[case::array_of_numbers(
7624        RuntimeValue::Array(vec![
7625            RuntimeValue::Number(0.into()),
7626            RuntimeValue::Number(255.into()),
7627            RuntimeValue::Number(128.into()),
7628        ]),
7629        Ok(RuntimeValue::Bytes(vec![0, 255, 128]))
7630    )]
7631    #[case::bytes_identity(
7632        RuntimeValue::Bytes(vec![1, 2, 3]),
7633        Ok(RuntimeValue::Bytes(vec![1, 2, 3]))
7634    )]
7635    fn test_to_bytes(#[case] input: RuntimeValue, #[case] expected: Result<RuntimeValue, Error>) {
7636        let ident = Ident::new("to_bytes");
7637        let result = eval_builtin(
7638            &RuntimeValue::None,
7639            &ident,
7640            vec![input],
7641            &Shared::new(SharedCell::new(Env::default())),
7642        );
7643        assert_eq!(result, expected);
7644    }
7645
7646    #[rstest]
7647    #[case::number(RuntimeValue::Number(42.into()))]
7648    #[case::array_with_non_number(RuntimeValue::Array(vec![RuntimeValue::String("x".to_string())]))]
7649    #[case::array_with_negative(RuntimeValue::Array(vec![RuntimeValue::Number((-1i64).into())]))]
7650    #[case::array_with_256(RuntimeValue::Array(vec![RuntimeValue::Number(256i64.into())]))]
7651    #[case::array_with_fractional(RuntimeValue::Array(vec![RuntimeValue::Number(1.5f64.into())]))]
7652    #[case::array_with_nan(RuntimeValue::Array(vec![RuntimeValue::Number(f64::NAN.into())]))]
7653    #[case::array_with_infinity(RuntimeValue::Array(vec![RuntimeValue::Number(f64::INFINITY.into())]))]
7654    fn test_to_bytes_invalid(#[case] input: RuntimeValue) {
7655        let ident = Ident::new("to_bytes");
7656        let result = eval_builtin(
7657            &RuntimeValue::None,
7658            &ident,
7659            vec![input],
7660            &Shared::new(SharedCell::new(Env::default())),
7661        );
7662        assert!(result.is_err());
7663    }
7664
7665    #[test]
7666    fn test_bytes_add() {
7667        let ident = Ident::new("add");
7668        let result = eval_builtin(
7669            &RuntimeValue::None,
7670            &ident,
7671            vec![RuntimeValue::Bytes(vec![1, 2]), RuntimeValue::Bytes(vec![3, 4])],
7672            &Shared::new(SharedCell::new(Env::default())),
7673        );
7674        assert_eq!(result, Ok(RuntimeValue::Bytes(vec![1, 2, 3, 4])));
7675    }
7676
7677    #[test]
7678    fn test_bytes_reverse() {
7679        let ident = Ident::new("reverse");
7680        let result = eval_builtin(
7681            &RuntimeValue::None,
7682            &ident,
7683            vec![RuntimeValue::Bytes(vec![1, 2, 3])],
7684            &Shared::new(SharedCell::new(Env::default())),
7685        );
7686        assert_eq!(result, Ok(RuntimeValue::Bytes(vec![3, 2, 1])));
7687    }
7688
7689    #[test]
7690    fn test_bytes_slice() {
7691        let ident = Ident::new("slice");
7692        let result = eval_builtin(
7693            &RuntimeValue::None,
7694            &ident,
7695            vec![
7696                RuntimeValue::Bytes(vec![10, 20, 30, 40, 50]),
7697                RuntimeValue::Number(1.into()),
7698                RuntimeValue::Number(4.into()),
7699            ],
7700            &Shared::new(SharedCell::new(Env::default())),
7701        );
7702        assert_eq!(result, Ok(RuntimeValue::Bytes(vec![20, 30, 40])));
7703    }
7704
7705    #[test]
7706    fn test_md5_bytes_input() {
7707        let ident = Ident::new("md5");
7708        let result = eval_builtin(
7709            &RuntimeValue::None,
7710            &ident,
7711            vec![RuntimeValue::Bytes(b"hello".to_vec())],
7712            &Shared::new(SharedCell::new(Env::default())),
7713        );
7714        assert_eq!(
7715            result,
7716            Ok(RuntimeValue::String("5d41402abc4b2a76b9719d911017c592".to_string()))
7717        );
7718    }
7719
7720    #[test]
7721    fn test_sha256_bytes_input() {
7722        let ident = Ident::new("sha256");
7723        let result = eval_builtin(
7724            &RuntimeValue::None,
7725            &ident,
7726            vec![RuntimeValue::Bytes(b"hello".to_vec())],
7727            &Shared::new(SharedCell::new(Env::default())),
7728        );
7729        assert_eq!(
7730            result,
7731            Ok(RuntimeValue::String(
7732                "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824".to_string()
7733            ))
7734        );
7735    }
7736
7737    #[test]
7738    fn test_sha512_string_input() {
7739        let ident = Ident::new("sha512");
7740        let result = eval_builtin(
7741            &RuntimeValue::None,
7742            &ident,
7743            vec![RuntimeValue::String("hello".to_string())],
7744            &Shared::new(SharedCell::new(Env::default())),
7745        );
7746        assert_eq!(
7747            result,
7748            Ok(RuntimeValue::String(
7749                "9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72323c3d99ba5c11d7c7acc6e14b8c5da0c4663475c2e5c3adef46f73bcdec043".to_string()
7750            ))
7751        );
7752    }
7753
7754    #[test]
7755    fn test_sha512_bytes_input() {
7756        let ident = Ident::new("sha512");
7757        let result = eval_builtin(
7758            &RuntimeValue::None,
7759            &ident,
7760            vec![RuntimeValue::Bytes(b"hello".to_vec())],
7761            &Shared::new(SharedCell::new(Env::default())),
7762        );
7763        assert_eq!(
7764            result,
7765            Ok(RuntimeValue::String(
7766                "9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72323c3d99ba5c11d7c7acc6e14b8c5da0c4663475c2e5c3adef46f73bcdec043".to_string()
7767            ))
7768        );
7769    }
7770
7771    #[rstest]
7772    #[case::lowercase("deadbeef", Ok(RuntimeValue::Bytes(vec![0xde, 0xad, 0xbe, 0xef])))]
7773    #[case::uppercase("DEADBEEF", Ok(RuntimeValue::Bytes(vec![0xde, 0xad, 0xbe, 0xef])))]
7774    #[case::empty("", Ok(RuntimeValue::Bytes(vec![])))]
7775    fn test_from_hex(#[case] input: &str, #[case] expected: Result<RuntimeValue, Error>) {
7776        let ident = Ident::new("from_hex");
7777        let result = eval_builtin(
7778            &RuntimeValue::None,
7779            &ident,
7780            vec![RuntimeValue::String(input.to_string())],
7781            &Shared::new(SharedCell::new(Env::default())),
7782        );
7783        assert_eq!(result, expected);
7784    }
7785
7786    #[rstest]
7787    #[case::odd_length("abc", true)]
7788    #[case::invalid_chars("zzzz", true)]
7789    fn test_from_hex_invalid(#[case] input: &str, #[case] is_err: bool) {
7790        let ident = Ident::new("from_hex");
7791        let result = eval_builtin(
7792            &RuntimeValue::None,
7793            &ident,
7794            vec![RuntimeValue::String(input.to_string())],
7795            &Shared::new(SharedCell::new(Env::default())),
7796        );
7797        assert_eq!(result.is_err(), is_err);
7798    }
7799
7800    #[rstest]
7801    #[case::basic(vec![0xde, 0xad, 0xbe, 0xef], Ok(RuntimeValue::String("deadbeef".to_string())))]
7802    #[case::empty(vec![], Ok(RuntimeValue::String("".to_string())))]
7803    #[case::zero_ff(vec![0x00, 0xff], Ok(RuntimeValue::String("00ff".to_string())))]
7804    #[case::all_zeros(vec![0x00, 0x00], Ok(RuntimeValue::String("0000".to_string())))]
7805    fn test_to_hex(#[case] input: Vec<u8>, #[case] expected: Result<RuntimeValue, Error>) {
7806        let ident = Ident::new("to_hex");
7807        let result = eval_builtin(
7808            &RuntimeValue::None,
7809            &ident,
7810            vec![RuntimeValue::Bytes(input)],
7811            &Shared::new(SharedCell::new(Env::default())),
7812        );
7813        assert_eq!(result, expected);
7814    }
7815
7816    #[test]
7817    fn test_to_hex_roundtrip() {
7818        let env = Shared::new(SharedCell::new(Env::default()));
7819        let original = vec![0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef];
7820        let hex = eval_builtin(
7821            &RuntimeValue::None,
7822            &Ident::new("to_hex"),
7823            vec![RuntimeValue::Bytes(original.clone())],
7824            &env,
7825        )
7826        .unwrap();
7827        let roundtripped = eval_builtin(&RuntimeValue::None, &Ident::new("from_hex"), vec![hex], &env).unwrap();
7828        assert_eq!(roundtripped, RuntimeValue::Bytes(original));
7829    }
7830
7831    #[rstest]
7832    #[case("gt",  vec![0x02], vec![0x01], true)]
7833    #[case("gt",  vec![0x01], vec![0x02], false)]
7834    #[case("gt",  vec![0x01], vec![0x01], false)]
7835    #[case("gt",  vec![0x01, 0x00], vec![0x01], true)]
7836    #[case("gte", vec![0x02], vec![0x01], true)]
7837    #[case("gte", vec![0x01], vec![0x01], true)]
7838    #[case("gte", vec![0x01], vec![0x02], false)]
7839    #[case("lt",  vec![0x01], vec![0x02], true)]
7840    #[case("lt",  vec![0x02], vec![0x01], false)]
7841    #[case("lt",  vec![0x01], vec![0x01], false)]
7842    #[case("lte", vec![0x01], vec![0x02], true)]
7843    #[case("lte", vec![0x01], vec![0x01], true)]
7844    #[case("lte", vec![0x02], vec![0x01], false)]
7845    fn test_bytes_comparison(#[case] op: &str, #[case] lhs: Vec<u8>, #[case] rhs: Vec<u8>, #[case] expected: bool) {
7846        let ident = Ident::new(op);
7847        let result = eval_builtin(
7848            &RuntimeValue::None,
7849            &ident,
7850            vec![RuntimeValue::Bytes(lhs), RuntimeValue::Bytes(rhs)],
7851            &Shared::new(SharedCell::new(Env::default())),
7852        );
7853        assert_eq!(result, Ok(RuntimeValue::Boolean(expected)));
7854    }
7855
7856    #[test]
7857    fn test_utf8_valid() {
7858        let ident = Ident::new("utf8");
7859        let result = eval_builtin(
7860            &RuntimeValue::None,
7861            &ident,
7862            vec![RuntimeValue::Bytes(b"hello".to_vec())],
7863            &Shared::new(SharedCell::new(Env::default())),
7864        );
7865        assert_eq!(result, Ok(RuntimeValue::String("hello".to_string())));
7866    }
7867
7868    #[test]
7869    fn test_utf8_invalid() {
7870        let ident = Ident::new("utf8");
7871        let result = eval_builtin(
7872            &RuntimeValue::None,
7873            &ident,
7874            vec![RuntimeValue::Bytes(vec![0xff, 0xfe])],
7875            &Shared::new(SharedCell::new(Env::default())),
7876        );
7877        assert!(result.is_err());
7878    }
7879
7880    #[test]
7881    fn test_xor_basic() {
7882        let ident = Ident::new("xor");
7883        let result = eval_builtin(
7884            &RuntimeValue::None,
7885            &ident,
7886            vec![
7887                RuntimeValue::Bytes(vec![0xaa, 0xbb]),
7888                RuntimeValue::Bytes(vec![0x55, 0x44]),
7889            ],
7890            &Shared::new(SharedCell::new(Env::default())),
7891        );
7892        assert_eq!(result, Ok(RuntimeValue::Bytes(vec![0xff, 0xff])));
7893    }
7894
7895    #[test]
7896    fn test_xor_identity() {
7897        let ident = Ident::new("xor");
7898        let result = eval_builtin(
7899            &RuntimeValue::None,
7900            &ident,
7901            vec![
7902                RuntimeValue::Bytes(vec![0x01, 0x02, 0x03]),
7903                RuntimeValue::Bytes(vec![0x00, 0x00, 0x00]),
7904            ],
7905            &Shared::new(SharedCell::new(Env::default())),
7906        );
7907        assert_eq!(result, Ok(RuntimeValue::Bytes(vec![0x01, 0x02, 0x03])));
7908    }
7909
7910    #[test]
7911    fn test_xor_length_mismatch() {
7912        let ident = Ident::new("xor");
7913        let result = eval_builtin(
7914            &RuntimeValue::None,
7915            &ident,
7916            vec![RuntimeValue::Bytes(vec![0x01, 0x02]), RuntimeValue::Bytes(vec![0x01])],
7917            &Shared::new(SharedCell::new(Env::default())),
7918        );
7919        assert!(result.is_err());
7920    }
7921
7922    #[rstest]
7923    #[case::simple(
7924        "<root>hello</root>",
7925        {
7926            let mut root = BTreeMap::new();
7927            root.insert(Ident::new("tag"), RuntimeValue::String("root".to_string()));
7928            root.insert(Ident::new("attributes"), RuntimeValue::new_dict());
7929            root.insert(Ident::new("children"), RuntimeValue::EMPTY_ARRAY);
7930            root.insert(Ident::new("text"), RuntimeValue::String("hello".to_string()));
7931            Ok(RuntimeValue::Dict(root))
7932        }
7933    )]
7934    #[case::with_attributes(
7935        "<root id=\"1\" class=\"main\">hello</root>",
7936        {
7937            let mut root = BTreeMap::new();
7938            let mut attrs = BTreeMap::new();
7939            attrs.insert(Ident::new("id"), RuntimeValue::String("1".to_string()));
7940            attrs.insert(Ident::new("class"), RuntimeValue::String("main".to_string()));
7941            root.insert(Ident::new("tag"), RuntimeValue::String("root".to_string()));
7942            root.insert(Ident::new("attributes"), RuntimeValue::Dict(attrs));
7943            root.insert(Ident::new("children"), RuntimeValue::EMPTY_ARRAY);
7944            root.insert(Ident::new("text"), RuntimeValue::String("hello".to_string()));
7945            Ok(RuntimeValue::Dict(root))
7946        }
7947    )]
7948    #[case::nested(
7949        "<root><child id=\"1\">hello</child><child id=\"2\">world</child></root>",
7950        {
7951            let mut root = BTreeMap::new();
7952            let mut child1 = BTreeMap::new();
7953            let mut attrs1 = BTreeMap::new();
7954            attrs1.insert(Ident::new("id"), RuntimeValue::String("1".to_string()));
7955            child1.insert(Ident::new("tag"), RuntimeValue::String("child".to_string()));
7956            child1.insert(Ident::new("attributes"), RuntimeValue::Dict(attrs1));
7957            child1.insert(Ident::new("children"), RuntimeValue::EMPTY_ARRAY);
7958            child1.insert(Ident::new("text"), RuntimeValue::String("hello".to_string()));
7959
7960            let mut child2 = BTreeMap::new();
7961            let mut attrs2 = BTreeMap::new();
7962            attrs2.insert(Ident::new("id"), RuntimeValue::String("2".to_string()));
7963            child2.insert(Ident::new("tag"), RuntimeValue::String("child".to_string()));
7964            child2.insert(Ident::new("attributes"), RuntimeValue::Dict(attrs2));
7965            child2.insert(Ident::new("children"), RuntimeValue::EMPTY_ARRAY);
7966            child2.insert(Ident::new("text"), RuntimeValue::String("world".to_string()));
7967
7968            root.insert(Ident::new("tag"), RuntimeValue::String("root".to_string()));
7969            root.insert(Ident::new("attributes"), RuntimeValue::new_dict());
7970            root.insert(Ident::new("children"), RuntimeValue::Array(vec![
7971                RuntimeValue::Dict(child1),
7972                RuntimeValue::Dict(child2),
7973            ]));
7974            root.insert(Ident::new("text"), RuntimeValue::NONE);
7975            Ok(RuntimeValue::Dict(root))
7976        }
7977    )]
7978    #[case::self_closing(
7979        "<root><child id=\"1\"/></root>",
7980        {
7981            let mut root = BTreeMap::new();
7982            let mut child = BTreeMap::new();
7983            let mut attrs = BTreeMap::new();
7984            attrs.insert(Ident::new("id"), RuntimeValue::String("1".to_string()));
7985            child.insert(Ident::new("tag"), RuntimeValue::String("child".to_string()));
7986            child.insert(Ident::new("attributes"), RuntimeValue::Dict(attrs));
7987            child.insert(Ident::new("children"), RuntimeValue::EMPTY_ARRAY);
7988            child.insert(Ident::new("text"), RuntimeValue::NONE);
7989
7990            root.insert(Ident::new("tag"), RuntimeValue::String("root".to_string()));
7991            root.insert(Ident::new("attributes"), RuntimeValue::new_dict());
7992            root.insert(Ident::new("children"), RuntimeValue::Array(vec![
7993                RuntimeValue::Dict(child),
7994            ]));
7995            root.insert(Ident::new("text"), RuntimeValue::NONE);
7996            Ok(RuntimeValue::Dict(root))
7997        }
7998    )]
7999    fn test_xml_parse(#[case] xml: &str, #[case] expected: Result<RuntimeValue, Error>) {
8000        let ident = Ident::new("_xml_parse");
8001        let result = eval_builtin(
8002            &RuntimeValue::None,
8003            &ident,
8004            vec![RuntimeValue::String(xml.to_string())],
8005            &Shared::new(SharedCell::new(Env::default())),
8006        );
8007        assert_eq!(result, expected);
8008    }
8009
8010    #[test]
8011    fn test_diff_strings() {
8012        let ident = Ident::new("_diff");
8013        let result = eval_builtin(
8014            &RuntimeValue::None,
8015            &ident,
8016            vec![RuntimeValue::String("abc".into()), RuntimeValue::String("abc ".into())],
8017            &Shared::new(SharedCell::new(Env::default())),
8018        );
8019
8020        assert!(result.is_ok());
8021        if let Ok(RuntimeValue::Array(changes)) = result {
8022            // line-level diff: delete "abc" + insert "abc " (replace pair)
8023            assert_eq!(changes.len(), 2);
8024            if let RuntimeValue::Dict(ref m) = changes[0] {
8025                assert_eq!(m.get(&Ident::new("tag")), Some(&RuntimeValue::String("delete".into())));
8026                assert_eq!(m.get(&Ident::new("value")), Some(&RuntimeValue::String("abc".into())));
8027                assert!(m.contains_key(&Ident::new("inline")));
8028            } else {
8029                panic!("Expected Dict change");
8030            }
8031            if let RuntimeValue::Dict(ref m) = changes[1] {
8032                assert_eq!(m.get(&Ident::new("tag")), Some(&RuntimeValue::String("insert".into())));
8033                assert_eq!(m.get(&Ident::new("value")), Some(&RuntimeValue::String("abc ".into())));
8034                // inline should show the trailing space as "insert"
8035                if let Some(RuntimeValue::Array(inline)) = m.get(&Ident::new("inline")) {
8036                    let last = inline.last().expect("inline should not be empty");
8037                    if let RuntimeValue::Dict(lm) = last {
8038                        assert_eq!(lm.get(&Ident::new("tag")), Some(&RuntimeValue::String("insert".into())));
8039                        assert_eq!(lm.get(&Ident::new("value")), Some(&RuntimeValue::String(" ".into())));
8040                    } else {
8041                        panic!("Expected Dict in inline");
8042                    }
8043                } else {
8044                    panic!("Expected inline Array");
8045                }
8046            } else {
8047                panic!("Expected Dict change");
8048            }
8049        } else {
8050            panic!("Expected Array result");
8051        }
8052    }
8053
8054    #[test]
8055    fn test_diff_arrays() {
8056        let ident = Ident::new("_diff");
8057        let result = eval_builtin(
8058            &RuntimeValue::None,
8059            &ident,
8060            vec![
8061                RuntimeValue::Array(vec![RuntimeValue::Number(1.into())]),
8062                RuntimeValue::Array(vec![RuntimeValue::Number(2.into())]),
8063            ],
8064            &Shared::new(SharedCell::new(Env::default())),
8065        );
8066
8067        assert!(result.is_ok());
8068        if let Ok(RuntimeValue::Array(changes)) = result {
8069            assert_eq!(changes.len(), 2); // delete 1, insert 2
8070            if let RuntimeValue::Dict(ref m) = changes[0] {
8071                assert_eq!(m.get(&Ident::new("tag")), Some(&RuntimeValue::String("delete".into())));
8072                assert_eq!(m.get(&Ident::new("value")), Some(&RuntimeValue::Number(1.into())));
8073                // non-string elements have no inline field
8074                assert!(!m.contains_key(&Ident::new("inline")));
8075            } else {
8076                panic!("Expected Dict change");
8077            }
8078            if let RuntimeValue::Dict(ref m) = changes[1] {
8079                assert_eq!(m.get(&Ident::new("tag")), Some(&RuntimeValue::String("insert".into())));
8080                assert_eq!(m.get(&Ident::new("value")), Some(&RuntimeValue::Number(2.into())));
8081                assert!(!m.contains_key(&Ident::new("inline")));
8082            } else {
8083                panic!("Expected Dict change");
8084            }
8085        } else {
8086            panic!("Expected Array result");
8087        }
8088    }
8089
8090    #[rstest]
8091    #[case::single_number(vec![RuntimeValue::Number(1.into())], vec![1u8])]
8092    #[case::multiple_numbers(vec![RuntimeValue::Number(1.into()), RuntimeValue::Number(2.into())], vec![1u8, 2u8])]
8093    #[case::number_array(vec![RuntimeValue::Array(vec![RuntimeValue::Number(1.into()), RuntimeValue::Number(2.into())])], vec![1u8, 2u8])]
8094    #[case::empty(vec![], vec![])]
8095    #[case::ignores_strings(vec![RuntimeValue::String("x".into())], vec![])]
8096    fn test_collect_depth_values(#[case] args: Vec<RuntimeValue>, #[case] expected: Vec<u8>) {
8097        assert_eq!(collect_depth_values(&args), expected);
8098    }
8099
8100    #[rstest]
8101    #[case::single_string(vec![RuntimeValue::String("rust".into())], vec!["rust".to_string()])]
8102    #[case::multiple_strings(vec![RuntimeValue::String("rust".into()), RuntimeValue::String("go".into())], vec!["rust".to_string(), "go".to_string()])]
8103    #[case::string_array(vec![RuntimeValue::Array(vec![RuntimeValue::String("rust".into()), RuntimeValue::String("go".into())])], vec!["rust".to_string(), "go".to_string()])]
8104    #[case::empty(vec![], vec![])]
8105    #[case::ignores_numbers(vec![RuntimeValue::Number(1.into())], vec![])]
8106    fn test_collect_string_values(#[case] args: Vec<RuntimeValue>, #[case] expected: Vec<String>) {
8107        assert_eq!(collect_string_values(&args), expected);
8108    }
8109
8110    #[rstest]
8111    #[case::heading_depth_match(
8112        Node::Heading(mq_markdown::Heading { depth: 1, values: vec![], position: None }),
8113        Selector::Heading(None),
8114        vec![RuntimeValue::Number(1.into())],
8115        true
8116    )]
8117    #[case::heading_depth_no_match(
8118        Node::Heading(mq_markdown::Heading { depth: 2, values: vec![], position: None }),
8119        Selector::Heading(None),
8120        vec![RuntimeValue::Number(1.into())],
8121        false
8122    )]
8123    #[case::heading_multi_depth_match(
8124        Node::Heading(mq_markdown::Heading { depth: 2, values: vec![], position: None }),
8125        Selector::Heading(None),
8126        vec![RuntimeValue::Number(1.into()), RuntimeValue::Number(2.into())],
8127        true
8128    )]
8129    #[case::heading_no_args_fallback(
8130        Node::Heading(mq_markdown::Heading { depth: 1, values: vec![], position: None }),
8131        Selector::Heading(None),
8132        vec![],
8133        true
8134    )]
8135    #[case::code_lang_match(
8136        Node::Code(mq_markdown::Code { lang: Some("rust".to_string()), meta: None, value: "fn main() {}".to_string(), fence: true, position: None }),
8137        Selector::Code,
8138        vec![RuntimeValue::String("rust".into())],
8139        true
8140    )]
8141    #[case::code_lang_no_match(
8142        Node::Code(mq_markdown::Code { lang: Some("python".to_string()), meta: None, value: "pass".to_string(), fence: true, position: None }),
8143        Selector::Code,
8144        vec![RuntimeValue::String("rust".into())],
8145        false
8146    )]
8147    #[case::code_no_args_fallback(
8148        Node::Code(mq_markdown::Code { lang: None, meta: None, value: "".to_string(), fence: true, position: None }),
8149        Selector::Code,
8150        vec![],
8151        true
8152    )]
8153    #[case::non_heading_node(
8154        Node::HorizontalRule(mq_markdown::HorizontalRule { position: None }),
8155        Selector::Heading(None),
8156        vec![RuntimeValue::Number(1.into())],
8157        false
8158    )]
8159    #[case::list_index_match(
8160        Node::List(mq_markdown::List { index: 2, level: 0, checked: None, ordered: false, values: vec![], position: None }),
8161        Selector::List(None, None),
8162        vec![RuntimeValue::Number(2.into())],
8163        true
8164    )]
8165    #[case::list_index_no_match(
8166        Node::List(mq_markdown::List { index: 0, level: 0, checked: None, ordered: false, values: vec![], position: None }),
8167        Selector::List(None, None),
8168        vec![RuntimeValue::Number(1.into())],
8169        false
8170    )]
8171    #[case::list_multi_index_match(
8172        Node::List(mq_markdown::List { index: 3, level: 0, checked: None, ordered: false, values: vec![], position: None }),
8173        Selector::List(None, None),
8174        vec![RuntimeValue::Number(1.into()), RuntimeValue::Number(3.into())],
8175        true
8176    )]
8177    #[case::list_no_args_fallback(
8178        Node::List(mq_markdown::List { index: 0, level: 0, checked: None, ordered: false, values: vec![], position: None }),
8179        Selector::List(None, None),
8180        vec![],
8181        true
8182    )]
8183    #[case::list_non_list_node(
8184        Node::HorizontalRule(mq_markdown::HorizontalRule { position: None }),
8185        Selector::List(None, None),
8186        vec![RuntimeValue::Number(0.into())],
8187        false
8188    )]
8189    #[case::table_row_match(
8190        Node::TableCell(mq_markdown::TableCell { column: 0, row: 1, values: vec![], position: None }),
8191        Selector::Table(None, None),
8192        vec![RuntimeValue::Number(1.into())],
8193        true
8194    )]
8195    #[case::table_row_no_match(
8196        Node::TableCell(mq_markdown::TableCell { column: 0, row: 0, values: vec![], position: None }),
8197        Selector::Table(None, None),
8198        vec![RuntimeValue::Number(1.into())],
8199        false
8200    )]
8201    #[case::table_row_and_col_match(
8202        Node::TableCell(mq_markdown::TableCell { column: 2, row: 1, values: vec![], position: None }),
8203        Selector::Table(None, None),
8204        vec![RuntimeValue::Number(1.into()), RuntimeValue::Number(2.into())],
8205        true
8206    )]
8207    #[case::table_row_and_col_no_match(
8208        Node::TableCell(mq_markdown::TableCell { column: 0, row: 1, values: vec![], position: None }),
8209        Selector::Table(None, None),
8210        vec![RuntimeValue::Number(1.into()), RuntimeValue::Number(2.into())],
8211        false
8212    )]
8213    #[case::table_no_args_fallback(
8214        Node::TableCell(mq_markdown::TableCell { column: 0, row: 0, values: vec![], position: None }),
8215        Selector::Table(None, None),
8216        vec![],
8217        true
8218    )]
8219    #[case::table_non_table_node(
8220        Node::HorizontalRule(mq_markdown::HorizontalRule { position: None }),
8221        Selector::Table(None, None),
8222        vec![RuntimeValue::Number(0.into())],
8223        false
8224    )]
8225    fn test_eval_selector_with_args(
8226        #[case] node: Node,
8227        #[case] selector: Selector,
8228        #[case] args: Vec<RuntimeValue>,
8229        #[case] expected_match: bool,
8230    ) {
8231        let result = eval_selector_with_args(&node, &selector, &args);
8232        assert_eq!(!result.is_none(), expected_match);
8233    }
8234
8235    fn env() -> Shared<SharedCell<Env>> {
8236        Shared::new(SharedCell::new(Env::default()))
8237    }
8238
8239    fn call(name: &str, args: Vec<RuntimeValue>) -> Result<RuntimeValue, Error> {
8240        eval_builtin(&RuntimeValue::None, &Ident::new(name), args, &env())
8241    }
8242
8243    // =========================================================================
8244    // band
8245    // =========================================================================
8246
8247    #[rstest]
8248    #[case(vec![0xff, 0xff], vec![0xff, 0xff], vec![0xff, 0xff])]
8249    #[case(vec![0xf0, 0x0f], vec![0xff, 0xff], vec![0xf0, 0x0f])]
8250    #[case(vec![0xaa, 0x55], vec![0x55, 0xaa], vec![0x00, 0x00])]
8251    #[case(vec![0xff],       vec![0x00],       vec![0x00])]
8252    #[case(vec![],           vec![],           vec![])]
8253    fn test_band(#[case] b1: Vec<u8>, #[case] b2: Vec<u8>, #[case] expected: Vec<u8>) {
8254        assert_eq!(
8255            call("band", vec![RuntimeValue::Bytes(b1), RuntimeValue::Bytes(b2)]),
8256            Ok(RuntimeValue::Bytes(expected))
8257        );
8258    }
8259
8260    #[rstest]
8261    #[case(vec![0x01, 0x02], vec![0x01])]
8262    #[case(vec![],           vec![0x00])]
8263    fn test_band_length_mismatch(#[case] b1: Vec<u8>, #[case] b2: Vec<u8>) {
8264        assert!(call("band", vec![RuntimeValue::Bytes(b1), RuntimeValue::Bytes(b2)]).is_err());
8265    }
8266
8267    #[test]
8268    fn test_band_type_error() {
8269        assert!(
8270            call(
8271                "band",
8272                vec![RuntimeValue::String("a".into()), RuntimeValue::Bytes(vec![0x01])]
8273            )
8274            .is_err()
8275        );
8276    }
8277
8278    // =========================================================================
8279    // bor
8280    // =========================================================================
8281
8282    #[rstest]
8283    #[case(vec![0x00, 0x00], vec![0x00, 0x00], vec![0x00, 0x00])]
8284    #[case(vec![0xf0, 0x00], vec![0x0f, 0x00], vec![0xff, 0x00])]
8285    #[case(vec![0xaa, 0x55], vec![0x55, 0xaa], vec![0xff, 0xff])]
8286    #[case(vec![0x00],       vec![0xff],       vec![0xff])]
8287    #[case(vec![],           vec![],           vec![])]
8288    fn test_bor(#[case] b1: Vec<u8>, #[case] b2: Vec<u8>, #[case] expected: Vec<u8>) {
8289        assert_eq!(
8290            call("bor", vec![RuntimeValue::Bytes(b1), RuntimeValue::Bytes(b2)]),
8291            Ok(RuntimeValue::Bytes(expected))
8292        );
8293    }
8294
8295    #[rstest]
8296    #[case(vec![0x01, 0x02], vec![0x01])]
8297    #[case(vec![],           vec![0x00])]
8298    fn test_bor_length_mismatch(#[case] b1: Vec<u8>, #[case] b2: Vec<u8>) {
8299        assert!(call("bor", vec![RuntimeValue::Bytes(b1), RuntimeValue::Bytes(b2)]).is_err());
8300    }
8301
8302    #[test]
8303    fn test_bor_type_error() {
8304        assert!(
8305            call(
8306                "bor",
8307                vec![RuntimeValue::Number(1.into()), RuntimeValue::Bytes(vec![0x01])]
8308            )
8309            .is_err()
8310        );
8311    }
8312
8313    // =========================================================================
8314    // bnot
8315    // =========================================================================
8316
8317    #[rstest]
8318    #[case(vec![0x00],       vec![0xff])]
8319    #[case(vec![0xff],       vec![0x00])]
8320    #[case(vec![0xf0, 0x0f], vec![0x0f, 0xf0])]
8321    #[case(vec![0x55, 0xaa], vec![0xaa, 0x55])]
8322    #[case(vec![],           vec![])]
8323    fn test_bnot(#[case] input: Vec<u8>, #[case] expected: Vec<u8>) {
8324        assert_eq!(
8325            call("bnot", vec![RuntimeValue::Bytes(input)]),
8326            Ok(RuntimeValue::Bytes(expected))
8327        );
8328    }
8329
8330    #[test]
8331    fn test_bnot_double_negation() {
8332        let original = vec![0xde, 0xad, 0xbe, 0xef];
8333        let once = call("bnot", vec![RuntimeValue::Bytes(original.clone())]).unwrap();
8334        let twice = call("bnot", vec![once]).unwrap();
8335        assert_eq!(twice, RuntimeValue::Bytes(original));
8336    }
8337
8338    #[test]
8339    fn test_bnot_type_error() {
8340        assert!(call("bnot", vec![RuntimeValue::String("a".into())]).is_err());
8341    }
8342
8343    // =========================================================================
8344    // starts_with / ends_with for bytes
8345    // =========================================================================
8346
8347    #[rstest]
8348    #[case(vec![0x01, 0x02, 0x03], vec![0x01, 0x02],             true)]
8349    #[case(vec![0x01, 0x02, 0x03], vec![0x01, 0x02, 0x03],       true)]
8350    #[case(vec![0x01, 0x02, 0x03], vec![0x02, 0x03],             false)]
8351    #[case(vec![0x01, 0x02, 0x03], vec![0x01, 0x02, 0x03, 0x04], false)]
8352    #[case(vec![0x01],             vec![],                        true)]
8353    #[case(vec![],                 vec![],                        true)]
8354    fn test_bytes_starts_with(#[case] haystack: Vec<u8>, #[case] prefix: Vec<u8>, #[case] expected: bool) {
8355        assert_eq!(
8356            call(
8357                "starts_with",
8358                vec![RuntimeValue::Bytes(haystack), RuntimeValue::Bytes(prefix)]
8359            ),
8360            Ok(RuntimeValue::Boolean(expected))
8361        );
8362    }
8363
8364    #[rstest]
8365    #[case(vec![0x01, 0x02, 0x03], vec![0x02, 0x03],             true)]
8366    #[case(vec![0x01, 0x02, 0x03], vec![0x01, 0x02, 0x03],       true)]
8367    #[case(vec![0x01, 0x02, 0x03], vec![0x01, 0x02],             false)]
8368    #[case(vec![0x01, 0x02, 0x03], vec![0x00, 0x01, 0x02, 0x03], false)]
8369    #[case(vec![0x01],             vec![],                        true)]
8370    #[case(vec![],                 vec![],                        true)]
8371    fn test_bytes_ends_with(#[case] haystack: Vec<u8>, #[case] suffix: Vec<u8>, #[case] expected: bool) {
8372        assert_eq!(
8373            call(
8374                "ends_with",
8375                vec![RuntimeValue::Bytes(haystack), RuntimeValue::Bytes(suffix)]
8376            ),
8377            Ok(RuntimeValue::Boolean(expected))
8378        );
8379    }
8380
8381    // =========================================================================
8382    // index / rindex for bytes
8383    // =========================================================================
8384
8385    #[rstest]
8386    #[case(vec![0x01, 0x02, 0x03, 0x02],     vec![0x02],       1)]
8387    #[case(vec![0x01, 0x02, 0x03],           vec![0x04],       -1)]
8388    #[case(vec![0x01, 0x02, 0x03],           vec![0x01, 0x02], 0)]
8389    #[case(vec![0x01, 0x02, 0x03],           vec![0x02, 0x03], 1)]
8390    #[case(vec![0x01, 0x02, 0x03],           vec![0x01, 0x02, 0x03], 0)]
8391    #[case(vec![0x01, 0x02, 0x03],           vec![0x01, 0x02, 0x03, 0x04], -1)]
8392    #[case(vec![],                           vec![0x01],       -1)]
8393    fn test_bytes_index(#[case] haystack: Vec<u8>, #[case] needle: Vec<u8>, #[case] expected: i64) {
8394        assert_eq!(
8395            call(
8396                "index",
8397                vec![RuntimeValue::Bytes(haystack), RuntimeValue::Bytes(needle)]
8398            ),
8399            Ok(RuntimeValue::Number(expected.into()))
8400        );
8401    }
8402
8403    #[rstest]
8404    #[case(vec![0x01, 0x02, 0x03, 0x02],     vec![0x02],       3)]
8405    #[case(vec![0x01, 0x02, 0x03],           vec![0x04],       -1)]
8406    #[case(vec![0x01, 0x02, 0x03, 0x01, 0x02], vec![0x01, 0x02], 3)]
8407    #[case(vec![0x01, 0x02, 0x03],           vec![0x01, 0x02, 0x03], 0)]
8408    #[case(vec![],                           vec![0x01],       -1)]
8409    fn test_bytes_rindex(#[case] haystack: Vec<u8>, #[case] needle: Vec<u8>, #[case] expected: i64) {
8410        assert_eq!(
8411            call(
8412                "rindex",
8413                vec![RuntimeValue::Bytes(haystack), RuntimeValue::Bytes(needle)]
8414            ),
8415            Ok(RuntimeValue::Number(expected.into()))
8416        );
8417    }
8418
8419    #[test]
8420    fn test_bytes_index_rindex_agree_single_occurrence() {
8421        let h = vec![0xaa, 0xbb, 0xcc];
8422        let n = vec![0xbb];
8423        let idx = call(
8424            "index",
8425            vec![RuntimeValue::Bytes(h.clone()), RuntimeValue::Bytes(n.clone())],
8426        )
8427        .unwrap();
8428        let ridx = call("rindex", vec![RuntimeValue::Bytes(h), RuntimeValue::Bytes(n)]).unwrap();
8429        assert_eq!(idx, ridx);
8430    }
8431
8432    // =========================================================================
8433    // repeat for bytes
8434    // =========================================================================
8435
8436    #[rstest]
8437    #[case(vec![0x01, 0x02], 0, vec![])]
8438    #[case(vec![0x01, 0x02], 1, vec![0x01, 0x02])]
8439    #[case(vec![0x01, 0x02], 3, vec![0x01, 0x02, 0x01, 0x02, 0x01, 0x02])]
8440    #[case(vec![0xff],       4, vec![0xff, 0xff, 0xff, 0xff])]
8441    #[case(vec![],           5, vec![])]
8442    fn test_bytes_repeat(#[case] input: Vec<u8>, #[case] n: u32, #[case] expected: Vec<u8>) {
8443        assert_eq!(
8444            call(
8445                "repeat",
8446                vec![RuntimeValue::Bytes(input), RuntimeValue::Number((n as f64).into())]
8447            ),
8448            Ok(RuntimeValue::Bytes(expected))
8449        );
8450    }
8451
8452    // =========================================================================
8453    // pack
8454    // =========================================================================
8455
8456    #[rstest]
8457    #[case("u8",    0.0,    vec![0x00])]
8458    #[case("u8",    255.0,  vec![0xff])]
8459    #[case("i8",    -1.0,   vec![0xff])]
8460    #[case("i8",    -128.0, vec![0x80])]
8461    #[case("i8",    127.0,  vec![0x7f])]
8462    #[case("u16be", 256.0,  vec![0x01, 0x00])]
8463    #[case("u16le", 256.0,  vec![0x00, 0x01])]
8464    #[case("i16be", -1.0,   vec![0xff, 0xff])]
8465    #[case("i16le", -1.0,   vec![0xff, 0xff])]
8466    #[case("u32be", 1.0,    vec![0x00, 0x00, 0x00, 0x01])]
8467    #[case("u32le", 1.0,    vec![0x01, 0x00, 0x00, 0x00])]
8468    #[case("i32be", -1.0,   vec![0xff, 0xff, 0xff, 0xff])]
8469    #[case("i32le", -1.0,   vec![0xff, 0xff, 0xff, 0xff])]
8470    #[case("u64be", 1.0,    vec![0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01])]
8471    #[case("u64le", 1.0,    vec![0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])]
8472    #[case("i64be", -1.0,   vec![0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff])]
8473    #[case("f32be", 1.0,    vec![0x3f, 0x80, 0x00, 0x00])]
8474    #[case("f32le", 1.0,    vec![0x00, 0x00, 0x80, 0x3f])]
8475    #[case("f64be", 1.0,    vec![0x3f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])]
8476    #[case("f64le", 1.0,    vec![0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x3f])]
8477    fn test_pack(#[case] fmt: &str, #[case] value: f64, #[case] expected: Vec<u8>) {
8478        assert_eq!(
8479            call(
8480                "pack",
8481                vec![RuntimeValue::String(fmt.into()), RuntimeValue::Number(value.into())]
8482            ),
8483            Ok(RuntimeValue::Bytes(expected))
8484        );
8485    }
8486
8487    #[rstest]
8488    #[case("z99")]
8489    #[case("u16")]
8490    #[case("")]
8491    fn test_pack_unknown_format(#[case] fmt: &str) {
8492        assert!(
8493            call(
8494                "pack",
8495                vec![RuntimeValue::String(fmt.into()), RuntimeValue::Number(0.0.into())]
8496            )
8497            .is_err()
8498        );
8499    }
8500
8501    #[test]
8502    fn test_pack_type_error() {
8503        assert!(
8504            call(
8505                "pack",
8506                vec![RuntimeValue::Number(1.into()), RuntimeValue::Number(0.0.into())]
8507            )
8508            .is_err()
8509        );
8510    }
8511
8512    // =========================================================================
8513    // unpack
8514    // =========================================================================
8515
8516    #[rstest]
8517    #[case("u8",    vec![0x2a],                                               42.0)]
8518    #[case("i8",    vec![0xff],                                               -1.0)]
8519    #[case("u16be", vec![0x01, 0x00],                                         256.0)]
8520    #[case("u16le", vec![0x00, 0x01],                                         256.0)]
8521    #[case("i16be", vec![0xff, 0xff],                                         -1.0)]
8522    #[case("i16le", vec![0xff, 0xff],                                         -1.0)]
8523    #[case("u32be", vec![0x00, 0x00, 0x00, 0x01],                             1.0)]
8524    #[case("u32le", vec![0x01, 0x00, 0x00, 0x00],                             1.0)]
8525    #[case("i32be", vec![0xff, 0xff, 0xff, 0xff],                             -1.0)]
8526    #[case("i32le", vec![0xff, 0xff, 0xff, 0xff],                             -1.0)]
8527    #[case("u64be", vec![0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01],    1.0)]
8528    #[case("u64le", vec![0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],    1.0)]
8529    #[case("i64be", vec![0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff],    -1.0)]
8530    #[case("f32be", vec![0x3f, 0x80, 0x00, 0x00],                             1.0)]
8531    #[case("f32le", vec![0x00, 0x00, 0x80, 0x3f],                             1.0)]
8532    #[case("f64be", vec![0x3f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],    1.0)]
8533    #[case("f64le", vec![0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x3f],    1.0)]
8534    fn test_unpack(#[case] fmt: &str, #[case] bytes: Vec<u8>, #[case] expected: f64) {
8535        assert_eq!(
8536            call(
8537                "unpack",
8538                vec![RuntimeValue::String(fmt.into()), RuntimeValue::Bytes(bytes)]
8539            ),
8540            Ok(RuntimeValue::Number(expected.into()))
8541        );
8542    }
8543
8544    #[rstest]
8545    #[case("u8",    vec![])]
8546    #[case("u16be", vec![0x00])]
8547    #[case("u32be", vec![0x00, 0x00, 0x00])]
8548    #[case("u64be", vec![0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])]
8549    #[case("f32be", vec![0x00, 0x00, 0x00])]
8550    #[case("f64be", vec![0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])]
8551    fn test_unpack_too_short(#[case] fmt: &str, #[case] bytes: Vec<u8>) {
8552        assert!(
8553            call(
8554                "unpack",
8555                vec![RuntimeValue::String(fmt.into()), RuntimeValue::Bytes(bytes)]
8556            )
8557            .is_err()
8558        );
8559    }
8560
8561    #[rstest]
8562    #[case("z99")]
8563    #[case("")]
8564    fn test_unpack_unknown_format(#[case] fmt: &str) {
8565        assert!(
8566            call(
8567                "unpack",
8568                vec![RuntimeValue::String(fmt.into()), RuntimeValue::Bytes(vec![0x00])]
8569            )
8570            .is_err()
8571        );
8572    }
8573
8574    #[test]
8575    fn test_unpack_type_error() {
8576        assert!(
8577            call(
8578                "unpack",
8579                vec![RuntimeValue::Number(1.into()), RuntimeValue::Bytes(vec![0x00])]
8580            )
8581            .is_err()
8582        );
8583    }
8584
8585    #[rstest]
8586    #[case("u8", 42.0)]
8587    #[case("i8",    -5.0)]
8588    #[case("u16be", 1234.0)]
8589    #[case("u16le", 1234.0)]
8590    #[case("i16be", -1000.0)]
8591    #[case("i16le", -1000.0)]
8592    #[case("u32be", 100000.0)]
8593    #[case("u32le", 100000.0)]
8594    #[case("i32be", -100000.0)]
8595    #[case("i32le", -100000.0)]
8596    #[case("u64be", 1000000.0)]
8597    #[case("u64le", 1000000.0)]
8598    #[case("i64be", -1000000.0)]
8599    #[case("i64le", -1000000.0)]
8600    #[case("f32be", 1.5)]
8601    #[case("f32le", 1.5)]
8602    #[case("f64be", 1.23456789)]
8603    #[case("f64le", 1.23456789)]
8604    fn test_pack_unpack_roundtrip(#[case] fmt: &str, #[case] value: f64) {
8605        let packed = call(
8606            "pack",
8607            vec![RuntimeValue::String(fmt.into()), RuntimeValue::Number(value.into())],
8608        )
8609        .unwrap();
8610        let result = call("unpack", vec![RuntimeValue::String(fmt.into()), packed]).unwrap();
8611        match result {
8612            RuntimeValue::Number(n) => assert!((n.value() - value).abs() < 1e-5),
8613            _ => panic!("expected Number"),
8614        }
8615    }
8616
8617    #[cfg(feature = "file-io")]
8618    #[test]
8619    fn test_file_exists_with_existing_file() {
8620        use std::io::Write;
8621        let mut tmp = tempfile::NamedTempFile::new().expect("failed to create temp file");
8622        tmp.write_all(b"hello").expect("failed to write");
8623        let path = tmp.path().to_string_lossy().to_string();
8624        assert_eq!(
8625            call("file_exists", vec![RuntimeValue::String(path)]),
8626            Ok(RuntimeValue::Boolean(true))
8627        );
8628    }
8629
8630    #[cfg(feature = "file-io")]
8631    #[test]
8632    fn test_file_exists_with_nonexistent_file() {
8633        assert_eq!(
8634            call(
8635                "file_exists",
8636                vec![RuntimeValue::String("/nonexistent/path/no_such_file.md".into())]
8637            ),
8638            Ok(RuntimeValue::Boolean(false))
8639        );
8640    }
8641
8642    #[cfg(feature = "file-io")]
8643    #[test]
8644    fn test_file_exists_invalid_type() {
8645        let result = call("file_exists", vec![RuntimeValue::Number(42.into())]);
8646        assert!(result.is_err());
8647    }
8648}