1#![no_std]
13#![forbid(unsafe_code)]
14#![warn(missing_docs)]
15
16extern crate alloc;
17#[cfg(feature = "std")]
18extern crate std;
19
20#[cfg(feature = "math")]
21mod math;
22#[cfg(feature = "regex")]
23mod regex;
24#[cfg(feature = "time")]
25mod time;
26
27use alloc::string::{String, ToString};
28use alloc::{borrow::ToOwned, boxed::Box, vec::Vec};
29use jaq_core::box_iter::{box_once, then, BoxIter};
30use jaq_core::{load, Bind, Cv, Error, Exn, FilterT, Native, RunPtr, UpdatePtr, ValR, ValX, ValXs};
31
32pub fn defs() -> impl Iterator<Item = load::parse::Def<&'static str>> {
34 load::parse(include_str!("defs.jq"), |p| p.defs())
35 .unwrap()
36 .into_iter()
37}
38
39pub type Filter<F> = (&'static str, Box<[Bind]>, F);
41
42#[cfg(all(
49 feature = "std",
50 feature = "format",
51 feature = "log",
52 feature = "math",
53 feature = "regex",
54 feature = "time",
55))]
56pub fn funs<V: ValT>() -> impl Iterator<Item = Filter<Native<V>>> {
57 base_funs().chain(extra_funs())
58}
59
60pub fn base_funs<V: ValT>() -> impl Iterator<Item = Filter<Native<V>>> {
67 let base_run = base_run().into_vec().into_iter().map(run);
68 base_run.chain([upd(error())])
69}
70
71#[cfg(all(
73 feature = "std",
74 feature = "format",
75 feature = "log",
76 feature = "math",
77 feature = "regex",
78 feature = "time",
79))]
80pub fn extra_funs<V: ValT>() -> impl Iterator<Item = Filter<Native<V>>> {
81 [std(), format(), math(), regex(), time()]
82 .into_iter()
83 .flat_map(|fs| fs.into_vec().into_iter().map(run))
84 .chain([debug(), stderr()].map(upd))
85}
86
87pub trait ValT: jaq_core::ValT + Ord + From<f64> {
89 fn into_seq<S: FromIterator<Self>>(self) -> Result<S, Self>;
93
94 fn as_isize(&self) -> Option<isize>;
96
97 fn as_f64(&self) -> Result<f64, Error<Self>>;
103}
104
105trait ValTx: ValT + Sized {
107 fn into_vec(self) -> Result<Vec<Self>, Error<Self>> {
108 self.into_seq().map_err(|v| Error::typ(v, "array"))
109 }
110
111 fn try_as_str(&self) -> Result<&str, Error<Self>> {
112 self.as_str()
113 .ok_or_else(|| Error::typ(self.clone(), "string"))
114 }
115
116 fn try_as_isize(&self) -> Result<isize, Error<Self>> {
117 self.as_isize()
118 .ok_or_else(|| Error::typ(self.clone(), "integer"))
119 }
120
121 #[cfg(feature = "math")]
122 fn try_as_i32(&self) -> Result<i32, Error<Self>> {
124 self.try_as_isize()?.try_into().map_err(Error::str)
125 }
126
127 fn mutate_arr(self, f: impl FnOnce(&mut Vec<Self>)) -> ValR<Self> {
129 let mut a = self.into_vec()?;
130 f(&mut a);
131 Ok(Self::from_iter(a))
132 }
133
134 fn try_mutate_arr<'a, F>(self, f: F) -> ValX<'a, Self>
136 where
137 F: FnOnce(&mut Vec<Self>) -> Result<(), Exn<'a, Self>>,
138 {
139 let mut a = self.into_vec()?;
140 f(&mut a)?;
141 Ok(Self::from_iter(a))
142 }
143
144 fn mutate_str(self, f: impl FnOnce(&mut str)) -> ValR<Self> {
145 let mut s = self.try_as_str()?.to_owned();
146 f(&mut s);
147 Ok(Self::from(s))
148 }
149
150 fn round(self, f: impl FnOnce(f64) -> f64) -> ValR<Self> {
151 if self.as_isize().is_some() {
152 Ok(self)
153 } else {
154 Ok(Self::from(f(self.as_f64()?) as isize))
155 }
156 }
157
158 fn trim_with(self, f: impl FnOnce(&str) -> &str) -> ValR<Self> {
159 let s = self.try_as_str()?;
160 let t = f(s);
161 Ok(if core::ptr::eq(s, t) {
162 self
164 } else {
165 t.to_string().into()
166 })
167 }
168}
169impl<T: ValT> ValTx for T {}
170
171pub fn run<V>((name, arity, run): Filter<RunPtr<V>>) -> Filter<Native<V>> {
173 (name, arity, Native::new(run))
174}
175
176fn upd<V>((name, arity, (run, update)): Filter<(RunPtr<V>, UpdatePtr<V>)>) -> Filter<Native<V>> {
178 (name, arity, Native::new(run).with_update(update))
179}
180
181fn sort_by<'a, V: ValT>(xs: &mut [V], f: impl Fn(V) -> ValXs<'a, V>) -> Result<(), Exn<'a, V>> {
183 let mut err = None;
185 xs.sort_by_cached_key(|x| {
186 if err.is_some() {
187 return Vec::new();
188 };
189 match f(x.clone()).collect() {
190 Ok(y) => y,
191 Err(e) => {
192 err = Some(e);
193 Vec::new()
194 }
195 }
196 });
197 err.map_or(Ok(()), Err)
198}
199
200fn group_by<'a, V: ValT>(xs: Vec<V>, f: impl Fn(V) -> ValXs<'a, V>) -> ValX<'a, V> {
202 let mut yx: Vec<(Vec<V>, V)> = xs
203 .into_iter()
204 .map(|x| Ok((f(x.clone()).collect::<Result<_, _>>()?, x)))
205 .collect::<Result<_, Exn<_>>>()?;
206
207 yx.sort_by(|(y1, _), (y2, _)| y1.cmp(y2));
208
209 let mut grouped = Vec::new();
210 let mut yx = yx.into_iter();
211 if let Some((mut group_y, first_x)) = yx.next() {
212 let mut group = Vec::from([first_x]);
213 for (y, x) in yx {
214 if group_y != y {
215 grouped.push(V::from_iter(core::mem::take(&mut group)));
216 group_y = y;
217 }
218 group.push(x);
219 }
220 if !group.is_empty() {
221 grouped.push(V::from_iter(group));
222 }
223 }
224
225 Ok(V::from_iter(grouped))
226}
227
228fn cmp_by<'a, V: Clone, F, R>(xs: Vec<V>, f: F, replace: R) -> Result<Option<V>, Exn<'a, V>>
230where
231 F: Fn(V) -> ValXs<'a, V>,
232 R: Fn(&[V], &[V]) -> bool,
233{
234 let iter = xs.into_iter();
235 let mut iter = iter.map(|x| (x.clone(), f(x).collect::<Result<Vec<_>, _>>()));
236 let (mut mx, mut my) = if let Some((x, y)) = iter.next() {
237 (x, y?)
238 } else {
239 return Ok(None);
240 };
241 for (x, y) in iter {
242 let y = y?;
243 if replace(&my, &y) {
244 (mx, my) = (x, y);
245 }
246 }
247 Ok(Some(mx))
248}
249
250fn explode<V: ValT>(s: &str) -> impl Iterator<Item = ValR<V>> + '_ {
252 let conv = |c: char| Ok(isize::try_from(c as u32).map_err(Error::str)?.into());
254 s.chars().map(conv)
255}
256
257fn implode<V: ValT>(xs: &[V]) -> Result<String, Error<V>> {
259 xs.iter().map(as_codepoint).collect()
260}
261
262fn as_codepoint<V: ValT>(v: &V) -> Result<char, Error<V>> {
264 let i = v.try_as_isize()?;
265 let u = u32::try_from(i).map_err(Error::str)?;
267 char::from_u32(u).ok_or_else(|| Error::str(format_args!("cannot use {u} as character")))
269}
270
271fn range<V: ValT>(mut from: ValX<V>, to: V, by: V) -> impl Iterator<Item = ValX<V>> {
280 use core::cmp::Ordering::{Equal, Greater, Less};
281 let cmp = by.partial_cmp(&V::from(0)).unwrap_or(Equal);
282 core::iter::from_fn(move || match from.clone() {
283 Ok(x) => match cmp {
284 Greater => x < to,
285 Less => x > to,
286 Equal => x != to,
287 }
288 .then(|| core::mem::replace(&mut from, (x + by.clone()).map_err(Exn::from))),
289 e @ Err(_) => {
290 from = Ok(to.clone());
292 Some(e)
293 }
294 })
295}
296
297fn once_or_empty<'a, T: 'a, E: 'a>(r: Result<Option<T>, E>) -> BoxIter<'a, Result<T, E>> {
298 Box::new(r.transpose().into_iter())
299}
300
301fn bome<'a, V: 'a>(r: ValR<V>) -> ValXs<'a, V> {
303 box_once(r.map_err(Exn::from))
304}
305
306pub fn unary<'a, V: Clone>(mut cv: Cv<'a, V>, f: impl Fn(V, V) -> ValR<V> + 'a) -> ValXs<'a, V> {
309 bome(f(cv.1, cv.0.pop_var()))
310}
311
312pub fn v(n: usize) -> Box<[Bind]> {
314 core::iter::repeat(Bind::Var(())).take(n).collect()
315}
316
317#[allow(clippy::unit_arg)]
318fn base_run<V: ValT, F: FilterT<V = V>>() -> Box<[Filter<RunPtr<V, F>>]> {
319 let f = || [Bind::Fun(())].into();
320 let vf = [Bind::Var(()), Bind::Fun(())].into();
321 Box::new([
322 ("inputs", v(0), |_, cv| {
323 Box::new(
324 cv.0.inputs()
325 .map(|r| r.map_err(|e| Exn::from(Error::str(e)))),
326 )
327 }),
328 ("floor", v(0), |_, cv| bome(cv.1.round(f64::floor))),
329 ("round", v(0), |_, cv| bome(cv.1.round(f64::round))),
330 ("ceil", v(0), |_, cv| bome(cv.1.round(f64::ceil))),
331 ("utf8bytelength", v(0), |_, cv| {
332 bome(cv.1.try_as_str().map(|s| (s.len() as isize).into()))
333 }),
334 ("explode", v(0), |_, cv| {
335 bome(cv.1.try_as_str().and_then(|s| explode(s).collect()))
336 }),
337 ("implode", v(0), |_, cv| {
338 bome(cv.1.into_vec().and_then(|s| implode(&s)).map(V::from))
339 }),
340 ("ascii_downcase", v(0), |_, cv| {
341 bome(cv.1.mutate_str(str::make_ascii_lowercase))
342 }),
343 ("ascii_upcase", v(0), |_, cv| {
344 bome(cv.1.mutate_str(str::make_ascii_uppercase))
345 }),
346 ("reverse", v(0), |_, cv| {
347 bome(cv.1.mutate_arr(|a| a.reverse()))
348 }),
349 ("sort", v(0), |_, cv| bome(cv.1.mutate_arr(|a| a.sort()))),
350 ("sort_by", f(), |lut, mut cv| {
351 let (f, fc) = cv.0.pop_fun();
352 let f = move |v| f.run(lut, (fc.clone(), v));
353 box_once(cv.1.try_mutate_arr(|a| sort_by(a, f)))
354 }),
355 ("group_by", f(), |lut, mut cv| {
356 let (f, fc) = cv.0.pop_fun();
357 let f = move |v| f.run(lut, (fc.clone(), v));
358 box_once((|| group_by(cv.1.into_vec()?, f))())
359 }),
360 ("min_by_or_empty", f(), |lut, mut cv| {
361 let (f, fc) = cv.0.pop_fun();
362 let f = move |a| cmp_by(a, |v| f.run(lut, (fc.clone(), v)), |my, y| y < my);
363 once_or_empty(cv.1.into_vec().map_err(Exn::from).and_then(f))
364 }),
365 ("max_by_or_empty", f(), |lut, mut cv| {
366 let (f, fc) = cv.0.pop_fun();
367 let f = move |a| cmp_by(a, |v| f.run(lut, (fc.clone(), v)), |my, y| y >= my);
368 once_or_empty(cv.1.into_vec().map_err(Exn::from).and_then(f))
369 }),
370 ("first", f(), |lut, mut cv| {
371 let (f, fc) = cv.0.pop_fun();
372 Box::new(f.run(lut, (fc, cv.1)).next().into_iter())
373 }),
374 ("last", f(), |lut, mut cv| {
375 let (f, fc) = cv.0.pop_fun();
376 once_or_empty(f.run(lut, (fc, cv.1)).try_fold(None, |_acc, x| x.map(Some)))
377 }),
378 ("limit", vf, |lut, mut cv| {
379 let (f, fc) = cv.0.pop_fun();
380 let n = cv.0.pop_var();
381 let pos = |n: isize| n.try_into().unwrap_or(0usize);
382 then(n.try_as_isize().map_err(Exn::from), |n| match pos(n) {
383 0 => Box::new(core::iter::empty()),
384 n => Box::new(f.run(lut, (fc, cv.1)).take(n)),
385 })
386 }),
387 ("range", v(3), |_, mut cv| {
388 let by = cv.0.pop_var();
389 let to = cv.0.pop_var();
390 let from = cv.0.pop_var();
391 Box::new(range(Ok(from), to, by))
392 }),
393 ("startswith", v(1), |_, cv| {
394 unary(cv, |v, s| {
395 Ok(v.try_as_str()?.starts_with(s.try_as_str()?).into())
396 })
397 }),
398 ("endswith", v(1), |_, cv| {
399 unary(cv, |v, s| {
400 Ok(v.try_as_str()?.ends_with(s.try_as_str()?).into())
401 })
402 }),
403 ("ltrimstr", v(1), |_, cv| {
404 unary(cv, |v, pre| {
405 Ok(v.try_as_str()?
406 .strip_prefix(pre.try_as_str()?)
407 .map_or_else(|| v.clone(), |s| V::from(s.to_owned())))
408 })
409 }),
410 ("rtrimstr", v(1), |_, cv| {
411 unary(cv, |v, suf| {
412 Ok(v.try_as_str()?
413 .strip_suffix(suf.try_as_str()?)
414 .map_or_else(|| v.clone(), |s| V::from(s.to_owned())))
415 })
416 }),
417 ("trim", v(0), |_, cv| bome(cv.1.trim_with(str::trim))),
418 ("ltrim", v(0), |_, cv| bome(cv.1.trim_with(str::trim_start))),
419 ("rtrim", v(0), |_, cv| bome(cv.1.trim_with(str::trim_end))),
420 ("escape_csv", v(0), |_, cv| {
421 bome(cv.1.try_as_str().map(|s| s.replace('"', "\"\"").into()))
422 }),
423 ("escape_sh", v(0), |_, cv| {
424 bome(cv.1.try_as_str().map(|s| s.replace('\'', r"'\''").into()))
425 }),
426 ])
427}
428
429#[cfg(feature = "std")]
430fn now<V: From<String>>() -> Result<f64, Error<V>> {
431 use std::time::{SystemTime, UNIX_EPOCH};
432 SystemTime::now()
433 .duration_since(UNIX_EPOCH)
434 .map(|x| x.as_secs_f64())
435 .map_err(Error::str)
436}
437
438#[cfg(feature = "std")]
439fn std<V: ValT>() -> Box<[Filter<RunPtr<V>>]> {
440 use std::env::vars;
441 Box::new([
442 ("env", v(0), |_, _| {
443 bome(V::from_map(vars().map(|(k, v)| (V::from(k), V::from(v)))))
444 }),
445 ("now", v(0), |_, _| bome(now().map(V::from))),
446 ("halt", v(0), |_, _| std::process::exit(0)),
447 ("halt_error", v(1), |_, mut cv| {
448 bome(cv.0.pop_var().try_as_isize().map(|exit_code| {
449 if let Some(s) = cv.1.as_str() {
450 std::print!("{}", s);
451 } else {
452 std::println!("{}", cv.1);
453 }
454 std::process::exit(exit_code as i32)
455 }))
456 }),
457 ])
458}
459
460#[cfg(feature = "format")]
461fn replace(s: &str, patterns: &[&str], replacements: &[&str]) -> String {
462 let ac = aho_corasick::AhoCorasick::new(patterns).unwrap();
463 ac.replace_all(s, replacements)
464}
465
466#[cfg(feature = "format")]
467fn format<V: ValT>() -> Box<[Filter<RunPtr<V>>]> {
468 Box::new([
469 ("escape_html", v(0), |_, cv| {
470 let pats = ["<", ">", "&", "\'", "\""];
471 let reps = ["<", ">", "&", "'", """];
472 bome(cv.1.try_as_str().map(|s| replace(s, &pats, &reps).into()))
473 }),
474 ("escape_tsv", v(0), |_, cv| {
475 let pats = ["\n", "\r", "\t", "\\", "\0"];
476 let reps = ["\\n", "\\r", "\\t", "\\\\", "\\0"];
477 bome(cv.1.try_as_str().map(|s| replace(s, &pats, &reps).into()))
478 }),
479 ("encode_uri", v(0), |_, cv| {
480 use urlencoding::encode;
481 bome(cv.1.try_as_str().map(|s| encode(s).into_owned().into()))
482 }),
483 ("decode_uri", v(0), |_, cv| {
484 use urlencoding::decode;
485 bome(cv.1.try_as_str().and_then(|s| {
486 let d = decode(s).map_err(Error::str)?;
487 Ok(d.into_owned().into())
488 }))
489 }),
490 ("encode_base64", v(0), |_, cv| {
491 use base64::{engine::general_purpose::STANDARD, Engine};
492 bome(cv.1.try_as_str().map(|s| STANDARD.encode(s).into()))
493 }),
494 ("decode_base64", v(0), |_, cv| {
495 use base64::{engine::general_purpose::STANDARD, Engine};
496 use core::str::from_utf8;
497 bome(cv.1.try_as_str().and_then(|s| {
498 let d = STANDARD.decode(s).map_err(Error::str)?;
499 Ok(from_utf8(&d).map_err(Error::str)?.to_owned().into())
500 }))
501 }),
502 ])
503}
504
505#[cfg(feature = "math")]
506fn math<V: ValT>() -> Box<[Filter<RunPtr<V>>]> {
507 let rename = |name, (_name, arity, f): Filter<RunPtr<V>>| (name, arity, f);
508 Box::new([
509 math::f_f!(acos),
510 math::f_f!(acosh),
511 math::f_f!(asin),
512 math::f_f!(asinh),
513 math::f_f!(atan),
514 math::f_f!(atanh),
515 math::f_f!(cbrt),
516 math::f_f!(cos),
517 math::f_f!(cosh),
518 math::f_f!(erf),
519 math::f_f!(erfc),
520 math::f_f!(exp),
521 math::f_f!(exp10),
522 math::f_f!(exp2),
523 math::f_f!(expm1),
524 math::f_f!(fabs),
525 math::f_fi!(frexp),
526 math::f_i!(ilogb),
527 math::f_f!(j0),
528 math::f_f!(j1),
529 math::f_f!(lgamma),
530 math::f_f!(log),
531 math::f_f!(log10),
532 math::f_f!(log1p),
533 math::f_f!(log2),
534 math::f_ff!(modf),
536 rename("nearbyint", math::f_f!(round)),
537 math::f_f!(rint),
539 math::f_f!(sin),
541 math::f_f!(sinh),
542 math::f_f!(sqrt),
543 math::f_f!(tan),
544 math::f_f!(tanh),
545 math::f_f!(tgamma),
546 math::f_f!(trunc),
547 math::f_f!(y0),
548 math::f_f!(y1),
549 math::ff_f!(atan2),
550 math::ff_f!(copysign),
551 math::ff_f!(fdim),
553 math::ff_f!(fmax),
554 math::ff_f!(fmin),
555 math::ff_f!(fmod),
556 math::ff_f!(hypot),
557 math::if_f!(jn),
558 math::fi_f!(ldexp),
559 math::ff_f!(nextafter),
560 math::ff_f!(pow),
562 math::ff_f!(remainder),
563 rename("scalbln", math::fi_f!(scalbn)),
565 math::if_f!(yn),
566 math::fff_f!(fma),
567 ])
568}
569
570#[cfg(feature = "regex")]
571fn re<V: ValT>(s: bool, m: bool, mut cv: Cv<V>) -> ValR<V> {
572 let flags = cv.0.pop_var();
573 let re = cv.0.pop_var();
574
575 use crate::regex::Part::{Matches, Mismatch};
576 let fail_flag = |e| Error::str(format_args!("invalid regex flag: {e}"));
577 let fail_re = |e| Error::str(format_args!("invalid regex: {e}"));
578
579 let flags = regex::Flags::new(flags.try_as_str()?).map_err(fail_flag)?;
580 let re = flags.regex(re.try_as_str()?).map_err(fail_re)?;
581 let out = regex::regex(cv.1.try_as_str()?, &re, flags, (s, m));
582 let out = out.into_iter().map(|out| match out {
583 Matches(ms) => ms.into_iter().map(|m| V::from_map(m.fields())).collect(),
584 Mismatch(s) => Ok(V::from(s.to_string())),
585 });
586 out.collect()
587}
588
589#[cfg(feature = "regex")]
590fn regex<V: ValT>() -> Box<[Filter<RunPtr<V>>]> {
591 let vv = || [Bind::Var(()), Bind::Var(())].into();
592 Box::new([
593 ("matches", vv(), |_, cv| bome(re(false, true, cv))),
594 ("split_matches", vv(), |_, cv| bome(re(true, true, cv))),
595 ("split_", vv(), |_, cv| bome(re(true, false, cv))),
596 ])
597}
598
599#[cfg(feature = "time")]
600fn time<V: ValT>() -> Box<[Filter<RunPtr<V>>]> {
601 use chrono::{Local, Utc};
602 Box::new([
603 ("fromdateiso8601", v(0), |_, cv| {
604 bome(cv.1.try_as_str().and_then(time::from_iso8601))
605 }),
606 ("todateiso8601", v(0), |_, cv| {
607 bome(time::to_iso8601(&cv.1).map(V::from))
608 }),
609 ("strftime", v(1), |_, cv| {
610 unary(cv, |v, fmt| time::strftime(&v, fmt.try_as_str()?, Utc))
611 }),
612 ("strflocaltime", v(1), |_, cv| {
613 unary(cv, |v, fmt| time::strftime(&v, fmt.try_as_str()?, Local))
614 }),
615 ("gmtime", v(0), |_, cv| bome(time::gmtime(&cv.1, Utc))),
616 ("localtime", v(0), |_, cv| bome(time::gmtime(&cv.1, Local))),
617 ("strptime", v(1), |_, cv| {
618 unary(cv, |v, fmt| {
619 time::strptime(v.try_as_str()?, fmt.try_as_str()?)
620 })
621 }),
622 ("mktime", v(0), |_, cv| bome(time::mktime(&cv.1))),
623 ])
624}
625
626fn error<V, F>() -> Filter<(RunPtr<V, F>, UpdatePtr<V, F>)> {
627 fn err<V>(cv: Cv<V>) -> ValXs<V> {
628 bome(Err(Error::new(cv.1)))
629 }
630 ("error", v(0), (|_, cv| err(cv), |_, cv, _| err(cv)))
631}
632
633#[cfg(feature = "log")]
634macro_rules! id_with {
636 ( $eff:expr ) => {
637 (
638 |_, cv| {
639 $eff(&cv.1);
640 box_once(Ok(cv.1))
641 },
642 |_, cv, f| {
643 $eff(&cv.1);
644 f(cv.1)
645 },
646 )
647 };
648}
649
650#[cfg(feature = "log")]
651fn debug<V: core::fmt::Display>() -> Filter<(RunPtr<V>, UpdatePtr<V>)> {
652 ("debug", v(0), id_with!(|x| log::debug!("{}", x)))
653}
654
655#[cfg(feature = "log")]
656fn stderr<V: ValT>() -> Filter<(RunPtr<V>, UpdatePtr<V>)> {
657 fn eprint_raw<V: ValT>(v: &V) {
658 if let Some(s) = v.as_str() {
659 log::error!("{}", s)
660 } else {
661 log::error!("{}", v)
662 }
663 }
664 ("stderr", v(0), id_with!(eprint_raw))
665}