use base64::Engine;
use chrono::{TimeZone, Utc};
use rand::Rng;
use std::borrow::{Borrow, Cow};
use std::collections::HashSet;
use crate::datetime::{format_custom_date, parse_custom_format, parse_timezone_offset};
use crate::parser::expressions::check_balanced_brackets;
use bumpalo::collections::CollectIn;
use bumpalo::collections::Vec as BumpVec;
use bumpalo::Bump;
use crate::{Error, Result};
use super::frame::Frame;
use super::value::serialize::{DumpFormatter, PrettyFormatter, Serializer};
use super::value::{ArrayFlags, Value};
use super::Evaluator;
macro_rules! min_args {
($context:ident, $args:ident, $min:literal) => {
if $args.len() < $min {
return Err(Error::T0410ArgumentNotValid(
$context.char_index,
$min,
$context.name.to_string(),
));
}
};
}
macro_rules! max_args {
($context:ident, $args:ident, $max:literal) => {
if $args.len() > $max {
return Err(Error::T0410ArgumentNotValid(
$context.char_index,
$max,
$context.name.to_string(),
));
}
};
}
macro_rules! bad_arg {
($context:ident, $index:literal) => {
return Err(Error::T0410ArgumentNotValid(
$context.char_index,
$index,
$context.name.to_string(),
))
};
}
macro_rules! assert_arg {
($condition: expr, $context:ident, $index:literal) => {
if !($condition) {
bad_arg!($context, $index);
}
};
}
macro_rules! assert_array_of_type {
($condition:expr, $context:ident, $index:literal, $t:literal) => {
if !($condition) {
return Err(Error::T0412ArgumentMustBeArrayOfType(
$context.char_index,
$index,
$context.name.to_string(),
$t.to_string(),
));
};
};
}
#[derive(Clone)]
pub struct FunctionContext<'a, 'e> {
pub name: &'a str,
pub char_index: usize,
pub input: &'a Value<'a>,
pub frame: Frame<'a>,
pub evaluator: &'e Evaluator<'a>,
pub arena: &'a Bump,
}
impl<'a, 'e> FunctionContext<'a, 'e> {
pub fn evaluate_function(
&self,
proc: &'a Value<'a>,
args: &[&'a Value<'a>],
) -> Result<&'a Value<'a>> {
self.evaluator
.apply_function(self.char_index, self.input, proc, args, &self.frame)
}
}
pub fn fn_append_internal<'a>(values: &mut BumpVec<&'a Value<'a>>, value: &'a Value<'a>) {
if value.is_undefined() {
return;
}
match value {
Value::Array(a, _) => values.extend_from_slice(a),
Value::Range(_) => values.extend(value.members()),
_ => values.push(value),
}
}
pub fn fn_append<'a>(
context: FunctionContext<'a, '_>,
args: &[&'a Value<'a>],
) -> Result<&'a Value<'a>> {
let arg1 = args.first().copied().unwrap_or_else(Value::undefined);
let arg2 = args.get(1).copied().unwrap_or_else(Value::undefined);
if arg1.is_undefined() {
return Ok(arg2);
}
if arg2.is_undefined() {
return Ok(arg1);
}
let arg1_len = if arg1.is_array() { arg1.len() } else { 1 };
let arg2_len = if arg2.is_array() { arg2.len() } else { 1 };
let result = Value::array_with_capacity(
context.arena,
arg1_len + arg2_len,
if arg1.is_array() {
arg1.get_flags()
} else {
ArrayFlags::SEQUENCE
},
);
if arg1.is_array() {
arg1.members().for_each(|m| result.push(m));
} else {
result.push(arg1);
}
if arg2.is_array() {
arg2.members().for_each(|m| result.push(m));
} else {
result.push(arg2)
}
Ok(result)
}
pub fn fn_boolean<'a>(
context: FunctionContext<'a, '_>,
args: &[&'a Value<'a>],
) -> Result<&'a Value<'a>> {
max_args!(context, args, 1);
let arg = args.first().copied().unwrap_or_else(Value::undefined);
Ok(match arg {
Value::Undefined => Value::undefined(),
Value::Null => Value::bool(false),
Value::Bool(b) => Value::bool(*b),
Value::Number(n) => {
arg.is_valid_number()?;
Value::bool(*n != 0.0)
}
Value::String(ref str) => Value::bool(!str.is_empty()),
Value::Object(ref obj) => Value::bool(!obj.is_empty()),
Value::Array { .. } => match arg.len() {
0 => Value::bool(false),
1 => fn_boolean(context.clone(), &[arg.get_member(0)])?,
_ => {
for item in arg.members() {
if fn_boolean(context.clone(), &[item])?.as_bool() {
return Ok(Value::bool(true));
}
}
Value::bool(false)
}
},
Value::Lambda { .. } | Value::NativeFn { .. } | Value::Transformer { .. } => {
Value::bool(false)
}
Value::Range(ref range) => Value::bool(!range.is_empty()),
})
}
pub fn fn_map<'a>(
context: FunctionContext<'a, '_>,
args: &[&'a Value<'a>],
) -> Result<&'a Value<'a>> {
let arr = args.first().copied().unwrap_or_else(Value::undefined);
let func = args.get(1).copied().unwrap_or_else(Value::undefined);
if arr.is_undefined() {
return Ok(Value::undefined());
}
let arr = Value::wrap_in_array_if_needed(context.arena, arr, ArrayFlags::empty());
assert_arg!(func.is_function(), context, 2);
let result = Value::array(context.arena, ArrayFlags::SEQUENCE);
for (index, item) in arr.members().enumerate() {
let mut args = Vec::new();
let arity = func.arity();
args.push(item);
if arity >= 2 {
args.push(Value::number(context.arena, index as f64));
}
if arity >= 3 {
args.push(arr);
}
let mapped = context.evaluate_function(func, &args)?;
if !mapped.is_undefined() {
result.push(mapped);
}
}
Ok(result)
}
pub fn fn_filter<'a>(
context: FunctionContext<'a, '_>,
args: &[&'a Value<'a>],
) -> Result<&'a Value<'a>> {
let arr = args.first().copied().unwrap_or_else(Value::undefined);
let func = args.get(1).copied().unwrap_or_else(Value::undefined);
if arr.is_undefined() {
return Ok(Value::undefined());
}
let arr = Value::wrap_in_array_if_needed(context.arena, arr, ArrayFlags::empty());
assert_arg!(func.is_function(), context, 2);
let result = Value::array(context.arena, ArrayFlags::SEQUENCE);
for (index, item) in arr.members().enumerate() {
let mut args = Vec::new();
let arity = func.arity();
args.push(item);
if arity >= 2 {
args.push(Value::number(context.arena, index as f64));
}
if arity >= 3 {
args.push(arr);
}
let include = context.evaluate_function(func, &args)?;
if include.is_truthy() {
result.push(item);
}
}
Ok(result)
}
pub fn fn_each<'a>(
context: FunctionContext<'a, '_>,
args: &[&'a Value<'a>],
) -> Result<&'a Value<'a>> {
let (obj, func) = if args.len() == 1 {
let obj_arg = if context.input.is_array() && context.input.has_flags(ArrayFlags::WRAPPED) {
&context.input[0]
} else {
context.input
};
(obj_arg, args[0])
} else {
(args[0], args[1])
};
if obj.is_undefined() {
return Ok(Value::undefined());
}
assert_arg!(obj.is_object(), context, 1);
assert_arg!(func.is_function(), context, 2);
let result = Value::array(context.arena, ArrayFlags::SEQUENCE);
for (key, value) in obj.entries() {
let key = Value::string(context.arena, key);
let mapped = context.evaluate_function(func, &[value, key])?;
if !mapped.is_undefined() {
result.push(mapped);
}
}
Ok(result)
}
pub fn fn_keys<'a>(
context: FunctionContext<'a, '_>,
args: &[&'a Value<'a>],
) -> Result<&'a Value<'a>> {
let obj = if args.is_empty() {
if context.input.is_array() && context.input.has_flags(ArrayFlags::WRAPPED) {
&context.input[0]
} else {
context.input
}
} else {
args[0]
};
if obj.is_undefined() {
return Ok(Value::undefined());
}
let mut keys = Vec::new();
if obj.is_array() && obj.members().all(|member| member.is_object()) {
for sub_object in obj.members() {
for (key, _) in sub_object.entries() {
if !keys.iter().any(|item| item == key) {
keys.push(key.to_string());
}
}
}
} else if obj.is_object() {
for (key, _) in obj.entries() {
keys.push(key.to_string());
}
}
let result = Value::array(context.arena, ArrayFlags::SEQUENCE);
for key in keys {
result.push(Value::string(context.arena, &key));
}
Ok(result)
}
pub fn fn_merge<'a>(
context: FunctionContext<'a, '_>,
args: &[&'a Value<'a>],
) -> Result<&'a Value<'a>> {
let mut array_of_objects = if args.is_empty() {
if context.input.is_array() && context.input.has_flags(ArrayFlags::WRAPPED) {
&context.input[0]
} else {
context.input
}
} else {
args[0]
};
if array_of_objects.is_undefined() {
return Ok(Value::undefined());
}
if array_of_objects.is_object() {
array_of_objects =
Value::wrap_in_array(context.arena, array_of_objects, ArrayFlags::empty());
}
assert_arg!(
array_of_objects.is_array() && array_of_objects.members().all(|member| member.is_object()),
context,
1
);
let result = Value::object(context.arena);
for obj in array_of_objects.members() {
for (key, value) in obj.entries() {
result.insert(key, value);
}
}
Ok(result)
}
pub fn fn_string<'a>(
context: FunctionContext<'a, '_>,
args: &[&'a Value<'a>],
) -> Result<&'a Value<'a>> {
max_args!(context, args, 2);
let input = if args.is_empty() {
if context.input.is_array() && context.input.has_flags(ArrayFlags::WRAPPED) {
&context.input[0]
} else {
context.input
}
} else {
args.first().copied().unwrap_or_else(Value::undefined)
};
if input.is_undefined() {
return Ok(Value::undefined());
}
let pretty = args.get(1).copied().unwrap_or_else(Value::undefined);
assert_arg!(pretty.is_undefined() || pretty.is_bool(), context, 2);
if input.is_string() {
Ok(input)
} else if input.is_function() {
Ok(Value::string(context.arena, ""))
} else if input.is_number() && !input.is_finite() {
Err(Error::D3001StringNotFinite(context.char_index))
} else if *pretty == true {
let serializer = Serializer::new(PrettyFormatter::default(), true);
let output = serializer.serialize(input)?;
Ok(Value::string(context.arena, &output))
} else {
let serializer = Serializer::new(DumpFormatter, true);
let output = serializer.serialize(input)?;
Ok(Value::string(context.arena, &output))
}
}
pub fn fn_substring_before<'a>(
context: FunctionContext<'a, '_>,
args: &[&'a Value<'a>],
) -> Result<&'a Value<'a>> {
let string = args.first().copied().unwrap_or_else(Value::undefined);
let chars = args.get(1).copied().unwrap_or_else(Value::undefined);
if !string.is_string() {
return Ok(Value::undefined());
}
if !chars.is_string() {
return Err(Error::D3010EmptyPattern(context.char_index));
}
let string: &str = &string.as_str();
let chars: &str = &chars.as_str();
if let Some(index) = string.find(chars) {
Ok(Value::string(context.arena, &string[..index]))
} else {
Ok(Value::string(context.arena, string))
}
}
pub fn fn_substring_after<'a>(
context: FunctionContext<'a, '_>,
args: &[&'a Value<'a>],
) -> Result<&'a Value<'a>> {
let string = args.first().copied().unwrap_or_else(Value::undefined);
let chars = args.get(1).copied().unwrap_or_else(Value::undefined);
if !string.is_string() {
return Ok(Value::undefined());
}
if !chars.is_string() {
return Err(Error::D3010EmptyPattern(context.char_index));
}
let string: &str = &string.as_str();
let chars: &str = &chars.as_str();
if let Some(index) = string.find(chars) {
let after_index = index + chars.len();
Ok(Value::string(context.arena, &string[after_index..]))
} else {
Ok(Value::string(context.arena, string))
}
}
pub fn fn_not<'a>(
_context: FunctionContext<'a, '_>,
args: &[&'a Value<'a>],
) -> Result<&'a Value<'a>> {
let arg = args.first().copied().unwrap_or_else(Value::undefined);
Ok(if arg.is_undefined() {
Value::undefined()
} else {
Value::bool(!arg.is_truthy())
})
}
pub fn fn_lowercase<'a>(
context: FunctionContext<'a, '_>,
args: &[&'a Value<'a>],
) -> Result<&'a Value<'a>> {
let arg = args.first().copied().unwrap_or_else(Value::undefined);
Ok(if !arg.is_string() {
Value::undefined()
} else {
Value::string(context.arena, &arg.as_str().to_lowercase())
})
}
pub fn fn_uppercase<'a>(
context: FunctionContext<'a, '_>,
args: &[&'a Value<'a>],
) -> Result<&'a Value<'a>> {
let arg = args.first().copied().unwrap_or_else(Value::undefined);
if !arg.is_string() {
Ok(Value::undefined())
} else {
Ok(Value::string(context.arena, &arg.as_str().to_uppercase()))
}
}
pub fn fn_trim<'a>(
context: FunctionContext<'a, '_>,
args: &[&'a Value<'a>],
) -> Result<&'a Value<'a>> {
let arg = args.first().copied().unwrap_or_else(Value::undefined);
if !arg.is_string() {
Ok(Value::undefined())
} else {
let orginal = arg.as_str();
let mut words = orginal.split_whitespace();
let trimmed = match words.next() {
None => String::new(),
Some(first_word) => {
let (lower, _) = words.size_hint();
let mut result = String::with_capacity(lower);
result.push_str(first_word);
for word in words {
result.push(' ');
result.push_str(word);
}
result
}
};
Ok(Value::string(context.arena, &trimmed))
}
}
pub fn fn_substring<'a>(
context: FunctionContext<'a, '_>,
args: &[&'a Value<'a>],
) -> Result<&'a Value<'a>> {
let string = args.first().copied().unwrap_or_else(Value::undefined);
let start = args.get(1).copied().unwrap_or_else(Value::undefined);
let length = args.get(2).copied().unwrap_or_else(Value::undefined);
if string.is_undefined() {
return Ok(Value::undefined());
}
assert_arg!(string.is_string(), context, 1);
assert_arg!(start.is_number(), context, 2);
let string = string.as_str();
let len = string.chars().count() as isize;
let mut start = start.as_isize();
if len + start < 0 {
start = 0;
}
let start = if start < 0 { len + start } else { start };
if length.is_undefined() {
Ok(Value::string(context.arena, &string[start as usize..]))
} else {
assert_arg!(length.is_number(), context, 3);
let length = length.as_isize();
if length < 0 {
Ok(Value::string(context.arena, ""))
} else {
let end = if start >= 0 {
(start + length) as usize
} else {
(len + start + length) as usize
};
let substring = string
.chars()
.skip(start as usize)
.take(end - start as usize)
.collect::<String>();
Ok(Value::string(context.arena, &substring))
}
}
}
pub fn fn_contains<'a>(
context: FunctionContext<'a, '_>,
args: &[&'a Value<'a>],
) -> Result<&'a Value<'a>> {
let str_value = args.first().copied().unwrap_or_else(Value::undefined);
let token_value = args.get(1).copied().unwrap_or_else(Value::undefined);
if str_value.is_undefined() {
return Ok(Value::undefined());
}
assert_arg!(str_value.is_string(), context, 1);
assert_arg!(token_value.is_string(), context, 2);
let str_value = str_value.as_str();
let token_value = token_value.as_str();
Ok(Value::bool(str_value.contains(&token_value.to_string())))
}
pub fn fn_replace<'a>(
context: FunctionContext<'a, '_>,
args: &[&'a Value<'a>],
) -> Result<&'a Value<'a>> {
let str_value = args.first().copied().unwrap_or_else(Value::undefined);
let pattern_value = args.get(1).copied().unwrap_or_else(Value::undefined);
let replacement_value = args.get(2).copied().unwrap_or_else(Value::undefined);
let limit_value = args.get(3).copied().unwrap_or_else(Value::undefined);
if str_value.is_undefined() {
return Ok(Value::undefined());
}
if pattern_value.is_string() && pattern_value.as_str().is_empty() {
return Err(Error::D3010EmptyPattern(context.char_index));
}
assert_arg!(str_value.is_string(), context, 1);
assert_arg!(pattern_value.is_string(), context, 2);
assert_arg!(replacement_value.is_string(), context, 3);
let str_value = str_value.as_str();
let pattern_value = pattern_value.as_str();
let replacement_value = replacement_value.as_str();
let limit_value = if limit_value.is_undefined() {
None
} else {
assert_arg!(limit_value.is_number(), context, 4);
if limit_value.as_isize().is_negative() {
return Err(Error::D3011NegativeLimit(context.char_index));
}
Some(limit_value.as_isize())
};
let replaced_string = if let Some(limit) = limit_value {
str_value.replacen(
&pattern_value.to_string(),
&replacement_value,
limit as usize,
)
} else {
str_value.replace(&pattern_value.to_string(), &replacement_value)
};
Ok(Value::string(context.arena, &replaced_string))
}
pub fn fn_split<'a>(
context: FunctionContext<'a, '_>,
args: &[&'a Value<'a>],
) -> Result<&'a Value<'a>> {
let str_value = args.first().copied().unwrap_or_else(Value::undefined);
let separator_value = args.get(1).copied().unwrap_or_else(Value::undefined);
let limit_value = args.get(2).copied().unwrap_or_else(Value::undefined);
if str_value.is_undefined() {
return Ok(Value::undefined());
}
assert_arg!(str_value.is_string(), context, 1);
assert_arg!(separator_value.is_string(), context, 2);
let str_value = str_value.as_str();
let separator_value = separator_value.as_str();
let limit_value = if limit_value.is_undefined() {
None
} else {
assert_arg!(limit_value.is_number(), context, 4);
if limit_value.as_isize().is_negative() {
return Err(Error::D3020NegativeLimit(context.char_index));
}
Some(limit_value.as_isize())
};
let substrings: Vec<&str> = if let Some(limit) = limit_value {
str_value
.split(&separator_value.to_string())
.take(limit as usize)
.collect()
} else {
str_value.split(&separator_value.to_string()).collect()
};
let substrings_count = substrings.len();
let result = Value::array_with_capacity(context.arena, substrings_count, ArrayFlags::empty());
for (index, substring) in substrings.into_iter().enumerate() {
if substring.is_empty() && (index == 0 || index == substrings_count - 1) {
continue;
}
result.push(Value::string(context.arena, substring));
}
Ok(result)
}
pub fn fn_abs<'a>(
context: FunctionContext<'a, '_>,
args: &[&'a Value<'a>],
) -> Result<&'a Value<'a>> {
let arg = args.first().copied().unwrap_or_else(Value::undefined);
if arg.is_undefined() {
return Ok(Value::undefined());
}
assert_arg!(arg.is_number(), context, 1);
Ok(Value::number(context.arena, arg.as_f64().abs()))
}
pub fn fn_floor<'a>(
context: FunctionContext<'a, '_>,
args: &[&'a Value<'a>],
) -> Result<&'a Value<'a>> {
let arg = args.first().copied().unwrap_or_else(Value::undefined);
if arg.is_undefined() {
return Ok(Value::undefined());
}
assert_arg!(arg.is_number(), context, 1);
Ok(Value::number(context.arena, arg.as_f64().floor()))
}
pub fn fn_ceil<'a>(
context: FunctionContext<'a, '_>,
args: &[&'a Value<'a>],
) -> Result<&'a Value<'a>> {
let arg = args.first().copied().unwrap_or_else(Value::undefined);
if arg.is_undefined() {
return Ok(Value::undefined());
}
assert_arg!(arg.is_number(), context, 1);
Ok(Value::number(context.arena, arg.as_f64().ceil()))
}
pub fn fn_lookup_internal<'a>(
context: FunctionContext<'a, '_>,
input: &'a Value<'a>,
key: &str,
) -> &'a Value<'a> {
match input {
Value::Array { .. } => {
let result = Value::array(context.arena, ArrayFlags::SEQUENCE);
for input in input.members() {
let res = fn_lookup_internal(context.clone(), input, key);
match res {
Value::Undefined => {}
Value::Array { .. } => {
res.members().for_each(|item| result.push(item));
}
_ => result.push(res),
};
}
result
}
Value::Object(..) => input.get_entry(key),
_ => Value::undefined(),
}
}
pub fn fn_lookup<'a>(
context: FunctionContext<'a, '_>,
args: &[&'a Value<'a>],
) -> Result<&'a Value<'a>> {
let input = args.first().copied().unwrap_or_else(Value::undefined);
let key = args.get(1).copied().unwrap_or_else(Value::undefined);
assert_arg!(key.is_string(), context, 2);
Ok(fn_lookup_internal(context.clone(), input, &key.as_str()))
}
pub fn fn_count<'a>(
context: FunctionContext<'a, '_>,
args: &[&'a Value<'a>],
) -> Result<&'a Value<'a>> {
max_args!(context, args, 1);
let count = match args.first() {
Some(Value::Array(a, _)) => a.len() as f64,
Some(Value::Undefined) => 0.0,
Some(_) => 1.0,
None => 0.0,
};
Ok(Value::number(context.arena, count))
}
pub fn fn_max<'a>(
context: FunctionContext<'a, '_>,
args: &[&'a Value<'a>],
) -> Result<&'a Value<'a>> {
max_args!(context, args, 1);
let arg = args.first().copied().unwrap_or_else(Value::undefined);
if arg.is_undefined() || (arg.is_array() && arg.is_empty()) {
return Ok(Value::undefined());
}
let arr = Value::wrap_in_array_if_needed(context.arena, arg, ArrayFlags::empty());
let mut max = f64::MIN;
for member in arr.members() {
assert_array_of_type!(member.is_number(), context, 1, "number");
max = f64::max(max, member.as_f64());
}
Ok(Value::number(context.arena, max))
}
pub fn fn_min<'a>(
context: FunctionContext<'a, '_>,
args: &[&'a Value<'a>],
) -> Result<&'a Value<'a>> {
max_args!(context, args, 1);
let arg = args.first().copied().unwrap_or_else(Value::undefined);
if arg.is_undefined() || (arg.is_array() && arg.is_empty()) {
return Ok(Value::undefined());
}
let arr = Value::wrap_in_array_if_needed(context.arena, arg, ArrayFlags::empty());
let mut min = f64::MAX;
for member in arr.members() {
assert_array_of_type!(member.is_number(), context, 1, "number");
min = f64::min(min, member.as_f64());
}
Ok(Value::number(context.arena, min))
}
pub fn fn_sum<'a>(
context: FunctionContext<'a, '_>,
args: &[&'a Value<'a>],
) -> Result<&'a Value<'a>> {
max_args!(context, args, 1);
let arg = args.first().copied().unwrap_or_else(Value::undefined);
if arg.is_undefined() {
return Ok(Value::undefined());
}
let arr = Value::wrap_in_array_if_needed(context.arena, arg, ArrayFlags::empty());
let mut sum = 0.0;
for member in arr.members() {
assert_array_of_type!(member.is_number(), context, 1, "number");
sum += member.as_f64();
}
Ok(Value::number(context.arena, sum))
}
pub fn fn_number<'a>(
context: FunctionContext<'a, '_>,
args: &[&'a Value<'a>],
) -> Result<&'a Value<'a>> {
max_args!(context, args, 1);
let arg = args.first().copied().unwrap_or_else(Value::undefined);
match arg {
Value::Undefined => Ok(Value::undefined()),
Value::Number(..) => Ok(arg),
Value::Bool(true) => Ok(Value::number(context.arena, 1)),
Value::Bool(false) => Ok(Value::number(context.arena, 0)),
Value::String(s) => {
let result: f64 = s
.parse()
.map_err(|_e| Error::D3030NonNumericCast(context.char_index, arg.to_string()))?;
if !result.is_nan() && !result.is_infinite() {
Ok(Value::number(context.arena, result))
} else {
Ok(Value::undefined())
}
}
_ => bad_arg!(context, 1),
}
}
pub fn fn_random<'a>(
context: FunctionContext<'a, '_>,
args: &[&'a Value<'a>],
) -> Result<&'a Value<'a>> {
max_args!(context, args, 0);
let v: f32 = rand::thread_rng().gen();
Ok(Value::number(context.arena, v))
}
pub fn fn_now<'a>(
context: FunctionContext<'a, '_>,
args: &[&'a Value<'a>],
) -> Result<&'a Value<'a>> {
max_args!(context, args, 2);
let now = Utc::now();
let (picture, timezone) = match args {
[picture, timezone] => (picture.as_str(), timezone.as_str()),
[picture] => (picture.as_str(), Cow::Borrowed("")),
[] => (Cow::Borrowed(""), Cow::Borrowed("")),
_ => return Ok(Value::string(context.arena, "")),
};
if picture.is_empty() && timezone.is_empty() {
return Ok(Value::string(
context.arena,
&now.to_rfc3339_opts(chrono::SecondsFormat::Millis, true),
));
}
let adjusted_time = if !timezone.is_empty() {
parse_timezone_offset(&timezone)
.map(|offset| now.with_timezone(&offset))
.ok_or_else(|| Error::T0410ArgumentNotValid(2, 1, context.name.to_string()))?
} else {
now.into()
};
if !picture.is_empty() {
let formatted_date = format_custom_date(&adjusted_time, &picture)?;
return Ok(Value::string(context.arena, &formatted_date));
}
Ok(Value::string(context.arena, ""))
}
pub fn fn_exists<'a>(
context: FunctionContext<'a, '_>,
args: &[&'a Value<'a>],
) -> Result<&'a Value<'a>> {
min_args!(context, args, 1);
max_args!(context, args, 1);
let arg = args.first().copied().unwrap_or_else(Value::undefined);
match arg {
Value::Undefined => Ok(Value::bool(false)),
_ => Ok(Value::bool(true)),
}
}
pub fn from_millis<'a>(
context: FunctionContext<'a, '_>,
args: &[&'a Value<'a>],
) -> Result<&'a Value<'a>> {
let arr = args.first().copied().unwrap_or_else(Value::undefined);
if arr.is_undefined() {
return Ok(Value::undefined());
}
max_args!(context, args, 3);
let millis = args[0].as_f64() as i64;
let timestamp = Utc
.timestamp_millis_opt(millis)
.single()
.ok_or_else(|| Error::T0410ArgumentNotValid(0, 1, context.name.to_string()))?;
let (picture, timezone) = match args {
[_, picture, timezone] => (
if picture.is_string() {
picture.as_str()
} else {
Cow::Borrowed("") },
timezone.as_str(),
),
[_, picture] => (
if picture.is_string() {
picture.as_str()
} else {
Cow::Borrowed("")
},
Cow::Borrowed(""),
),
_ => (Cow::Borrowed(""), Cow::Borrowed("")),
};
if picture.is_empty() && timezone.is_empty() {
return Ok(Value::string(
context.arena,
×tamp.to_rfc3339_opts(chrono::SecondsFormat::Millis, true),
));
}
if let Err(err) = check_balanced_brackets(&picture) {
return Err(Error::D3135PictureStringNoClosingBracketError(err));
}
let adjusted_time = if !timezone.is_empty() {
parse_timezone_offset(&timezone)
.map(|offset| timestamp.with_timezone(&offset))
.ok_or_else(|| Error::T0410ArgumentNotValid(0, 1, context.name.to_string()))?
} else {
timestamp.into()
};
if !picture.is_empty() {
let formatted_result = format_custom_date(&adjusted_time, &picture)?;
return Ok(Value::string(context.arena, &formatted_result));
}
Ok(Value::string(
context.arena,
&adjusted_time.to_rfc3339_opts(chrono::SecondsFormat::Millis, true),
))
}
pub fn to_millis<'a>(
context: FunctionContext<'a, '_>,
args: &[&'a Value<'a>],
) -> Result<&'a Value<'a>> {
let arr: &Value<'a> = args.first().copied().unwrap_or_else(Value::undefined);
if arr.is_undefined() {
return Ok(Value::undefined());
}
max_args!(context, args, 2);
let timestamp_str = args[0].as_str();
if timestamp_str.is_empty() {
return Ok(Value::undefined());
}
let picture = match args {
[_, picture] => picture.as_str(),
_ => Cow::Borrowed(""),
};
match parse_custom_format(×tamp_str, &picture) {
Some(millis) => Ok(Value::number(context.arena, millis as f64)),
None => Ok(Value::undefined()),
}
}
pub fn fn_zip<'a>(
context: FunctionContext<'a, '_>,
args: &[&'a Value<'a>],
) -> Result<&'a Value<'a>> {
if args.iter().any(|arg| arg.is_null() || arg.is_undefined()) {
return Ok(Value::array(context.arena, ArrayFlags::empty()));
}
let arrays: Vec<&bumpalo::collections::Vec<'a, &'a Value<'a>>> = args
.iter()
.filter_map(|arg| match *arg {
Value::Array(arr, _) => Some(arr),
_ => None,
})
.collect();
if arrays.is_empty() {
let result: bumpalo::collections::Vec<&Value<'a>> =
args.iter().copied().collect_in(context.arena);
let outer_array =
Value::array_from(context.arena, result, ArrayFlags::empty()) as &Value<'a>;
let outer_array_alloc: bumpalo::collections::Vec<&Value<'a>> =
bumpalo::vec![in context.arena; outer_array];
return Ok(Value::array_from(
context.arena,
outer_array_alloc,
ArrayFlags::empty(),
));
}
let min_length = arrays.iter().map(|arr| arr.len()).min().unwrap_or(0);
let mut iterators: Vec<_> = arrays
.iter()
.map(|arr| arr.iter().take(min_length))
.collect();
let result: bumpalo::collections::Vec<&Value<'a>> = std::iter::repeat(())
.take(min_length)
.map(|_| {
let zipped: bumpalo::collections::Vec<&Value<'a>> = iterators
.iter_mut()
.map(|it| *it.next().unwrap()) .collect_in(context.arena);
context
.arena
.alloc(Value::Array(zipped, ArrayFlags::empty())) as &Value<'a>
})
.collect_in(context.arena);
Ok(Value::array_from(
context.arena,
result,
ArrayFlags::empty(),
))
}
pub fn single<'a>(
context: FunctionContext<'a, '_>,
args: &[&'a Value<'a>],
) -> Result<&'a Value<'a>> {
max_args!(context, args, 2);
let arr: &Value<'a> = args.first().copied().unwrap_or_else(Value::undefined);
if arr.is_undefined() {
return Ok(Value::undefined());
}
let func = args
.get(1)
.filter(|f| f.is_function())
.copied()
.unwrap_or_else(|| {
context
.arena
.alloc(Value::nativefn(context.arena, "default_true", 1, |_, _| {
Ok(&Value::Bool(true))
}))
});
if !arr.is_array() {
let res = context.evaluate_function(func, &[arr])?;
return if res.as_bool() {
Ok(arr)
} else {
Err(Error::D3139Error(
"No value matched the predicate.".to_string(),
))
};
}
if let Value::Array(elements, _) = arr {
let mut result: Option<&'a Value<'a>> = None;
for (index, entry) in elements.iter().enumerate() {
let res = context.evaluate_function(
func,
&[entry, Value::number(context.arena, index as f64), arr],
)?;
if res.as_bool() {
if result.is_some() {
return Err(Error::D3138Error(format!(
"More than one value matched the predicate at index {}",
index
)));
} else {
result = Some(entry);
}
}
}
result.ok_or_else(|| Error::D3139Error("No values matched the predicate.".to_string()))
} else {
Err(Error::T0410ArgumentNotValid(0, 2, context.name.to_string()))
}
}
pub fn fn_assert<'a>(
context: FunctionContext<'a, '_>,
args: &[&'a Value<'a>],
) -> Result<&'a Value<'a>> {
let condition = args.first().copied().unwrap_or_else(Value::undefined);
let message = args.get(1).copied().unwrap_or_else(Value::undefined);
assert_arg!(condition.is_bool(), context, 1);
if let Value::Bool(false) = condition {
Err(Error::D3141Assert(if message.is_string() {
message.as_str().to_string()
} else {
"$assert() statement failed".to_string()
}))
} else {
Ok(Value::undefined())
}
}
pub fn fn_error<'a>(
context: FunctionContext<'a, '_>,
args: &[&'a Value<'a>],
) -> Result<&'a Value<'a>> {
let message = args.first().copied().unwrap_or_else(Value::undefined);
assert_arg!(message.is_undefined() || message.is_string(), context, 1);
Err(Error::D3137Error(if message.is_string() {
message.as_str().to_string()
} else {
"$error() function evaluated".to_string()
}))
}
pub fn fn_length<'a>(
context: FunctionContext<'a, '_>,
args: &[&'a Value<'a>],
) -> Result<&'a Value<'a>> {
max_args!(context, args, 1);
let arg1 = args.first().copied().unwrap_or_else(Value::undefined);
if arg1.is_undefined() {
return Ok(Value::undefined());
}
assert_arg!(arg1.is_string(), context, 1);
Ok(Value::number(
context.arena,
arg1.as_str().chars().count() as f64,
))
}
pub fn fn_sqrt<'a>(
context: FunctionContext<'a, '_>,
args: &[&'a Value<'a>],
) -> Result<&'a Value<'a>> {
max_args!(context, args, 1);
let arg1 = args.first().copied().unwrap_or_else(Value::undefined);
if arg1.is_undefined() {
return Ok(Value::undefined());
}
assert_arg!(arg1.is_number(), context, 1);
let n = arg1.as_f64();
if n.is_sign_negative() {
Err(Error::D3060SqrtNegative(context.char_index, n.to_string()))
} else {
Ok(Value::number(context.arena, n.sqrt()))
}
}
pub fn fn_power<'a>(
context: FunctionContext<'a, '_>,
args: &[&'a Value<'a>],
) -> Result<&'a Value<'a>> {
max_args!(context, args, 2);
let number = args.first().copied().unwrap_or_else(Value::undefined);
let exp = args.get(1).copied().unwrap_or_else(Value::undefined);
if number.is_undefined() {
return Ok(Value::undefined());
}
assert_arg!(number.is_number(), context, 1);
assert_arg!(exp.is_number(), context, 2);
let result = number.as_f64().powf(exp.as_f64());
if !result.is_finite() {
Err(Error::D3061PowUnrepresentable(
context.char_index,
number.to_string(),
exp.to_string(),
))
} else {
Ok(Value::number(context.arena, result))
}
}
pub fn fn_reverse<'a>(
context: FunctionContext<'a, '_>,
args: &[&'a Value<'a>],
) -> Result<&'a Value<'a>> {
max_args!(context, args, 1);
let arr = args.first().copied().unwrap_or_else(Value::undefined);
if arr.is_undefined() {
return Ok(Value::undefined());
}
assert_arg!(arr.is_array(), context, 1);
let result = Value::array_with_capacity(context.arena, arr.len(), ArrayFlags::empty());
arr.members().rev().for_each(|member| result.push(member));
Ok(result)
}
#[allow(clippy::mutable_key_type)]
pub fn fn_distinct<'a>(
context: FunctionContext<'a, '_>,
args: &[&'a Value<'a>],
) -> Result<&'a Value<'a>> {
max_args!(context, args, 1);
let arr = args.first().copied().unwrap_or_else(Value::undefined);
if !arr.is_array() || arr.len() <= 1 {
return Ok(arr);
}
let result = Value::array_with_capacity(context.arena, arr.len(), ArrayFlags::empty());
let mut set = HashSet::new();
for member in arr.members() {
if set.contains(member) {
continue;
}
set.insert(member);
result.push(member);
}
Ok(result)
}
pub fn fn_join<'a>(
context: FunctionContext<'a, '_>,
args: &[&'a Value<'a>],
) -> Result<&'a Value<'a>> {
max_args!(context, args, 2);
let strings = args.first().copied().unwrap_or_else(Value::undefined);
if strings.is_undefined() {
return Ok(Value::undefined());
}
if strings.is_string() {
return Ok(strings);
}
assert_array_of_type!(strings.is_array(), context, 1, "string");
let separator = args.get(1).copied().unwrap_or_else(Value::undefined);
assert_arg!(
separator.is_undefined() || separator.is_string(),
context,
2
);
let separator = if separator.is_string() {
separator.as_str()
} else {
"".into()
};
let mut result = String::with_capacity(1024);
for (index, member) in strings.members().enumerate() {
assert_array_of_type!(member.is_string(), context, 1, "string");
result.push_str(member.as_str().borrow());
if index != strings.len() - 1 {
result.push_str(&separator);
}
}
Ok(Value::string(context.arena, &result))
}
pub fn fn_sort<'a, 'e>(
context: FunctionContext<'a, 'e>,
args: &[&'a Value<'a>],
) -> Result<&'a Value<'a>> {
max_args!(context, args, 2);
let arr = args.first().copied().unwrap_or_else(Value::undefined);
if arr.is_undefined() {
return Ok(Value::undefined());
}
if !arr.is_array() || arr.len() <= 1 {
return Ok(Value::wrap_in_array_if_needed(
context.arena,
arr,
ArrayFlags::empty(),
));
}
let unsorted = arr.members().collect::<Vec<&'a Value<'a>>>();
let sorted = if args.get(1).is_none() {
merge_sort(
unsorted,
&|a: &'a Value<'a>, b: &'a Value<'a>| match (a, b) {
(Value::Number(a), Value::Number(b)) => Ok(a > b),
(Value::String(a), Value::String(b)) => Ok(a > b),
_ => Err(Error::D3070InvalidDefaultSort(context.char_index)),
},
)?
} else {
let comparator = args.get(1).copied().unwrap_or_else(Value::undefined);
assert_arg!(comparator.is_function(), context, 2);
merge_sort(unsorted, &|a: &'a Value<'a>, b: &'a Value<'a>| {
let result = context.evaluate_function(comparator, &[a, b])?;
Ok(result.is_truthy())
})?
};
let result = Value::array_with_capacity(context.arena, sorted.len(), arr.get_flags());
sorted.iter().for_each(|member| result.push(member));
Ok(result)
}
pub fn merge_sort<'a, F>(items: Vec<&'a Value<'a>>, comp: &F) -> Result<Vec<&'a Value<'a>>>
where
F: Fn(&'a Value<'a>, &'a Value<'a>) -> Result<bool>,
{
fn merge_iter<'a, F>(
result: &mut Vec<&'a Value<'a>>,
left: &[&'a Value<'a>],
right: &[&'a Value<'a>],
comp: &F,
) -> Result<()>
where
F: Fn(&'a Value<'a>, &'a Value<'a>) -> Result<bool>,
{
if left.is_empty() {
result.extend(right);
Ok(())
} else if right.is_empty() {
result.extend(left);
Ok(())
} else if comp(left[0], right[0])? {
result.push(right[0]);
merge_iter(result, left, &right[1..], comp)
} else {
result.push(left[0]);
merge_iter(result, &left[1..], right, comp)
}
}
fn merge<'a, F>(
left: &[&'a Value<'a>],
right: &[&'a Value<'a>],
comp: &F,
) -> Result<Vec<&'a Value<'a>>>
where
F: Fn(&'a Value<'a>, &'a Value<'a>) -> Result<bool>,
{
let mut merged = Vec::with_capacity(left.len() + right.len());
merge_iter(&mut merged, left, right, comp)?;
Ok(merged)
}
if items.len() <= 1 {
return Ok(items);
}
let middle = (items.len() as f64 / 2.0).floor() as usize;
let (left, right) = items.split_at(middle);
let left = merge_sort(left.to_vec(), comp)?;
let right = merge_sort(right.to_vec(), comp)?;
merge(&left, &right, comp)
}
pub fn fn_base64_encode<'a>(
context: FunctionContext<'a, '_>,
args: &[&'a Value<'a>],
) -> Result<&'a Value<'a>> {
max_args!(context, args, 1);
let arg = args.first().copied().unwrap_or_else(Value::undefined);
if arg.is_undefined() {
return Ok(Value::undefined());
}
assert_arg!(arg.is_string(), context, 1);
let base64 = base64::engine::general_purpose::STANDARD;
let encoded = base64.encode(arg.as_str().as_bytes());
Ok(Value::string(context.arena, &encoded))
}
pub fn fn_base64_decode<'a>(
context: FunctionContext<'a, '_>,
args: &[&'a Value<'a>],
) -> Result<&'a Value<'a>> {
max_args!(context, args, 1);
let arg = args.first().copied().unwrap_or_else(Value::undefined);
if arg.is_undefined() {
return Ok(Value::undefined());
}
assert_arg!(arg.is_string(), context, 1);
let base64 = base64::engine::general_purpose::STANDARD;
let decoded = base64.decode(arg.as_str().as_bytes());
let data = decoded.map_err(|e| Error::D3137Error(e.to_string()))?;
let decoded = String::from_utf8(data).map_err(|e| Error::D3137Error(e.to_string()))?;
Ok(Value::string(context.arena, &decoded))
}
pub fn fn_round<'a>(
context: FunctionContext<'a, '_>,
args: &[&'a Value<'a>],
) -> Result<&'a Value<'a>> {
max_args!(context, args, 2);
let number = &args[0];
if number.is_undefined() {
return Ok(Value::undefined());
}
assert_arg!(number.is_number(), context, 1);
let precision = if let Some(precision) = args.get(1) {
assert_arg!(precision.is_integer(), context, 2);
precision.as_isize()
} else {
0
};
let num = multiply_by_pow10(number.as_f64(), precision)?;
let num = num.round_ties_even();
let num = multiply_by_pow10(num, -precision)?;
Ok(Value::number(context.arena, num))
}
fn is_array_of_strings(value: &Value) -> bool {
if let Value::Array(elements, _) = value {
elements.iter().all(|v| v.is_string())
} else {
false
}
}
pub fn fn_reduce<'a>(
context: FunctionContext<'a, '_>,
args: &[&'a Value<'a>],
) -> Result<&'a Value<'a>> {
max_args!(context, args, 3);
if args.len() < 2 {
return Err(Error::T0410ArgumentNotValid(0, 2, context.name.to_string()));
}
let original_value = args[0];
let func = args[1];
let init = args.get(2).copied();
if func.is_function() && func.arity() < 2 {
return Err(Error::D3050SecondArguement(context.name.to_string()));
}
if !original_value.is_array() {
if original_value.is_number() {
return Ok(original_value);
}
if original_value.is_string() {
return Ok(original_value);
}
return Ok(Value::undefined());
}
let (elements, _extra_field) = match original_value {
Value::Array(elems, extra) => (elems, extra),
_ => return Err(Error::D3050SecondArguement(context.name.to_string())),
};
if elements.is_empty() {
return Ok(init.unwrap_or_else(|| Value::undefined()));
}
if !func.is_function() {
return Err(Error::T0410ArgumentNotValid(1, 1, context.name.to_string()));
}
let mut accumulator = init.unwrap_or_else(|| elements[0]);
let has_init_value = init.is_some();
let is_non_single_array_of_strings = is_array_of_strings(original_value) && elements.len() > 1;
let start_index = if has_init_value || is_non_single_array_of_strings {
0
} else {
1
};
for (index, value) in elements[start_index..].iter().enumerate() {
let index_value = Value::number(context.arena, index as f64);
let result =
context.evaluate_function(func, &[accumulator, value, index_value, original_value]);
match result {
Ok(new_accumulator) => {
accumulator = new_accumulator;
}
Err(_) => {
return Err(Error::T0410ArgumentNotValid(1, 1, context.name.to_string()));
}
}
}
Ok(accumulator)
}
fn multiply_by_pow10(num: f64, pow: isize) -> Result<f64> {
let num_str = format!("{}e{}", num, pow);
num_str
.parse::<f64>()
.map_err(|e| Error::D3137Error(e.to_string()))
}
pub fn fn_pad<'a>(
context: FunctionContext<'a, '_>,
args: &[&'a Value<'a>],
) -> Result<&'a Value<'a>> {
let str_value = args.first().copied().unwrap_or_else(Value::undefined);
if !str_value.is_string() {
return Ok(Value::undefined());
}
let width_value = args.get(1).copied().unwrap_or_else(Value::undefined);
if !width_value.is_number() {
return Ok(Value::undefined());
}
let str_to_pad = str_value.as_str(); let width_i64 = width_value.as_f64().round() as i64;
let width = width_i64.unsigned_abs() as usize;
let is_right_padding = width_i64 > 0; let pad_char = args
.get(2)
.map(|v| v.as_str())
.filter(|c| !c.is_empty())
.unwrap_or(Cow::Borrowed(" "));
let pad_length = width.saturating_sub(str_to_pad.chars().count());
if pad_length == 0 {
return Ok(Value::string(context.arena, &str_to_pad));
}
let padding = pad_char
.chars()
.cycle()
.take(pad_length)
.collect::<String>();
let result = if is_right_padding {
format!("{}{}", str_to_pad, padding)
} else {
format!("{}{}", padding, str_to_pad)
};
Ok(Value::string(context.arena, &result))
}