1mod collections;
10mod numeric;
11mod strings;
12
13use std::sync::OnceLock;
14
15use serde::Serialize;
16
17use crate::error::{Error, Result};
18use crate::eval::{Env, Evaluator};
19use crate::signature::Signature;
20use crate::value::{self, Function, Value};
21
22pub(crate) struct Call<'c, 'a> {
24 pub input: &'c Value<'a>,
26 pub env: &'c Env<'a>,
28}
29
30pub(crate) type NativeImpl =
31 for<'a> fn(&mut Evaluator<'_, 'a>, Vec<Value<'a>>, &Call<'_, 'a>) -> Result<Value<'a>>;
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
35#[serde(rename_all = "kebab-case")]
36pub enum Category {
37 String,
39 Numeric,
41 Aggregation,
43 Boolean,
45 Array,
47 Object,
49 HigherOrder,
51 DateTime,
53 Encoding,
55 Misc,
57}
58
59pub(crate) struct Native {
61 pub name: &'static str,
62 pub signature: Option<Signature>,
63 pub params: &'static [&'static str],
65 pub arity: usize,
67 pub category: Category,
68 pub summary: &'static str,
69 pub imp: NativeImpl,
70}
71
72#[derive(Debug, Clone, Serialize)]
74pub struct FunctionInfo {
75 pub name: &'static str,
77 pub signature: String,
79 pub params: &'static [&'static str],
81 pub category: Category,
83 pub summary: &'static str,
85}
86
87pub(crate) fn arg<'a>(args: &[Value<'a>], i: usize) -> Value<'a> {
88 args.get(i).cloned().unwrap_or_default()
89}
90
91pub(crate) fn num<'a>(args: &[Value<'a>], i: usize) -> Option<f64> {
92 args.get(i).and_then(Value::as_number)
93}
94
95pub(crate) fn text<'a>(args: &[Value<'a>], i: usize) -> Option<std::rc::Rc<str>> {
96 match args.get(i) {
97 Some(Value::String(s)) => Some(s.clone()),
98 _ => None,
99 }
100}
101
102macro_rules! natives {
103 ($( $name:literal $sig:literal [$($param:literal),*] $arity:literal $cat:ident $summary:literal => $imp:expr ;)*) => {
104 fn build() -> Vec<Native> {
105 vec![$(Native {
106 name: $name,
107 signature: Some(Signature::parse($sig).expect("valid built-in signature")),
108 params: &[$($param),*],
109 arity: $arity,
110 category: Category::$cat,
111 summary: $summary,
112 imp: $imp,
113 }),*]
114 }
115 };
116}
117
118use collections as c;
119use numeric as n;
120use strings as s;
121
122natives! {
123 "sum" "<a<n>:n>" ["array"] 1 Aggregation "Sum of an array of numbers" => |_, a, _| sum(&arg(&a, 0));
125 "count" "<a:n>" ["array"] 1 Aggregation "Number of items in an array" => |_, a, _| Ok(match arg(&a, 0) {
126 Value::Undefined => Value::Number(0.0),
127 Value::Array(arr) => Value::Number(arr.len() as f64),
128 _ => Value::Number(1.0),
129 });
130 "max" "<a<n>:n>" ["array"] 1 Aggregation "Largest number in an array" => |_, a, _| extreme(&arg(&a, 0), f64::max);
131 "min" "<a<n>:n>" ["array"] 1 Aggregation "Smallest number in an array" => |_, a, _| extreme(&arg(&a, 0), f64::min);
132 "average" "<a<n>:n>" ["array"] 1 Aggregation "Mean of an array of numbers" => |_, a, _| {
133 let items = arg(&a, 0);
134 let Some(arr) = items.as_array().filter(|a| !a.items().is_empty()) else {
135 return Ok(Value::Undefined);
136 };
137 let n = arr.len() as f64;
138 let total: f64 = arr.items().iter().filter_map(Value::as_number).sum();
139 if total.is_infinite() {
140 let mean = arr.items().iter().filter_map(Value::as_number).map(|x| x / n).sum();
142 return value::finite(mean);
143 }
144 Ok(Value::Number(total / n))
145 };
146
147 "string" "<x-b?:s>" ["arg", "prettify"] 1 String "Casts a value to a string; objects and arrays become JSON" => |ev, a, _| {
149 let pretty = matches!(arg(&a, 1), Value::Bool(true));
150 Ok(to_string(&arg(&a, 0), pretty, ev.max_string_length())?.map_or(Value::Undefined, Value::string))
151 };
152 "substring" "<s-nn?:s>" ["str", "start", "length"] 3 String "Characters from a start position, optionally limited to a length" => s::substring;
153 "substringBefore" "<s-s:s>" ["str", "chars"] 2 String "The part of a string before the first occurrence of a substring" => s::substring_before;
154 "substringAfter" "<s-s:s>" ["str", "chars"] 2 String "The part of a string after the first occurrence of a substring" => s::substring_after;
155 "lowercase" "<s-:s>" ["str"] 1 String "Converts a string to lower case" => s::lowercase;
156 "uppercase" "<s-:s>" ["str"] 1 String "Converts a string to upper case" => s::uppercase;
157 "length" "<s-:n>" ["str"] 1 String "Number of characters in a string" => s::length;
158 "trim" "<s-:s>" ["str"] 1 String "Normalizes and trims whitespace" => s::trim;
159 "pad" "<s-ns?:s>" ["str", "width", "char"] 3 String "Pads a string to a width; positive pads on the right, negative on the left" => s::pad;
160 "match" "<s-f<s:o>n?:a<o>>" ["str", "regex", "limit"] 3 String "Matches a regular expression, returning the matches with their positions and groups" => s::match_;
161 "contains" "<s-(sf):b>" ["str", "token"] 2 String "Whether a string contains a substring or matches a regular expression" => s::contains;
162 "replace" "<s-(sf)(sf)n?:s>" ["str", "pattern", "replacement", "limit"] 4 String "Replaces occurrences of a substring or regular expression" => s::replace;
163 "split" "<s-(sf)n?:a<s>>" ["str", "separator", "limit"] 3 String "Splits a string by a separator or regular expression" => s::split;
164 "join" "<a<s>s?:s>" ["strs", "separator"] 2 String "Joins an array of strings with an optional separator" => s::join;
165 "formatNumber" "<n-so?:s>" ["value", "picture", "options"] 3 Numeric "Formats a number with an XPath picture string" => n::format_number;
166 "formatBase" "<n-n?:s>" ["value", "radix"] 2 Numeric "Formats a number in another base (2 to 36)" => n::format_base;
167 "formatInteger" "<n-s:s>" ["value", "picture"] 2 Numeric "Formats an integer with an XPath picture string (words, roman, letters, digits)" => |ev, a, _| match num(&a, 0) {
168 None => Ok(Value::Undefined),
169 Some(v) => crate::datetime::format_integer(v, &text(&a, 1).unwrap_or_default(), ev.max_string_length()).map(Value::string),
170 };
171 "parseInteger" "<s-s:n>" ["value", "picture"] 2 Numeric "Parses an integer written according to an XPath picture string" => |_, a, _| match text(&a, 0) {
172 None => Ok(Value::Undefined),
173 Some(v) => crate::datetime::parse_integer(&v, &text(&a, 1).unwrap_or_default()).and_then(value::finite),
174 };
175
176 "number" "<(nsb)-:n>" ["arg"] 1 Numeric "Casts a value to a number" => n::number;
178 "floor" "<n-:n>" ["arg"] 1 Numeric "Rounds down" => |_, a, _| Ok(num(&a, 0).map_or(Value::Undefined, |v| Value::Number(v.floor())));
179 "ceil" "<n-:n>" ["arg"] 1 Numeric "Rounds up" => |_, a, _| Ok(num(&a, 0).map_or(Value::Undefined, |v| Value::Number(v.ceil())));
180 "round" "<n-n?:n>" ["arg", "precision"] 2 Numeric "Rounds half to even, optionally to a number of decimal places" => |_, a, _| {
181 num(&a, 0).map_or(Ok(Value::Undefined), |v| value::finite(n::round(v, num(&a, 1).unwrap_or(0.0))))
182 };
183 "abs" "<n-:n>" ["arg"] 1 Numeric "Absolute value" => |_, a, _| Ok(num(&a, 0).map_or(Value::Undefined, |v| Value::Number(v.abs())));
184 "sqrt" "<n-:n>" ["arg"] 1 Numeric "Square root" => n::sqrt;
185 "power" "<n-n:n>" ["arg", "exp"] 2 Numeric "Raises a number to a power" => n::power;
186 "random" "<:n>" [] 0 Numeric "A pseudo-random number between 0 (inclusive) and 1 (exclusive)" => |ev, _, _| Ok(Value::Number(ev.random()));
187
188 "boolean" "<x-:b>" ["arg"] 1 Boolean "The effective boolean value of a value" => |_, a, _| Ok(boolean(&arg(&a, 0))?.map_or(Value::Undefined, Value::Bool));
190 "not" "<x-:b>" ["arg"] 1 Boolean "Logical negation of the effective boolean value" => |_, a, _| Ok(boolean(&arg(&a, 0))?.map_or(Value::Undefined, |b| Value::Bool(!b)));
191 "exists" "<x:b>" ["arg"] 1 Boolean "Whether a value is defined" => |_, a, _| Ok(Value::Bool(!arg(&a, 0).is_undefined()));
192
193 "map" "<af>" ["arr", "func"] 2 HigherOrder "Applies a function to each item of an array" => c::map;
195 "zip" "<a+>" [] 0 Array "Combines arrays item by item into an array of arrays" => c::zip;
196 "filter" "<af>" ["arr", "func"] 2 HigherOrder "The items of an array for which a predicate holds" => c::filter;
197 "single" "<af?>" ["arr", "func"] 2 HigherOrder "The only item of an array matching a predicate; an error otherwise" => c::single;
198 "reduce" "<afj?:j>" ["sequence", "func", "init"] 3 HigherOrder "Folds an array into a single value with a function" => c::reduce;
199 "sift" "<o-f?:o>" ["arg", "func"] 2 HigherOrder "The key/value pairs of an object for which a predicate holds" => c::sift;
200
201 "keys" "<x-:a<s>>" ["arg"] 1 Object "The keys of an object, or the union of keys of an array of objects" => c::keys;
203 "lookup" "<x-s:x>" ["input", "key"] 2 Object "The value of a key in an object, or in each object of an array" => |_, a, _| {
204 Ok(lookup(&arg(&a, 0), text(&a, 1).as_deref().unwrap_or_default()))
205 };
206 "spread" "<x-:a<o>>" ["arg"] 1 Object "Splits an object into an array of single-key objects" => c::spread;
207 "merge" "<a<o>:o>" ["arg"] 1 Object "Merges an array of objects into one; later keys win" => c::merge;
208 "each" "<o-f:a>" ["obj", "func"] 2 Object "Applies a function to each key/value pair of an object" => c::each;
209
210 "append" "<xx:a>" ["arg1", "arg2"] 2 Array "Concatenates two values into an array" => |_, a, _| Ok(append(arg(&a, 0), arg(&a, 1)));
212 "reverse" "<a:a>" ["arr"] 1 Array "Reverses an array" => c::reverse;
213 "sort" "<af?:a>" ["arr", "comparator"] 2 Array "Sorts an array, optionally with a comparator function" => c::sort;
214 "shuffle" "<a:a>" ["arr"] 1 Array "Shuffles an array into a random order" => c::shuffle;
215 "distinct" "<x:x>" ["arr"] 1 Array "Removes duplicate values from an array" => c::distinct;
216
217 "error" "<s?:x>" ["message"] 1 Misc "Raises an error (D3137) with a message" => c::error;
219 "assert" "<bs?:x>" ["condition", "message"] 2 Misc "Raises an error (D3141) when a condition is false" => c::assert;
220 "type" "<x:s>" ["value"] 1 Misc "The type of a value: null, number, string, boolean, array, object or function" => c::type_;
221 "eval" "<sx?:x>" ["expr", "focus"] 2 Misc "Parses and evaluates a JSONata expression given as a string" => c::eval;
222 "clone" "<(oa)-:o>" ["arg"] 1 Misc "A deep copy of a value" => |ev, a, _| clone(&arg(&a, 0), ev.max_string_length());
223
224 "base64encode" "<s-:s>" ["str"] 1 Encoding "Encodes a string as Base64 (each character taken as a byte)" => s::base64encode;
226 "base64decode" "<s-:s>" ["str"] 1 Encoding "Decodes a Base64 string (each byte becomes a character)" => s::base64decode;
227 "encodeUrlComponent" "<s-:s>" ["str"] 1 Encoding "Percent-encodes a URL component" => s::encode_url_component;
228 "encodeUrl" "<s-:s>" ["str"] 1 Encoding "Percent-encodes a URL, leaving reserved characters" => s::encode_url;
229 "decodeUrlComponent" "<s-:s>" ["str"] 1 Encoding "Decodes a percent-encoded URL component" => s::decode_url_component;
230 "decodeUrl" "<s-:s>" ["str"] 1 Encoding "Decodes a percent-encoded URL, leaving reserved characters encoded" => s::decode_url;
231
232 "now" "<s?s?:s>" ["picture", "timezone"] 2 DateTime "The evaluation's timestamp as an ISO 8601 string, or formatted with a picture" => |ev, a, _| {
234 crate::datetime::from_millis(ev.timestamp(), text(&a, 0).as_deref(), text(&a, 1).as_deref(), ev.max_string_length()).map(Value::string)
235 };
236 "millis" "<:n>" [] 0 DateTime "The evaluation's timestamp in milliseconds since the epoch" => |ev, _, _| Ok(Value::Number(ev.timestamp()));
237 "fromMillis" "<n-s?s?:s>" ["millis", "picture", "timezone"] 3 DateTime "Formats milliseconds since the epoch as a timestamp string" => |ev, a, _| match num(&a, 0) {
238 None => Ok(Value::Undefined),
239 Some(ms) => crate::datetime::from_millis(ms, text(&a, 1).as_deref(), text(&a, 2).as_deref(), ev.max_string_length()).map(Value::string),
240 };
241 "toMillis" "<s-s?:n>" ["timestamp", "picture"] 2 DateTime "Parses a timestamp string into milliseconds since the epoch" => |ev, a, _| match text(&a, 0) {
242 None => Ok(Value::Undefined),
243 Some(ts) => crate::datetime::to_millis(&ts, text(&a, 1).as_deref(), ev.timestamp(), |re, s| ev.find_regex(re, s, 0))
244 .map(|v| v.map_or(Value::Undefined, Value::Number)),
245 };
246}
247
248fn table() -> &'static [Native] {
249 static TABLE: OnceLock<Vec<Native>> = OnceLock::new();
250 TABLE.get_or_init(build)
251}
252
253pub(crate) fn native(name: &str) -> Option<&'static Native> {
255 table().iter().find(|n| n.name == name)
256}
257
258pub fn catalog() -> Vec<FunctionInfo> {
260 table()
261 .iter()
262 .map(|n| FunctionInfo {
263 name: n.name,
264 signature: n
265 .signature
266 .as_ref()
267 .map(|s| s.definition.clone())
268 .unwrap_or_default(),
269 params: n.params,
270 category: n.category,
271 summary: n.summary,
272 })
273 .collect()
274}
275
276pub(crate) fn native_value<'a>(name: &str) -> Option<Value<'a>> {
278 native(name).map(|native| Value::function(Function::Native(native)))
279}
280
281pub(crate) fn boolean<'a>(v: &Value<'a>) -> Result<Option<bool>> {
286 let mut v = v;
288 while let Value::Array(a) = v
289 && a.items().len() == 1
290 {
291 v = &a.items()[0];
292 }
293 Ok(Some(match v {
294 Value::Undefined => return Ok(None),
295 Value::Array(a) => {
297 let mut any = false;
298 value::for_each_leaf(a.items(), |item| {
299 any |= boolean(item)? == Some(true);
300 Ok::<_, Error>(())
301 })?;
302 any
303 }
304 Value::String(s) => !s.is_empty(),
305 Value::Number(n) => v.is_numeric()? && *n != 0.0,
306 Value::Object(o) => !o.borrow().is_empty(),
307 Value::Bool(b) => *b,
308 Value::Null | Value::Function(_) => false,
309 }))
310}
311
312pub(crate) fn lookup<'a>(input: &Value<'a>, key: &str) -> Value<'a> {
314 match input {
315 Value::Array(a) => {
316 let mut out = Vec::new();
317 let Ok(()) = value::for_each_leaf(a.items(), |item| {
318 match lookup(item, key) {
319 Value::Undefined => {}
320 Value::Array(inner) => out.extend(inner.items().iter().cloned()),
321 other => out.push(other),
322 }
323 Ok::<_, std::convert::Infallible>(())
324 });
325 Value::sequence(out)
326 }
327 Value::Object(o) => o.get(key),
328 _ => Value::Undefined,
329 }
330}
331
332pub(crate) fn append<'a>(a: Value<'a>, b: Value<'a>) -> Value<'a> {
334 if a.is_undefined() {
335 return b;
336 }
337 if b.is_undefined() {
338 return a;
339 }
340 let mut items = a.items_or_self();
341 match &b {
342 Value::Array(arr) => items.extend(arr.items().iter().cloned()),
343 other => items.push(other.clone()),
344 }
345 Value::array(items, 0)
346}
347
348pub(crate) fn to_string<'a>(v: &Value<'a>, pretty: bool, limit: usize) -> Result<Option<String>> {
350 Ok(Some(match v {
351 Value::Undefined => return Ok(None),
352 Value::String(s) => s.to_string(),
353 Value::Function(_) => String::new(),
354 Value::Number(n) if !n.is_finite() => {
355 return Err(Error::new("D3001").with("value", value::json_number(*n)));
356 }
357 other => value::stringify(other, pretty, limit)?,
358 }))
359}
360
361fn sum<'a>(items: &Value<'a>) -> Result<Value<'a>> {
362 let Value::Array(a) = items else {
363 return Ok(Value::Undefined);
364 };
365 value::finite(a.items().iter().filter_map(Value::as_number).sum())
366}
367
368fn extreme<'a>(items: &Value<'a>, pick: fn(f64, f64) -> f64) -> Result<Value<'a>> {
369 match items {
370 Value::Array(a) if !a.items().is_empty() => Ok(Value::Number(
371 a.items()
372 .iter()
373 .filter_map(Value::as_number)
374 .reduce(pick)
375 .unwrap_or(f64::NAN),
376 )),
377 _ => Ok(Value::Undefined),
378 }
379}
380
381pub(crate) fn clone<'a>(v: &Value<'a>, limit: usize) -> Result<Value<'a>> {
384 let Some(text) = to_string(v, false, limit)? else {
385 return Ok(Value::Undefined);
386 };
387 let json: serde_json::Value = serde_json::from_str(&text)
388 .map_err(|e| Error::new("D3141").with("message", e.to_string()))?;
389 Ok(Value::from_json(&json))
390}
391
392pub(crate) fn merge_sort<T: Clone>(
394 items: Vec<T>,
395 swap: &mut dyn FnMut(&T, &T) -> Result<bool>,
396) -> Result<Vec<T>> {
397 if items.len() <= 1 {
398 return Ok(items);
399 }
400 let mut left = items;
401 let right = left.split_off(left.len() / 2);
402 let left = merge_sort(left, swap)?;
403 let right = merge_sort(right, swap)?;
404 let mut out = Vec::with_capacity(left.len() + right.len());
405 let (mut i, mut j) = (0, 0);
406 while i < left.len() && j < right.len() {
407 if swap(&left[i], &right[j])? {
408 out.push(right[j].clone());
409 j += 1;
410 } else {
411 out.push(left[i].clone());
412 i += 1;
413 }
414 }
415 out.extend_from_slice(&left[i..]);
416 out.extend_from_slice(&right[j..]);
417 Ok(out)
418}
419
420pub(crate) fn hof_args<'a>(
423 func: &Value<'a>,
424 a1: Value<'a>,
425 a2: Value<'a>,
426 a3: Value<'a>,
427) -> Vec<Value<'a>> {
428 let arity = match func {
429 Value::Function(f) => f.arity(),
430 _ => 1,
431 };
432 let mut args = vec![a1];
433 if arity >= 2 {
434 args.push(a2);
435 }
436 if arity >= 3 {
437 args.push(a3);
438 }
439 args
440}