Skip to main content

jaq_std/
lib.rs

1//! Standard library for the jq language.
2//!
3//! The standard library provides a set of filters.
4//! These filters are either implemented as definitions or as functions.
5//! For example, the standard library provides the `map(f)` filter,
6//! which is defined using the more elementary filter `[.[] | f]`.
7//!
8//! If you want to use the standard library in jaq, then
9//! you'll likely only need [`funs`] and [`defs`].
10//! Most other functions are relevant if you
11//! want to implement your own native filters.
12#![no_std]
13#![forbid(unsafe_code)]
14#![warn(missing_docs)]
15
16extern crate alloc;
17#[cfg(feature = "std")]
18extern crate std;
19
20pub mod input;
21#[cfg(feature = "math")]
22mod math;
23#[cfg(feature = "regex")]
24mod regex;
25#[cfg(feature = "time")]
26mod time;
27
28#[cfg(feature = "std")]
29use alloc::string::String;
30#[cfg(feature = "format")]
31use alloc::string::ToString;
32use alloc::{boxed::Box, vec::Vec};
33#[cfg(feature = "log")]
34use bstr::BStr;
35use bstr::ByteSlice;
36use jaq_core::box_iter::{box_once, BoxIter};
37use jaq_core::native::{bome, run, unary, v, Filter, Fun};
38#[cfg(feature = "regex")]
39use jaq_core::Cv;
40#[cfg(any(feature = "regex", feature = "std"))]
41use jaq_core::ValT as _;
42use jaq_core::{load, Bind, DataT, Error, Exn, RunPtr, ValR, ValX, ValXs};
43
44/// Definitions of the standard library.
45pub fn defs() -> impl Iterator<Item = load::parse::Def<&'static str>> {
46    load::parse(include_str!("defs.jq"), |p| p.defs())
47        .unwrap()
48        .into_iter()
49}
50
51/// Named filters available by default in jaq
52/// which are implemented as native filters, such as `length`, `keys`, ...,
53/// but also `now`, `debug`, `fromdateiso8601`, ...
54///
55/// This is the combination of [`base_funs`] and [`extra_funs`].
56/// It does not include filters implemented by definition, such as `map`.
57#[cfg(all(
58    feature = "std",
59    feature = "format",
60    feature = "log",
61    feature = "math",
62    feature = "regex",
63    feature = "time",
64))]
65pub fn funs<D: DataT>() -> impl Iterator<Item = Fun<D>>
66where
67    for<'a> D::V<'a>: ValT,
68{
69    base_funs().chain(extra_funs())
70}
71
72/// Minimal set of filters that are generic over the value type.
73/// Return the minimal set of named filters available in jaq
74/// which are implemented as native filters, such as `length`, `keys`, ...,
75/// but not `now`, `debug`, `fromdateiso8601`, ...
76///
77/// Does not return filters from the standard library, such as `map`.
78pub fn base_funs<D: DataT>() -> impl Iterator<Item = Fun<D>>
79where
80    for<'a> D::V<'a>: ValT,
81{
82    base_run().into_vec().into_iter().map(run)
83}
84
85/// Supplementary set of filters that are generic over the value type.
86#[cfg(all(
87    feature = "std",
88    feature = "format",
89    feature = "log",
90    feature = "math",
91    feature = "regex",
92    feature = "time",
93))]
94pub fn extra_funs<D: DataT>() -> impl Iterator<Item = Fun<D>>
95where
96    for<'a> D::V<'a>: ValT,
97{
98    [std(), format(), math(), regex(), time(), log()]
99        .into_iter()
100        .flat_map(|fs| fs.into_vec().into_iter().map(run))
101}
102
103/// Values that the standard library can operate on.
104pub trait ValT: jaq_core::ValT + Ord + From<f64> + From<usize> {
105    /// Convert an array into a sequence.
106    ///
107    /// This returns the original value as `Err` if it is not an array.
108    fn into_seq<S: FromIterator<Self>>(self) -> Result<S, Self>;
109
110    /// True if the value is integer.
111    fn is_int(&self) -> bool;
112
113    /// Use the value as machine-sized integer.
114    ///
115    /// If this function returns `Some(_)`, then [`Self::is_int`] must return true.
116    /// However, the other direction must not necessarily be the case, because
117    /// there may be integer values that are not representable by `isize`.
118    fn as_isize(&self) -> Option<isize>;
119
120    /// Use the value as floating-point number.
121    ///
122    /// This succeeds for all numeric values,
123    /// rounding too large/small ones to +/- Infinity.
124    fn as_f64(&self) -> Option<f64>;
125
126    /// True if the value is interpreted as UTF-8 string.
127    fn is_utf8_str(&self) -> bool;
128
129    /// If the value is a string (whatever its interpretation), return its bytes.
130    fn as_bytes(&self) -> Option<&[u8]>;
131
132    /// If the value is interpreted as UTF-8 string, return its bytes.
133    fn as_utf8_bytes(&self) -> Option<&[u8]> {
134        self.is_utf8_str().then(|| self.as_bytes()).flatten()
135    }
136
137    /// If the value is a string (whatever its interpretation), return its bytes, else fail.
138    fn try_as_bytes(&self) -> Result<&[u8], Error<Self>> {
139        self.as_bytes().ok_or_else(|| self.fail_str())
140    }
141
142    /// If the value is interpreted as UTF-8 string, return its bytes, else fail.
143    fn try_as_utf8_bytes(&self) -> Result<&[u8], Error<Self>> {
144        self.as_utf8_bytes().ok_or_else(|| self.fail_str())
145    }
146
147    /// If the value is a string and `sub` points to a slice of the string,
148    /// shorten the string to `sub`, else panic.
149    fn as_sub_str(&self, sub: &[u8]) -> Self;
150
151    /// Interpret bytes as UTF-8 string value.
152    fn from_utf8_bytes(b: impl AsRef<[u8]> + Send + 'static) -> Self;
153}
154
155/// Convenience trait for implementing the core functions.
156trait ValTx: ValT + Sized {
157    fn into_vec(self) -> Result<Vec<Self>, Error<Self>> {
158        self.into_seq().map_err(|v| Error::typ(v, "array"))
159    }
160
161    fn try_as_isize(&self) -> Result<isize, Error<Self>> {
162        self.as_isize()
163            .ok_or_else(|| Error::typ(self.clone(), "integer"))
164    }
165
166    fn try_as_i32(&self) -> Result<i32, Error<Self>> {
167        self.try_as_isize()?.try_into().map_err(Error::str)
168    }
169
170    fn try_as_f64(&self) -> Result<f64, Error<Self>> {
171        self.as_f64()
172            .ok_or_else(|| Error::typ(self.clone(), "number"))
173    }
174
175    /// Apply a function to an array.
176    fn mutate_arr(self, f: impl FnOnce(&mut Vec<Self>)) -> ValR<Self> {
177        let mut a = self.into_vec()?;
178        f(&mut a);
179        Ok(Self::from_iter(a))
180    }
181
182    /// Apply a function to an array.
183    fn try_mutate_arr<'a, F>(self, f: F) -> ValX<'a, Self>
184    where
185        F: FnOnce(&mut Vec<Self>) -> Result<(), Exn<'a, Self>>,
186    {
187        let mut a = self.into_vec()?;
188        f(&mut a)?;
189        Ok(Self::from_iter(a))
190    }
191
192    fn round(self, f: impl FnOnce(f64) -> f64) -> ValR<Self> {
193        Ok(if self.is_int() {
194            self
195        } else {
196            let f = f(self.try_as_f64()?);
197            if f.is_finite() {
198                if isize::MIN as f64 <= f && f <= isize::MAX as f64 {
199                    Self::from(f as isize)
200                } else {
201                    // print floating-point number without decimal places,
202                    // i.e. like an integer
203                    Self::from_num(&alloc::format!("{f:.0}"))?
204                }
205            } else {
206                Self::from(f)
207            }
208        })
209    }
210
211    /// If the value is interpreted as UTF-8 string,
212    /// return its `str` representation.
213    #[cfg(any(feature = "regex", feature = "time"))]
214    fn try_as_str(&self) -> Result<&str, Error<Self>> {
215        self.try_as_utf8_bytes()
216            .and_then(|s| core::str::from_utf8(s).map_err(Error::str))
217    }
218
219    fn map_utf8_str<B>(self, f: impl FnOnce(&[u8]) -> B) -> ValR<Self>
220    where
221        B: AsRef<[u8]> + Send + 'static,
222    {
223        Ok(Self::from_utf8_bytes(f(self.try_as_utf8_bytes()?)))
224    }
225
226    fn trim_utf8_with(&self, f: impl FnOnce(&[u8]) -> &[u8]) -> ValR<Self> {
227        Ok(self.as_sub_str(f(self.try_as_utf8_bytes()?)))
228    }
229
230    /// Helper function to strip away the prefix or suffix of a string.
231    fn strip_fix<F>(self, fix: &Self, f: F) -> Result<Self, Error<Self>>
232    where
233        F: for<'a> FnOnce(&'a [u8], &[u8]) -> Option<&'a [u8]>,
234    {
235        Ok(match f(self.try_as_bytes()?, fix.try_as_bytes()?) {
236            Some(sub) => self.as_sub_str(sub),
237            None => self,
238        })
239    }
240
241    fn fail_str(&self) -> Error<Self> {
242        Error::typ(self.clone(), "string")
243    }
244}
245impl<T: ValT> ValTx for T {}
246
247/// Sort array by the given function.
248fn sort_by<'a, V: ValT>(xs: &mut [V], f: impl Fn(V) -> ValXs<'a, V>) -> Result<(), Exn<'a, V>> {
249    // Some(e) iff an error has previously occurred
250    let mut err = None;
251    xs.sort_by_cached_key(|x| {
252        if err.is_some() {
253            return Vec::new();
254        };
255        match f(x.clone()).collect() {
256            Ok(y) => y,
257            Err(e) => {
258                err = Some(e);
259                Vec::new()
260            }
261        }
262    });
263    err.map_or(Ok(()), Err)
264}
265
266/// Group an array by the given function.
267fn group_by<'a, V: ValT>(xs: Vec<V>, f: impl Fn(V) -> ValXs<'a, V>) -> ValX<'a, V> {
268    let mut yx: Vec<(Vec<V>, V)> = xs
269        .into_iter()
270        .map(|x| Ok((f(x.clone()).collect::<Result<_, _>>()?, x)))
271        .collect::<Result<_, Exn<_>>>()?;
272
273    yx.sort_by(|(y1, _), (y2, _)| y1.cmp(y2));
274
275    let mut grouped = Vec::new();
276    let mut yx = yx.into_iter();
277    if let Some((mut group_y, first_x)) = yx.next() {
278        let mut group = Vec::from([first_x]);
279        for (y, x) in yx {
280            if group_y != y {
281                grouped.push(V::from_iter(core::mem::take(&mut group)));
282                group_y = y;
283            }
284            group.push(x);
285        }
286        if !group.is_empty() {
287            grouped.push(V::from_iter(group));
288        }
289    }
290
291    Ok(V::from_iter(grouped))
292}
293
294/// Get the minimum or maximum element from an array according to the given function.
295fn cmp_by<'a, V: Clone, F, R>(xs: Vec<V>, f: F, replace: R) -> Result<Option<V>, Exn<'a, V>>
296where
297    F: Fn(V) -> ValXs<'a, V>,
298    R: Fn(&[V], &[V]) -> bool,
299{
300    let iter = xs.into_iter();
301    let mut iter = iter.map(|x| (x.clone(), f(x).collect::<Result<Vec<_>, _>>()));
302    let (mut mx, mut my) = if let Some((x, y)) = iter.next() {
303        (x, y?)
304    } else {
305        return Ok(None);
306    };
307    for (x, y) in iter {
308        let y = y?;
309        if replace(&my, &y) {
310            (mx, my) = (x, y);
311        }
312    }
313    Ok(Some(mx))
314}
315
316/// Convert a string into an array of its Unicode codepoints (with negative integers representing UTF-8 errors).
317fn explode<V: ValT>(s: &[u8]) -> impl Iterator<Item = ValR<V>> + '_ {
318    let invalid = [].iter();
319    Explode { s, invalid }.map(|r| match r {
320        Err(b) => Ok((-(b as isize)).into()),
321        // conversion from u32 to isize may fail on 32-bit systems for high values of c
322        Ok(c) => Ok(isize::try_from(c as u32).map_err(Error::str)?.into()),
323    })
324}
325
326struct Explode<'a> {
327    s: &'a [u8],
328    invalid: core::slice::Iter<'a, u8>,
329}
330impl Iterator for Explode<'_> {
331    type Item = Result<char, u8>;
332    fn next(&mut self) -> Option<Self::Item> {
333        self.invalid.next().map(|next| Err(*next)).or_else(|| {
334            let (c, size) = bstr::decode_utf8(self.s);
335            let (consumed, rest) = self.s.split_at(size);
336            self.s = rest;
337            c.map(Ok).or_else(|| {
338                // invalid UTF-8 sequence, emit all invalid bytes
339                self.invalid = consumed.iter();
340                self.invalid.next().map(|next| Err(*next))
341            })
342        })
343    }
344    fn size_hint(&self) -> (usize, Option<usize>) {
345        let max = self.s.len();
346        let min = self.s.len() / 4;
347        let inv = self.invalid.as_slice().len();
348        (min + inv, Some(max + inv))
349    }
350}
351
352/// Convert an array of Unicode codepoints (with negative integers representing UTF-8 errors) into a string.
353fn implode<V: ValT>(xs: &[V]) -> Result<Vec<u8>, Error<V>> {
354    let mut v = Vec::with_capacity(xs.len());
355    for x in xs {
356        // on 32-bit systems, some high u32 values cannot be represented as isize
357        let i = x.try_as_isize()?;
358        if let Ok(b) = u8::try_from(-i) {
359            v.push(b)
360        } else {
361            // may fail e.g. on `[1114112] | implode`
362            let c = u32::try_from(i).ok().and_then(char::from_u32);
363            let c = c.ok_or_else(|| Error::str(format_args!("cannot use {i} as character")))?;
364            v.extend(c.encode_utf8(&mut [0; 4]).as_bytes())
365        }
366    }
367    Ok(v)
368}
369
370fn once_or_empty<'a, T: 'a, E: 'a>(r: Result<Option<T>, E>) -> BoxIter<'a, Result<T, E>> {
371    Box::new(r.transpose().into_iter())
372}
373
374// Primitive float rounding methods are unavailable without `std`.
375// These can be dropped after `core_float_math` lands: https://github.com/rust-lang/rust/issues/137578
376fn floor(x: f64) -> f64 {
377    #[cfg(feature = "std")]
378    return x.floor();
379    #[cfg(not(feature = "std"))]
380    return no_std_float::floor(x);
381}
382
383fn round(x: f64) -> f64 {
384    #[cfg(feature = "std")]
385    return x.round();
386    #[cfg(not(feature = "std"))]
387    return no_std_float::round(x);
388}
389
390fn ceil(x: f64) -> f64 {
391    #[cfg(feature = "std")]
392    return x.ceil();
393    #[cfg(not(feature = "std"))]
394    return no_std_float::ceil(x);
395}
396
397#[cfg(any(not(feature = "std"), test))]
398mod no_std_float {
399    const SIGN_MASK: u64 = 1 << 63;
400    const SIG_BITS: i32 = 52;
401    const SIG_MASK: u64 = (1 << SIG_BITS) - 1;
402    const EXPONENT_BIAS: i32 = 1023;
403
404    // Adapted from Rust's MIT-licensed `libm` implementation:
405    // https://github.com/rust-lang/compiler-builtins/blob/5c5f07851b1878013ac81e129b0517feaaf8661d/libm/src/math/generic/trunc.rs
406    fn trunc(x: f64) -> f64 {
407        let xi = x.to_bits();
408        let e = ((xi >> SIG_BITS) & 0x7ff) as i32 - EXPONENT_BIAS;
409        if e >= SIG_BITS {
410            return x;
411        }
412        let clear_mask = if e < 0 { !SIGN_MASK } else { SIG_MASK >> e };
413        let cleared = xi & clear_mask;
414        f64::from_bits(xi ^ cleared)
415    }
416
417    pub fn floor(x: f64) -> f64 {
418        let trunc = trunc(x);
419        if x < trunc {
420            trunc - 1.0
421        } else {
422            trunc
423        }
424    }
425
426    pub fn round(x: f64) -> f64 {
427        let trunc = trunc(x);
428        let fract = x - trunc;
429        if fract >= 0.5 {
430            trunc + 1.0
431        } else if fract <= -0.5 {
432            trunc - 1.0
433        } else {
434            trunc
435        }
436    }
437
438    pub fn ceil(x: f64) -> f64 {
439        let trunc = trunc(x);
440        if x > trunc {
441            trunc + 1.0
442        } else {
443            trunc
444        }
445    }
446
447    #[cfg(all(test, feature = "std"))]
448    mod tests {
449        #[track_caller]
450        fn assert_same(actual: f64, expected: f64) {
451            if expected.is_nan() {
452                assert!(actual.is_nan());
453            } else {
454                assert_eq!(actual.to_bits(), expected.to_bits());
455            }
456        }
457
458        #[test]
459        fn matches_std() {
460            let values = [
461                f64::NEG_INFINITY,
462                -((1_u64 << 53) as f64),
463                -1.5,
464                -0.5,
465                -f64::from_bits(1),
466                -0.0,
467                0.0,
468                f64::from_bits(1),
469                0.5,
470                1.5,
471                (1_u64 << 53) as f64,
472                f64::INFINITY,
473                f64::NAN,
474            ];
475            for x in values {
476                assert_same(super::trunc(x), x.trunc());
477                assert_same(super::floor(x), x.floor());
478                assert_same(super::round(x), x.round());
479                assert_same(super::ceil(x), x.ceil());
480            }
481        }
482    }
483}
484
485#[allow(clippy::unit_arg)]
486fn base_run<D: DataT>() -> Box<[Filter<RunPtr<D>>]>
487where
488    for<'a> D::V<'a>: ValT,
489{
490    let f = || [Bind::Fun(())].into();
491    Box::new([
492        ("floor", v(0), |cv| bome(cv.1.round(floor))),
493        ("round", v(0), |cv| bome(cv.1.round(round))),
494        ("ceil", v(0), |cv| bome(cv.1.round(ceil))),
495        ("utf8bytelength", v(0), |cv| {
496            bome(cv.1.try_as_utf8_bytes().map(|s| (s.len() as isize).into()))
497        }),
498        ("explode", v(0), |cv| {
499            bome(cv.1.try_as_utf8_bytes().and_then(|s| explode(s).collect()))
500        }),
501        ("implode", v(0), |cv| {
502            let implode = |s: Vec<_>| implode(&s);
503            bome(cv.1.into_vec().and_then(implode).map(D::V::from_utf8_bytes))
504        }),
505        ("ascii_downcase", v(0), |cv| {
506            bome(cv.1.map_utf8_str(ByteSlice::to_ascii_lowercase))
507        }),
508        ("ascii_upcase", v(0), |cv| {
509            bome(cv.1.map_utf8_str(ByteSlice::to_ascii_uppercase))
510        }),
511        ("reverse", v(0), |cv| bome(cv.1.mutate_arr(|a| a.reverse()))),
512        ("sort", v(0), |cv| bome(cv.1.mutate_arr(|a| a.sort()))),
513        ("sort_by", f(), |mut cv| {
514            let (f, fc) = cv.0.pop_fun();
515            let f = move |v| f.run((fc.clone(), v));
516            box_once(cv.1.try_mutate_arr(|a| sort_by(a, f)))
517        }),
518        ("group_by", f(), |mut cv| {
519            let (f, fc) = cv.0.pop_fun();
520            let f = move |v| f.run((fc.clone(), v));
521            box_once((|| group_by(cv.1.into_vec()?, f))())
522        }),
523        ("min_by_or_empty", f(), |mut cv| {
524            let (f, fc) = cv.0.pop_fun();
525            let f = move |a| cmp_by(a, |v| f.run((fc.clone(), v)), |my, y| y < my);
526            once_or_empty(cv.1.into_vec().map_err(Exn::from).and_then(f))
527        }),
528        ("max_by_or_empty", f(), |mut cv| {
529            let (f, fc) = cv.0.pop_fun();
530            let f = move |a| cmp_by(a, |v| f.run((fc.clone(), v)), |my, y| y >= my);
531            once_or_empty(cv.1.into_vec().map_err(Exn::from).and_then(f))
532        }),
533        ("startswith", v(1), |cv| {
534            unary(cv, |v, s| {
535                Ok(v.try_as_bytes()?.starts_with(s.try_as_bytes()?).into())
536            })
537        }),
538        ("endswith", v(1), |cv| {
539            unary(cv, |v, s| {
540                Ok(v.try_as_bytes()?.ends_with(s.try_as_bytes()?).into())
541            })
542        }),
543        ("ltrimstr", v(1), |cv| {
544            unary(cv, |v, pre| v.strip_fix(&pre, <[u8]>::strip_prefix))
545        }),
546        ("rtrimstr", v(1), |cv| {
547            unary(cv, |v, suf| v.strip_fix(&suf, <[u8]>::strip_suffix))
548        }),
549        ("trim", v(0), |cv| {
550            bome(cv.1.trim_utf8_with(ByteSlice::trim))
551        }),
552        ("ltrim", v(0), |cv| {
553            bome(cv.1.trim_utf8_with(ByteSlice::trim_start))
554        }),
555        ("rtrim", v(0), |cv| {
556            bome(cv.1.trim_utf8_with(ByteSlice::trim_end))
557        }),
558        ("escape_sh", v(0), |cv| {
559            bome(
560                cv.1.try_as_utf8_bytes()
561                    .map(|s| ValT::from_utf8_bytes(s.replace(b"'", b"'\\''"))),
562            )
563        }),
564        ("halt", v(1), |mut cv| {
565            let exit_code = cv.0.pop_var().try_as_i32().map_err(Exn::from);
566            box_once(exit_code.and_then(|exit_code| Err(Exn::halt(exit_code))))
567        }),
568    ])
569}
570
571#[cfg(feature = "std")]
572fn now<V: From<String>>() -> Result<f64, Error<V>> {
573    use std::time::{SystemTime, UNIX_EPOCH};
574    SystemTime::now()
575        .duration_since(UNIX_EPOCH)
576        .map(|x| x.as_secs_f64())
577        .map_err(Error::str)
578}
579
580#[cfg(feature = "std")]
581fn std<D: DataT>() -> Box<[Filter<RunPtr<D>>]>
582where
583    for<'a> D::V<'a>: ValT,
584{
585    use std::env::vars;
586    Box::new([
587        ("env", v(0), |_| {
588            bome(D::V::from_map(
589                vars().map(|(k, v)| (D::V::from(k), D::V::from(v))),
590            ))
591        }),
592        ("now", v(0), |_| bome(now().map(D::V::from))),
593    ])
594}
595
596#[cfg(feature = "format")]
597fn replace(s: &[u8], patterns: &[&str], replacements: &[&str]) -> Vec<u8> {
598    let ac = aho_corasick::AhoCorasick::new(patterns).unwrap();
599    ac.replace_all_bytes(s, replacements)
600}
601
602#[cfg(feature = "format")]
603fn format<D: DataT>() -> Box<[Filter<RunPtr<D>>]>
604where
605    for<'a> D::V<'a>: ValT,
606{
607    const HTML_PATS: [&str; 5] = ["<", ">", "&", "\'", "\""];
608    const HTML_REPS: [&str; 5] = ["&lt;", "&gt;", "&amp;", "&apos;", "&quot;"];
609    Box::new([
610        ("escape_html", v(0), |cv| {
611            bome(cv.1.map_utf8_str(|s| replace(s, &HTML_PATS, &HTML_REPS)))
612        }),
613        ("unescape_html", v(0), |cv| {
614            bome(cv.1.map_utf8_str(|s| replace(s, &HTML_REPS, &HTML_PATS)))
615        }),
616        ("encode_uri", v(0), |cv| {
617            bome(cv.1.map_utf8_str(|s| urlencoding::encode_binary(s).to_string()))
618        }),
619        ("decode_uri", v(0), |cv| {
620            bome(cv.1.map_utf8_str(|s| urlencoding::decode_binary(s).to_vec()))
621        }),
622        ("encode_base64", v(0), |cv| {
623            use base64::{engine::general_purpose::STANDARD, Engine};
624            bome(cv.1.map_utf8_str(|s| STANDARD.encode(s)))
625        }),
626        ("decode_base64", v(0), |cv| {
627            use base64::{engine::general_purpose::STANDARD, Engine};
628            bome(cv.1.try_as_utf8_bytes().and_then(|s| {
629                STANDARD
630                    .decode(s)
631                    .map_err(Error::str)
632                    .map(ValT::from_utf8_bytes)
633            }))
634        }),
635    ])
636}
637
638#[cfg(feature = "math")]
639fn math<D: DataT>() -> Box<[Filter<RunPtr<D>>]>
640where
641    for<'a> D::V<'a>: ValT,
642{
643    let rename = |name, (_name, arity, f): Filter<RunPtr<D>>| (name, arity, f);
644    Box::new([
645        math::f_f!(acos),
646        math::f_f!(acosh),
647        math::f_f!(asin),
648        math::f_f!(asinh),
649        math::f_f!(atan),
650        math::f_f!(atanh),
651        math::f_f!(cbrt),
652        math::f_f!(cos),
653        math::f_f!(cosh),
654        math::f_f!(erf),
655        math::f_f!(erfc),
656        math::f_f!(exp),
657        math::f_f!(exp10),
658        math::f_f!(exp2),
659        math::f_f!(expm1),
660        math::f_f!(fabs),
661        math::f_fi!(frexp),
662        math::f_i!(ilogb),
663        math::f_f!(j0),
664        math::f_f!(j1),
665        math::f_f!(lgamma),
666        math::f_f!(log),
667        math::f_f!(log10),
668        math::f_f!(log1p),
669        math::f_f!(log2),
670        // logb is implemented in jaq-std
671        math::f_ff!(modf),
672        rename("nearbyint", math::f_f!(round)),
673        // pow10 is implemented in jaq-std
674        math::f_f!(rint),
675        // significand is implemented in jaq-std
676        math::f_f!(sin),
677        math::f_f!(sinh),
678        math::f_f!(sqrt),
679        math::f_f!(tan),
680        math::f_f!(tanh),
681        math::f_f!(tgamma),
682        math::f_f!(trunc),
683        math::f_f!(y0),
684        math::f_f!(y1),
685        math::ff_f!(atan2),
686        math::ff_f!(copysign),
687        // drem is implemented in jaq-std
688        math::ff_f!(fdim),
689        math::ff_f!(fmax),
690        math::ff_f!(fmin),
691        math::ff_f!(fmod),
692        math::ff_f!(hypot),
693        math::if_f!(jn),
694        math::fi_f!(ldexp),
695        math::ff_f!(nextafter),
696        // nexttoward is implemented in jaq-std
697        math::ff_f!(pow),
698        math::ff_f!(remainder),
699        // scalb is implemented in jaq-std
700        rename("scalbln", math::fi_f!(scalbn)),
701        math::if_f!(yn),
702        math::fff_f!(fma),
703    ])
704}
705
706#[cfg(feature = "regex")]
707fn re<'a, D: DataT>(s: bool, m: bool, mut cv: Cv<'a, D>) -> ValR<D::V<'a>>
708where
709    D::V<'a>: ValT,
710{
711    let flags = cv.0.pop_var();
712    let re = cv.0.pop_var();
713
714    use crate::regex::Part::{Matches, Mismatch};
715    let fail_flag = |e| Error::str(format_args!("invalid regex flag: {e}"));
716    let fail_re = |e| Error::str(format_args!("invalid regex: {e}"));
717
718    let flags = regex::Flags::new(flags.try_as_str()?).map_err(fail_flag)?;
719    let re = flags.regex(re.try_as_str()?).map_err(fail_re)?;
720    let out = regex::regex(cv.1.try_as_utf8_bytes()?, &re, flags, (s, m));
721    let sub = |s| cv.1.as_sub_str(s);
722    let out = out.into_iter().map(|out| match out {
723        Matches(ms) => ms
724            .into_iter()
725            .map(|m| D::V::from_map(m.fields(sub)))
726            .collect(),
727        Mismatch(s) => Ok(sub(s)),
728    });
729    out.collect()
730}
731
732#[cfg(feature = "regex")]
733fn regex<D: DataT>() -> Box<[Filter<RunPtr<D>>]>
734where
735    for<'a> D::V<'a>: ValT,
736{
737    let vv = || [Bind::Var(()), Bind::Var(())].into();
738    Box::new([
739        ("matches", vv(), |cv| bome(re(false, true, cv))),
740        ("split_matches", vv(), |cv| bome(re(true, true, cv))),
741        ("split_", vv(), |cv| bome(re(true, false, cv))),
742    ])
743}
744
745#[cfg(feature = "time")]
746fn time<D: DataT>() -> Box<[Filter<RunPtr<D>>]>
747where
748    for<'a> D::V<'a>: ValT,
749{
750    use jiff::tz::TimeZone;
751    Box::new([
752        ("fromdateiso8601", v(0), |cv| {
753            bome(cv.1.try_as_str().and_then(time::from_iso8601))
754        }),
755        ("todateiso8601", v(0), |cv| {
756            bome(time::to_iso8601(&cv.1).map(D::V::from))
757        }),
758        ("strftime", v(1), |cv| {
759            unary(cv, |v, fmt| {
760                time::strftime(&v, fmt.try_as_str()?, TimeZone::UTC)
761            })
762        }),
763        ("strflocaltime", v(1), |cv| {
764            unary(cv, |v, fmt| {
765                time::strftime(&v, fmt.try_as_str()?, TimeZone::system())
766            })
767        }),
768        ("gmtime", v(0), |cv| {
769            bome(time::gmtime(&cv.1, TimeZone::UTC))
770        }),
771        ("localtime", v(0), |cv| {
772            bome(time::gmtime(&cv.1, TimeZone::system()))
773        }),
774        ("strptime", v(1), |cv| {
775            unary(cv, |v, fmt| {
776                time::strptime(v.try_as_str()?, fmt.try_as_str()?)
777            })
778        }),
779        ("mktime", v(0), |cv| bome(time::mktime(&cv.1))),
780    ])
781}
782
783#[cfg(feature = "log")]
784fn log<D: DataT>() -> Box<[Filter<RunPtr<D>>]>
785where
786    for<'a> D::V<'a>: ValT,
787{
788    fn eprint_raw<V: ValT>(v: &V) {
789        if let Some(s) = v.as_utf8_bytes() {
790            log::error!("{}", BStr::new(s))
791        } else {
792            log::error!("{v}")
793        }
794    }
795    /// Construct a filter that applies an effect function before returning nothing.
796    macro_rules! empty_with {
797        ( $eff:expr ) => {
798            |cv| {
799                $eff(&cv.1);
800                Box::new(core::iter::empty())
801            }
802        };
803    }
804    Box::new([
805        ("debug_empty", v(0), empty_with!(|x| log::debug!("{x}"))),
806        ("stderr_empty", v(0), empty_with!(eprint_raw)),
807    ])
808}