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
37pub(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
254fn 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#[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#[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#[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#[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#[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#[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 [RuntimeValue::Number(end)] => {
1372 let end_val = end.value() as isize;
1373 generate_numeric_range(0, end_val, 1).map(RuntimeValue::Array)
1374 }
1375 [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 [
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 [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 [
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 repeat(&mut RuntimeValue::Array(std::mem::take(array)), n.value() as usize)
1881 } else {
1882 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 let mut new_array = if index >= array_val.len() {
2580 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 std::mem::take(array_val)
2588 };
2589
2590 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 [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 [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 [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#[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
5478pub 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, 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
5666fn 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 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()), RuntimeValue::Number(0.into()), RuntimeValue::Number(1.into()), RuntimeValue::Number(0.into()), RuntimeValue::Number(0.into()), RuntimeValue::Number(0.into()), RuntimeValue::Number(4.into()), RuntimeValue::Number(0.into()), ])
5824 );
5825 }
5826
5827 #[test]
5828 fn test_gmtime_known_date() {
5829 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()), RuntimeValue::Number(0.into()), RuntimeValue::Number(1.into()), RuntimeValue::Number(0.into()), RuntimeValue::Number(0.into()), RuntimeValue::Number(0.into()), RuntimeValue::Number(1.into()), RuntimeValue::Number(0.into()), ])
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 #[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 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 #[test]
5927 fn test_date_add_months_end_of_month() {
5928 let env = Shared::new(SharedCell::new(Env::default()));
5930 let arr = gmtime_array(1706659200); 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 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 let env = Shared::new(SharedCell::new(Env::default()));
5951 let arr = gmtime_array(1709164800); 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 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 #[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 #[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 "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 "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 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 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 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]; 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 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 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); 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 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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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}