1#![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
28use alloc::string::{String, ToString};
29use alloc::{boxed::Box, vec::Vec};
30use bstr::{BStr, ByteSlice};
31use jaq_core::box_iter::{box_once, BoxIter};
32use jaq_core::native::{bome, run, unary, v, Filter, Fun};
33use jaq_core::{load, Bind, Cv, DataT, Error, Exn, RunPtr, ValR, ValT as _, ValX, ValXs};
34
35pub fn defs() -> impl Iterator<Item = load::parse::Def<&'static str>> {
37 load::parse(include_str!("defs.jq"), |p| p.defs())
38 .unwrap()
39 .into_iter()
40}
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<D: DataT>() -> impl Iterator<Item = Fun<D>>
57where
58 for<'a> D::V<'a>: ValT,
59{
60 base_funs().chain(extra_funs())
61}
62
63pub fn base_funs<D: DataT>() -> impl Iterator<Item = Fun<D>>
70where
71 for<'a> D::V<'a>: ValT,
72{
73 base_run().into_vec().into_iter().map(run)
74}
75
76#[cfg(all(
78 feature = "std",
79 feature = "format",
80 feature = "log",
81 feature = "math",
82 feature = "regex",
83 feature = "time",
84))]
85pub fn extra_funs<D: DataT>() -> impl Iterator<Item = Fun<D>>
86where
87 for<'a> D::V<'a>: ValT,
88{
89 [std(), format(), math(), regex(), time(), log()]
90 .into_iter()
91 .flat_map(|fs| fs.into_vec().into_iter().map(run))
92}
93
94pub trait ValT: jaq_core::ValT + Ord + From<f64> + From<usize> {
96 fn into_seq<S: FromIterator<Self>>(self) -> Result<S, Self>;
100
101 fn is_int(&self) -> bool;
103
104 fn as_isize(&self) -> Option<isize>;
110
111 fn as_f64(&self) -> Option<f64>;
116
117 fn is_utf8_str(&self) -> bool;
119
120 fn as_bytes(&self) -> Option<&[u8]>;
122
123 fn as_utf8_bytes(&self) -> Option<&[u8]> {
125 self.is_utf8_str().then(|| self.as_bytes()).flatten()
126 }
127
128 fn try_as_bytes(&self) -> Result<&[u8], Error<Self>> {
130 self.as_bytes().ok_or_else(|| self.fail_str())
131 }
132
133 fn try_as_utf8_bytes(&self) -> Result<&[u8], Error<Self>> {
135 self.as_utf8_bytes().ok_or_else(|| self.fail_str())
136 }
137
138 fn as_sub_str(&self, sub: &[u8]) -> Self;
141
142 fn from_utf8_bytes(b: impl AsRef<[u8]> + Send + 'static) -> Self;
144}
145
146trait ValTx: ValT + Sized {
148 fn into_vec(self) -> Result<Vec<Self>, Error<Self>> {
149 self.into_seq().map_err(|v| Error::typ(v, "array"))
150 }
151
152 fn try_as_isize(&self) -> Result<isize, Error<Self>> {
153 self.as_isize()
154 .ok_or_else(|| Error::typ(self.clone(), "integer"))
155 }
156
157 fn try_as_i32(&self) -> Result<i32, Error<Self>> {
158 self.try_as_isize()?.try_into().map_err(Error::str)
159 }
160
161 fn try_as_f64(&self) -> Result<f64, Error<Self>> {
162 self.as_f64()
163 .ok_or_else(|| Error::typ(self.clone(), "number"))
164 }
165
166 fn mutate_arr(self, f: impl FnOnce(&mut Vec<Self>)) -> ValR<Self> {
168 let mut a = self.into_vec()?;
169 f(&mut a);
170 Ok(Self::from_iter(a))
171 }
172
173 fn try_mutate_arr<'a, F>(self, f: F) -> ValX<'a, Self>
175 where
176 F: FnOnce(&mut Vec<Self>) -> Result<(), Exn<'a, Self>>,
177 {
178 let mut a = self.into_vec()?;
179 f(&mut a)?;
180 Ok(Self::from_iter(a))
181 }
182
183 fn round(self, f: impl FnOnce(f64) -> f64) -> ValR<Self> {
184 Ok(if self.is_int() {
185 self
186 } else {
187 let f = f(self.try_as_f64()?);
188 if f.is_finite() {
189 if isize::MIN as f64 <= f && f <= isize::MAX as f64 {
190 Self::from(f as isize)
191 } else {
192 Self::from_num(&alloc::format!("{f:.0}"))?
195 }
196 } else {
197 Self::from(f)
198 }
199 })
200 }
201
202 fn try_as_str(&self) -> Result<&str, Error<Self>> {
205 self.try_as_utf8_bytes()
206 .and_then(|s| core::str::from_utf8(s).map_err(Error::str))
207 }
208
209 fn map_utf8_str<B>(self, f: impl FnOnce(&[u8]) -> B) -> ValR<Self>
210 where
211 B: AsRef<[u8]> + Send + 'static,
212 {
213 Ok(Self::from_utf8_bytes(f(self.try_as_utf8_bytes()?)))
214 }
215
216 fn trim_utf8_with(&self, f: impl FnOnce(&[u8]) -> &[u8]) -> ValR<Self> {
217 Ok(self.as_sub_str(f(self.try_as_utf8_bytes()?)))
218 }
219
220 fn strip_fix<F>(self, fix: &Self, f: F) -> Result<Self, Error<Self>>
222 where
223 F: for<'a> FnOnce(&'a [u8], &[u8]) -> Option<&'a [u8]>,
224 {
225 Ok(match f(self.try_as_bytes()?, fix.try_as_bytes()?) {
226 Some(sub) => self.as_sub_str(sub),
227 None => self,
228 })
229 }
230
231 fn fail_str(&self) -> Error<Self> {
232 Error::typ(self.clone(), "string")
233 }
234}
235impl<T: ValT> ValTx for T {}
236
237fn sort_by<'a, V: ValT>(xs: &mut [V], f: impl Fn(V) -> ValXs<'a, V>) -> Result<(), Exn<'a, V>> {
239 let mut err = None;
241 xs.sort_by_cached_key(|x| {
242 if err.is_some() {
243 return Vec::new();
244 };
245 match f(x.clone()).collect() {
246 Ok(y) => y,
247 Err(e) => {
248 err = Some(e);
249 Vec::new()
250 }
251 }
252 });
253 err.map_or(Ok(()), Err)
254}
255
256fn group_by<'a, V: ValT>(xs: Vec<V>, f: impl Fn(V) -> ValXs<'a, V>) -> ValX<'a, V> {
258 let mut yx: Vec<(Vec<V>, V)> = xs
259 .into_iter()
260 .map(|x| Ok((f(x.clone()).collect::<Result<_, _>>()?, x)))
261 .collect::<Result<_, Exn<_>>>()?;
262
263 yx.sort_by(|(y1, _), (y2, _)| y1.cmp(y2));
264
265 let mut grouped = Vec::new();
266 let mut yx = yx.into_iter();
267 if let Some((mut group_y, first_x)) = yx.next() {
268 let mut group = Vec::from([first_x]);
269 for (y, x) in yx {
270 if group_y != y {
271 grouped.push(V::from_iter(core::mem::take(&mut group)));
272 group_y = y;
273 }
274 group.push(x);
275 }
276 if !group.is_empty() {
277 grouped.push(V::from_iter(group));
278 }
279 }
280
281 Ok(V::from_iter(grouped))
282}
283
284fn cmp_by<'a, V: Clone, F, R>(xs: Vec<V>, f: F, replace: R) -> Result<Option<V>, Exn<'a, V>>
286where
287 F: Fn(V) -> ValXs<'a, V>,
288 R: Fn(&[V], &[V]) -> bool,
289{
290 let iter = xs.into_iter();
291 let mut iter = iter.map(|x| (x.clone(), f(x).collect::<Result<Vec<_>, _>>()));
292 let (mut mx, mut my) = if let Some((x, y)) = iter.next() {
293 (x, y?)
294 } else {
295 return Ok(None);
296 };
297 for (x, y) in iter {
298 let y = y?;
299 if replace(&my, &y) {
300 (mx, my) = (x, y);
301 }
302 }
303 Ok(Some(mx))
304}
305
306fn explode<V: ValT>(s: &[u8]) -> impl Iterator<Item = ValR<V>> + '_ {
308 let invalid = [].iter();
309 Explode { s, invalid }.map(|r| match r {
310 Err(b) => Ok((-(b as isize)).into()),
311 Ok(c) => Ok(isize::try_from(c as u32).map_err(Error::str)?.into()),
313 })
314}
315
316struct Explode<'a> {
317 s: &'a [u8],
318 invalid: core::slice::Iter<'a, u8>,
319}
320impl Iterator for Explode<'_> {
321 type Item = Result<char, u8>;
322 fn next(&mut self) -> Option<Self::Item> {
323 self.invalid.next().map(|next| Err(*next)).or_else(|| {
324 let (c, size) = bstr::decode_utf8(self.s);
325 let (consumed, rest) = self.s.split_at(size);
326 self.s = rest;
327 c.map(Ok).or_else(|| {
328 self.invalid = consumed.iter();
330 self.invalid.next().map(|next| Err(*next))
331 })
332 })
333 }
334 fn size_hint(&self) -> (usize, Option<usize>) {
335 let max = self.s.len();
336 let min = self.s.len() / 4;
337 let inv = self.invalid.as_slice().len();
338 (min + inv, Some(max + inv))
339 }
340}
341
342fn implode<V: ValT>(xs: &[V]) -> Result<Vec<u8>, Error<V>> {
344 let mut v = Vec::with_capacity(xs.len());
345 for x in xs {
346 let i = x.try_as_isize()?;
348 if let Ok(b) = u8::try_from(-i) {
349 v.push(b)
350 } else {
351 let c = u32::try_from(i).ok().and_then(char::from_u32);
353 let c = c.ok_or_else(|| Error::str(format_args!("cannot use {i} as character")))?;
354 v.extend(c.encode_utf8(&mut [0; 4]).as_bytes())
355 }
356 }
357 Ok(v)
358}
359
360fn once_or_empty<'a, T: 'a, E: 'a>(r: Result<Option<T>, E>) -> BoxIter<'a, Result<T, E>> {
361 Box::new(r.transpose().into_iter())
362}
363
364#[allow(clippy::unit_arg)]
365fn base_run<D: DataT>() -> Box<[Filter<RunPtr<D>>]>
366where
367 for<'a> D::V<'a>: ValT,
368{
369 let f = || [Bind::Fun(())].into();
370 Box::new([
371 ("floor", v(0), |cv| bome(cv.1.round(f64::floor))),
372 ("round", v(0), |cv| bome(cv.1.round(f64::round))),
373 ("ceil", v(0), |cv| bome(cv.1.round(f64::ceil))),
374 ("utf8bytelength", v(0), |cv| {
375 bome(cv.1.try_as_utf8_bytes().map(|s| (s.len() as isize).into()))
376 }),
377 ("explode", v(0), |cv| {
378 bome(cv.1.try_as_utf8_bytes().and_then(|s| explode(s).collect()))
379 }),
380 ("implode", v(0), |cv| {
381 let implode = |s: Vec<_>| implode(&s);
382 bome(cv.1.into_vec().and_then(implode).map(D::V::from_utf8_bytes))
383 }),
384 ("ascii_downcase", v(0), |cv| {
385 bome(cv.1.map_utf8_str(ByteSlice::to_ascii_lowercase))
386 }),
387 ("ascii_upcase", v(0), |cv| {
388 bome(cv.1.map_utf8_str(ByteSlice::to_ascii_uppercase))
389 }),
390 ("reverse", v(0), |cv| bome(cv.1.mutate_arr(|a| a.reverse()))),
391 ("sort", v(0), |cv| bome(cv.1.mutate_arr(|a| a.sort()))),
392 ("sort_by", f(), |mut cv| {
393 let (f, fc) = cv.0.pop_fun();
394 let f = move |v| f.run((fc.clone(), v));
395 box_once(cv.1.try_mutate_arr(|a| sort_by(a, f)))
396 }),
397 ("group_by", f(), |mut cv| {
398 let (f, fc) = cv.0.pop_fun();
399 let f = move |v| f.run((fc.clone(), v));
400 box_once((|| group_by(cv.1.into_vec()?, f))())
401 }),
402 ("min_by_or_empty", f(), |mut cv| {
403 let (f, fc) = cv.0.pop_fun();
404 let f = move |a| cmp_by(a, |v| f.run((fc.clone(), v)), |my, y| y < my);
405 once_or_empty(cv.1.into_vec().map_err(Exn::from).and_then(f))
406 }),
407 ("max_by_or_empty", f(), |mut cv| {
408 let (f, fc) = cv.0.pop_fun();
409 let f = move |a| cmp_by(a, |v| f.run((fc.clone(), v)), |my, y| y >= my);
410 once_or_empty(cv.1.into_vec().map_err(Exn::from).and_then(f))
411 }),
412 ("startswith", v(1), |cv| {
413 unary(cv, |v, s| {
414 Ok(v.try_as_bytes()?.starts_with(s.try_as_bytes()?).into())
415 })
416 }),
417 ("endswith", v(1), |cv| {
418 unary(cv, |v, s| {
419 Ok(v.try_as_bytes()?.ends_with(s.try_as_bytes()?).into())
420 })
421 }),
422 ("ltrimstr", v(1), |cv| {
423 unary(cv, |v, pre| v.strip_fix(&pre, <[u8]>::strip_prefix))
424 }),
425 ("rtrimstr", v(1), |cv| {
426 unary(cv, |v, suf| v.strip_fix(&suf, <[u8]>::strip_suffix))
427 }),
428 ("trim", v(0), |cv| {
429 bome(cv.1.trim_utf8_with(ByteSlice::trim))
430 }),
431 ("ltrim", v(0), |cv| {
432 bome(cv.1.trim_utf8_with(ByteSlice::trim_start))
433 }),
434 ("rtrim", v(0), |cv| {
435 bome(cv.1.trim_utf8_with(ByteSlice::trim_end))
436 }),
437 ("escape_sh", v(0), |cv| {
438 bome(
439 cv.1.try_as_utf8_bytes()
440 .map(|s| ValT::from_utf8_bytes(s.replace(b"'", b"'\\''"))),
441 )
442 }),
443 ("halt", v(1), |mut cv| {
444 let exit_code = cv.0.pop_var().try_as_i32().map_err(Exn::from);
445 box_once(exit_code.and_then(|exit_code| Err(Exn::halt(exit_code))))
446 }),
447 ])
448}
449
450#[cfg(feature = "std")]
451fn now<V: From<String>>() -> Result<f64, Error<V>> {
452 use std::time::{SystemTime, UNIX_EPOCH};
453 SystemTime::now()
454 .duration_since(UNIX_EPOCH)
455 .map(|x| x.as_secs_f64())
456 .map_err(Error::str)
457}
458
459#[cfg(feature = "std")]
460fn std<D: DataT>() -> Box<[Filter<RunPtr<D>>]>
461where
462 for<'a> D::V<'a>: ValT,
463{
464 use std::env::vars;
465 Box::new([
466 ("env", v(0), |_| {
467 bome(D::V::from_map(
468 vars().map(|(k, v)| (D::V::from(k), D::V::from(v))),
469 ))
470 }),
471 ("now", v(0), |_| bome(now().map(D::V::from))),
472 ])
473}
474
475#[cfg(feature = "format")]
476fn replace(s: &[u8], patterns: &[&str], replacements: &[&str]) -> Vec<u8> {
477 let ac = aho_corasick::AhoCorasick::new(patterns).unwrap();
478 ac.replace_all_bytes(s, replacements)
479}
480
481#[cfg(feature = "format")]
482fn format<D: DataT>() -> Box<[Filter<RunPtr<D>>]>
483where
484 for<'a> D::V<'a>: ValT,
485{
486 const HTML_PATS: [&str; 5] = ["<", ">", "&", "\'", "\""];
487 const HTML_REPS: [&str; 5] = ["<", ">", "&", "'", """];
488 Box::new([
489 ("escape_html", v(0), |cv| {
490 bome(cv.1.map_utf8_str(|s| replace(s, &HTML_PATS, &HTML_REPS)))
491 }),
492 ("unescape_html", v(0), |cv| {
493 bome(cv.1.map_utf8_str(|s| replace(s, &HTML_REPS, &HTML_PATS)))
494 }),
495 ("encode_uri", v(0), |cv| {
496 bome(cv.1.map_utf8_str(|s| urlencoding::encode_binary(s).to_string()))
497 }),
498 ("decode_uri", v(0), |cv| {
499 bome(cv.1.map_utf8_str(|s| urlencoding::decode_binary(s).to_vec()))
500 }),
501 ("encode_base64", v(0), |cv| {
502 use base64::{engine::general_purpose::STANDARD, Engine};
503 bome(cv.1.map_utf8_str(|s| STANDARD.encode(s)))
504 }),
505 ("decode_base64", v(0), |cv| {
506 use base64::{engine::general_purpose::STANDARD, Engine};
507 bome(cv.1.try_as_utf8_bytes().and_then(|s| {
508 STANDARD
509 .decode(s)
510 .map_err(Error::str)
511 .map(ValT::from_utf8_bytes)
512 }))
513 }),
514 ])
515}
516
517#[cfg(feature = "math")]
518fn math<D: DataT>() -> Box<[Filter<RunPtr<D>>]>
519where
520 for<'a> D::V<'a>: ValT,
521{
522 let rename = |name, (_name, arity, f): Filter<RunPtr<D>>| (name, arity, f);
523 Box::new([
524 math::f_f!(acos),
525 math::f_f!(acosh),
526 math::f_f!(asin),
527 math::f_f!(asinh),
528 math::f_f!(atan),
529 math::f_f!(atanh),
530 math::f_f!(cbrt),
531 math::f_f!(cos),
532 math::f_f!(cosh),
533 math::f_f!(erf),
534 math::f_f!(erfc),
535 math::f_f!(exp),
536 math::f_f!(exp10),
537 math::f_f!(exp2),
538 math::f_f!(expm1),
539 math::f_f!(fabs),
540 math::f_fi!(frexp),
541 math::f_i!(ilogb),
542 math::f_f!(j0),
543 math::f_f!(j1),
544 math::f_f!(lgamma),
545 math::f_f!(log),
546 math::f_f!(log10),
547 math::f_f!(log1p),
548 math::f_f!(log2),
549 math::f_ff!(modf),
551 rename("nearbyint", math::f_f!(round)),
552 math::f_f!(rint),
554 math::f_f!(sin),
556 math::f_f!(sinh),
557 math::f_f!(sqrt),
558 math::f_f!(tan),
559 math::f_f!(tanh),
560 math::f_f!(tgamma),
561 math::f_f!(trunc),
562 math::f_f!(y0),
563 math::f_f!(y1),
564 math::ff_f!(atan2),
565 math::ff_f!(copysign),
566 math::ff_f!(fdim),
568 math::ff_f!(fmax),
569 math::ff_f!(fmin),
570 math::ff_f!(fmod),
571 math::ff_f!(hypot),
572 math::if_f!(jn),
573 math::fi_f!(ldexp),
574 math::ff_f!(nextafter),
575 math::ff_f!(pow),
577 math::ff_f!(remainder),
578 rename("scalbln", math::fi_f!(scalbn)),
580 math::if_f!(yn),
581 math::fff_f!(fma),
582 ])
583}
584
585#[cfg(feature = "regex")]
586fn re<'a, D: DataT>(s: bool, m: bool, mut cv: Cv<'a, D>) -> ValR<D::V<'a>>
587where
588 D::V<'a>: ValT,
589{
590 let flags = cv.0.pop_var();
591 let re = cv.0.pop_var();
592
593 use crate::regex::Part::{Matches, Mismatch};
594 let fail_flag = |e| Error::str(format_args!("invalid regex flag: {e}"));
595 let fail_re = |e| Error::str(format_args!("invalid regex: {e}"));
596
597 let flags = regex::Flags::new(flags.try_as_str()?).map_err(fail_flag)?;
598 let re = flags.regex(re.try_as_str()?).map_err(fail_re)?;
599 let out = regex::regex(cv.1.try_as_utf8_bytes()?, &re, flags, (s, m));
600 let sub = |s| cv.1.as_sub_str(s);
601 let out = out.into_iter().map(|out| match out {
602 Matches(ms) => ms
603 .into_iter()
604 .map(|m| D::V::from_map(m.fields(sub)))
605 .collect(),
606 Mismatch(s) => Ok(sub(s)),
607 });
608 out.collect()
609}
610
611#[cfg(feature = "regex")]
612fn regex<D: DataT>() -> Box<[Filter<RunPtr<D>>]>
613where
614 for<'a> D::V<'a>: ValT,
615{
616 let vv = || [Bind::Var(()), Bind::Var(())].into();
617 Box::new([
618 ("matches", vv(), |cv| bome(re(false, true, cv))),
619 ("split_matches", vv(), |cv| bome(re(true, true, cv))),
620 ("split_", vv(), |cv| bome(re(true, false, cv))),
621 ])
622}
623
624#[cfg(feature = "time")]
625fn time<D: DataT>() -> Box<[Filter<RunPtr<D>>]>
626where
627 for<'a> D::V<'a>: ValT,
628{
629 use jiff::tz::TimeZone;
630 Box::new([
631 ("fromdateiso8601", v(0), |cv| {
632 bome(cv.1.try_as_str().and_then(time::from_iso8601))
633 }),
634 ("todateiso8601", v(0), |cv| {
635 bome(time::to_iso8601(&cv.1).map(D::V::from))
636 }),
637 ("strftime", v(1), |cv| {
638 unary(cv, |v, fmt| {
639 time::strftime(&v, fmt.try_as_str()?, TimeZone::UTC)
640 })
641 }),
642 ("strflocaltime", v(1), |cv| {
643 unary(cv, |v, fmt| {
644 time::strftime(&v, fmt.try_as_str()?, TimeZone::system())
645 })
646 }),
647 ("gmtime", v(0), |cv| {
648 bome(time::gmtime(&cv.1, TimeZone::UTC))
649 }),
650 ("localtime", v(0), |cv| {
651 bome(time::gmtime(&cv.1, TimeZone::system()))
652 }),
653 ("strptime", v(1), |cv| {
654 unary(cv, |v, fmt| {
655 time::strptime(v.try_as_str()?, fmt.try_as_str()?)
656 })
657 }),
658 ("mktime", v(0), |cv| bome(time::mktime(&cv.1))),
659 ])
660}
661
662#[cfg(feature = "log")]
663fn log<D: DataT>() -> Box<[Filter<RunPtr<D>>]>
664where
665 for<'a> D::V<'a>: ValT,
666{
667 fn eprint_raw<V: ValT>(v: &V) {
668 if let Some(s) = v.as_utf8_bytes() {
669 log::error!("{}", BStr::new(s))
670 } else {
671 log::error!("{v}")
672 }
673 }
674 macro_rules! empty_with {
676 ( $eff:expr ) => {
677 |cv| {
678 $eff(&cv.1);
679 Box::new(core::iter::empty())
680 }
681 };
682 }
683 Box::new([
684 ("debug_empty", v(0), empty_with!(|x| log::debug!("{x}"))),
685 ("stderr_empty", v(0), empty_with!(eprint_raw)),
686 ])
687}