#[cfg(not(test))]
use alloc::boxed::Box;
#[cfg(not(test))]
use alloc::format;
#[cfg(not(test))]
use alloc::string::{String, ToString};
#[cfg(not(test))]
use alloc::vec;
#[cfg(not(test))]
use alloc::vec::Vec;
use indexmap::IndexMap;
pub trait EvalSemantics: Copy + Default {
const OVERFLOW_WRAPS: bool;
const DIV_BY_ZERO_IS_INFINITY: bool;
const NEGATIVE_INDEX_IN_HAS: bool;
}
#[derive(Debug, Clone, Copy, Default)]
pub struct JqSemantics;
impl EvalSemantics for JqSemantics {
const OVERFLOW_WRAPS: bool = false;
const DIV_BY_ZERO_IS_INFINITY: bool = false;
const NEGATIVE_INDEX_IN_HAS: bool = false;
}
#[derive(Debug, Clone, Copy, Default)]
pub struct YqSemantics;
impl EvalSemantics for YqSemantics {
const OVERFLOW_WRAPS: bool = true;
const DIV_BY_ZERO_IS_INFINITY: bool = true;
const NEGATIVE_INDEX_IN_HAS: bool = true;
}
use crate::json::light::{JsonCursor, JsonElements, JsonFields, StandardJson};
use super::expr::{
ArithOp, AssignOp, Builtin, CompareOp, Expr, FormatType, Literal, ObjectEntry, ObjectKey,
Pattern, StringPart,
};
use super::value::OwnedValue;
#[derive(Debug)]
pub enum QueryResult<'a, W = Vec<u64>> {
One(StandardJson<'a, W>),
OneCursor(JsonCursor<'a, W>),
Many(Vec<StandardJson<'a, W>>),
None,
Error(EvalError),
Owned(OwnedValue),
ManyOwned(Vec<OwnedValue>),
Break(String),
}
impl<'a, W: Clone + AsRef<[u64]>> QueryResult<'a, W> {
#[inline]
fn materialize_cursor(self) -> Self {
match self {
QueryResult::OneCursor(c) => QueryResult::One(c.value()),
other => other,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct EvalError {
pub message: String,
}
impl EvalError {
pub fn new(message: impl Into<String>) -> Self {
EvalError {
message: message.into(),
}
}
pub fn type_error(expected: &str, got: &str) -> Self {
EvalError::new(format!("expected {}, got {}", expected, got))
}
pub fn field_not_found(name: &str) -> Self {
EvalError::new(format!("field '{}' not found", name))
}
pub fn index_out_of_bounds(index: i64, len: usize) -> Self {
EvalError::new(format!("index {} out of bounds (length {})", index, len))
}
}
impl core::fmt::Display for EvalError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", self.message)
}
}
fn type_name<W>(value: &StandardJson<'_, W>) -> &'static str {
match value {
StandardJson::Object(_) => "object",
StandardJson::Array(_) => "array",
StandardJson::String(_) => "string",
StandardJson::Number(_) => "number",
StandardJson::Bool(_) => "boolean",
StandardJson::Null => "null",
StandardJson::Error(_) => "error",
}
}
fn to_owned<W: Clone + AsRef<[u64]>>(value: &StandardJson<'_, W>) -> OwnedValue {
match value {
StandardJson::Null => OwnedValue::Null,
StandardJson::Bool(b) => OwnedValue::Bool(*b),
StandardJson::Number(n) => {
if let Ok(i) = n.as_i64() {
OwnedValue::Int(i)
} else if let Ok(f) = n.as_f64() {
OwnedValue::Float(f)
} else {
OwnedValue::Float(0.0)
}
}
StandardJson::String(s) => {
if let Ok(cow) = s.as_str() {
OwnedValue::String(cow.into_owned())
} else {
OwnedValue::String(String::new())
}
}
StandardJson::Array(elements) => {
let items: Vec<OwnedValue> = (*elements).map(|e| to_owned(&e)).collect();
OwnedValue::Array(items)
}
StandardJson::Object(fields) => {
let mut map = IndexMap::new();
for field in *fields {
if let StandardJson::String(key_str_val) = field.key() {
if let Ok(cow) = key_str_val.as_str() {
map.insert(cow.into_owned(), to_owned(&field.value()));
}
}
}
OwnedValue::Object(map)
}
StandardJson::Error(_) => OwnedValue::Null,
}
}
fn needs_path_context(expr: &Expr) -> bool {
match expr {
Expr::Builtin(Builtin::PathNoArg) => true,
Expr::Builtin(Builtin::Parent) => true,
Expr::Builtin(Builtin::ParentN(_)) => true,
Expr::Builtin(Builtin::Key) => true,
Expr::Pipe(exprs) => exprs.iter().any(needs_path_context),
Expr::Paren(inner) => needs_path_context(inner),
Expr::Optional(inner) => needs_path_context(inner),
Expr::Comma(exprs) => exprs.iter().any(needs_path_context),
Expr::If {
cond,
then_branch,
else_branch,
} => {
needs_path_context(cond)
|| needs_path_context(then_branch)
|| needs_path_context(else_branch)
}
Expr::Try { expr, catch } => {
needs_path_context(expr) || catch.as_ref().is_some_and(|c| needs_path_context(c))
}
_ => false,
}
}
fn eval_single<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
expr: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
match expr {
Expr::Identity => QueryResult::One(value),
Expr::Field(name) => match value {
StandardJson::Object(fields) => match find_field::<W>(fields, name) {
Some(v) => QueryResult::One(v),
None => QueryResult::One(StandardJson::Null),
},
StandardJson::Null => QueryResult::One(StandardJson::Null),
_ if optional => QueryResult::None,
_ => QueryResult::Error(EvalError::type_error("object", type_name(&value))),
},
Expr::Index(idx) => match value {
StandardJson::Array(elements) => match get_element_at_index::<W>(elements, *idx) {
Some(v) => QueryResult::One(v),
None => QueryResult::One(StandardJson::Null),
},
StandardJson::Null => QueryResult::One(StandardJson::Null),
_ if optional => QueryResult::None,
_ => QueryResult::Error(EvalError::type_error("array", type_name(&value))),
},
Expr::Slice { start, end } => match value {
StandardJson::Array(elements) => {
let results = slice_elements::<W>(elements, *start, *end);
QueryResult::Many(results)
}
StandardJson::Null => QueryResult::One(StandardJson::Null),
StandardJson::String(s) => {
let s_str = match s.as_str() {
Ok(s) => s,
Err(_) => return QueryResult::Error(EvalError::new("invalid UTF-8 in string")),
};
if start.is_none() && end.is_none() {
return QueryResult::One(value);
}
if let Some(0) = start {
if end.is_none() && !s_str.is_empty() {
return QueryResult::One(value);
}
}
let len = s_str.chars().count();
let resolve_idx = |idx: i64| -> usize {
if idx >= 0 {
(idx as usize).min(len)
} else {
let pos = len as i64 + idx;
if pos < 0 {
0
} else {
pos as usize
}
}
};
let start_idx = start.map(resolve_idx).unwrap_or(0);
let end_idx = end.map(resolve_idx).unwrap_or(len);
if start_idx >= end_idx || start_idx >= len {
return QueryResult::Owned(OwnedValue::String(String::new()));
}
if start_idx == 0 && end_idx == len {
return QueryResult::One(value);
}
let sliced: String = s_str
.chars()
.skip(start_idx)
.take(end_idx - start_idx)
.collect();
QueryResult::Owned(OwnedValue::String(sliced))
}
_ if optional => QueryResult::None,
_ => QueryResult::Error(EvalError::type_error("array", type_name(&value))),
},
Expr::Iterate => match value {
StandardJson::Array(elements) => {
let results: Vec<_> = elements.collect();
QueryResult::Many(results)
}
StandardJson::Object(fields) => {
let results: Vec<_> = fields.map(|f| f.value()).collect();
QueryResult::Many(results)
}
_ if optional => QueryResult::None,
_ => QueryResult::Error(EvalError::type_error("array or object", type_name(&value))),
},
Expr::Optional(inner) => eval_single::<W, S>(inner, value, true),
Expr::Pipe(exprs) => eval_pipe::<W, S>(exprs, value, optional),
Expr::Comma(exprs) => eval_comma::<W, S>(exprs, value, optional),
Expr::Array(inner) => eval_array_construction::<W, S>(inner, value, optional),
Expr::Object(entries) => eval_object_construction::<W, S>(entries, value, optional),
Expr::Literal(lit) => QueryResult::Owned(literal_to_owned(lit)),
Expr::RecursiveDescent => eval_recursive_descent::<W, S>(value),
Expr::Paren(inner) => eval_single::<W, S>(inner, value, optional),
Expr::Arithmetic { op, left, right } => {
eval_arithmetic::<W, S>(*op, left, right, value, optional)
}
Expr::Compare { op, left, right } => {
eval_compare::<W, S>(*op, left, right, value, optional)
}
Expr::And(left, right) => eval_and::<W, S>(left, right, value, optional),
Expr::Or(left, right) => eval_or::<W, S>(left, right, value, optional),
Expr::Not => eval_not::<W>(value),
Expr::Alternative(left, right) => eval_alternative::<W, S>(left, right, value, optional),
Expr::If {
cond,
then_branch,
else_branch,
} => eval_if::<W, S>(cond, then_branch, else_branch, value, optional),
Expr::Try { expr, catch } => eval_try::<W, S>(expr, catch.as_deref(), value, optional),
Expr::Error(msg) => eval_error::<W, S>(msg.as_deref(), value, optional),
Expr::Builtin(builtin) => eval_builtin::<W, S>(builtin, value, optional),
Expr::StringInterpolation(parts) => {
eval_string_interpolation::<W, S>(parts, value, optional)
}
Expr::Format(format_type) => eval_format::<W>(format_type.clone(), value, optional),
Expr::As { expr, var, body } => eval_as::<W, S>(expr, var, body, value, optional),
Expr::Var(name) => {
QueryResult::Error(EvalError::new(format!("undefined variable: ${}", name)))
}
Expr::Loc { line } => {
let mut obj = IndexMap::new();
obj.insert("file".into(), OwnedValue::String("<stdin>".into()));
obj.insert("line".into(), OwnedValue::Int(*line as i64));
QueryResult::Owned(OwnedValue::Object(obj))
}
Expr::Env => {
eval_env::<W>(optional)
}
Expr::Reduce {
input,
var,
init,
update,
} => eval_reduce::<W, S>(input, var, init, update, value, optional),
Expr::Foreach {
input,
var,
init,
update,
extract,
} => eval_foreach::<W, S>(
input,
var,
init,
update,
extract.as_deref(),
value,
optional,
),
Expr::Limit { n, expr } => eval_limit::<W, S>(n, expr, value, optional),
Expr::FirstExpr(expr) => eval_first_expr::<W, S>(expr, value, optional),
Expr::LastExpr(expr) => eval_last_expr::<W, S>(expr, value, optional),
Expr::NthExpr { n, expr } => eval_nth_expr::<W, S>(n, expr, value, optional),
Expr::Until { cond, update } => eval_until::<W, S>(cond, update, value, optional),
Expr::While { cond, update } => eval_while::<W, S>(cond, update, value, optional),
Expr::Repeat(expr) => eval_repeat::<W, S>(expr, value, optional),
Expr::Range { from, to, step } => {
eval_range::<W, S>(from, to.as_deref(), step.as_deref(), value, optional)
}
Expr::AsPattern {
expr,
pattern,
body,
} => eval_as_pattern::<W, S>(expr, pattern, body, value, optional),
Expr::FuncDef {
name,
params,
body,
then,
} => eval_func_def::<W, S>(name, params, body, then, value, optional),
Expr::FuncCall { name, args } => eval_func_call::<W>(name, args, value, optional),
Expr::NamespacedCall {
namespace,
name,
args: _,
} => {
QueryResult::Error(EvalError::new(format!(
"module '{}' not loaded (namespaced call {}::{})",
namespace, namespace, name
)))
}
Expr::Assign { path, value: val } => eval_assign::<W, S>(path, val, value, optional),
Expr::Update { path, filter } => eval_update::<W, S>(path, filter, value, optional),
Expr::CompoundAssign {
op,
path,
value: val,
} => eval_compound_assign::<W, S>(*op, path, val, value, optional),
Expr::AlternativeAssign { path, value: val } => {
eval_alternative_assign::<W, S>(path, val, value, optional)
}
Expr::Label { name, body } => eval_label::<W, S>(name, body, value, optional),
Expr::Break(name) => QueryResult::Break(name.clone()),
}
}
fn literal_to_owned(lit: &Literal) -> OwnedValue {
match lit {
Literal::Null => OwnedValue::Null,
Literal::Bool(b) => OwnedValue::Bool(*b),
Literal::Int(n) => OwnedValue::Int(*n),
Literal::Float(f) => OwnedValue::Float(*f),
Literal::String(s) => OwnedValue::String(s.clone()),
}
}
fn eval_comma<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
exprs: &[Expr],
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
if exprs.is_empty() {
return QueryResult::None;
}
let mut all_results = Vec::new();
let mut all_owned = Vec::new();
let mut has_owned = false;
for expr in exprs {
match eval_single::<W, S>(expr, value.clone(), optional).materialize_cursor() {
QueryResult::One(v) => all_results.push(v),
QueryResult::OneCursor(_) => {
unreachable!("materialize_cursor should have converted this")
}
QueryResult::Many(vs) => all_results.extend(vs),
QueryResult::Owned(v) => {
has_owned = true;
all_owned.push(v);
}
QueryResult::ManyOwned(vs) => {
has_owned = true;
all_owned.extend(vs);
}
QueryResult::None => {}
QueryResult::Error(e) => return QueryResult::Error(e),
QueryResult::Break(label) => return QueryResult::Break(label),
}
}
if has_owned {
let mut converted: Vec<OwnedValue> = all_results.iter().map(to_owned).collect();
converted.extend(all_owned);
if converted.len() == 1 {
QueryResult::Owned(converted.pop().unwrap())
} else {
QueryResult::ManyOwned(converted)
}
} else if all_results.len() == 1 {
QueryResult::One(all_results.pop().unwrap())
} else {
QueryResult::Many(all_results)
}
}
fn eval_array_construction<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
inner: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let result = eval_single::<W, S>(inner, value, optional);
let items: Vec<OwnedValue> = match result.materialize_cursor() {
QueryResult::One(v) => vec![to_owned(&v)],
QueryResult::OneCursor(_) => unreachable!(),
QueryResult::Many(vs) => vs.iter().map(to_owned).collect(),
QueryResult::Owned(v) => vec![v],
QueryResult::ManyOwned(vs) => vs,
QueryResult::None => vec![],
QueryResult::Error(e) => return QueryResult::Error(e),
QueryResult::Break(label) => return QueryResult::Break(label),
};
QueryResult::Owned(OwnedValue::Array(items))
}
fn eval_object_construction<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
entries: &[super::expr::ObjectEntry],
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let mut map = IndexMap::new();
for entry in entries {
let key_str = match &entry.key {
ObjectKey::Literal(s) => s.clone(),
ObjectKey::Expr(key_expr) => {
let key_result = eval_single::<W, S>(key_expr, value.clone(), optional);
match key_result {
QueryResult::One(StandardJson::String(s)) => {
if let Ok(cow) = s.as_str() {
cow.into_owned()
} else {
return QueryResult::Error(EvalError::new("key must be a string"));
}
}
QueryResult::Owned(OwnedValue::String(s)) => s,
QueryResult::Error(e) => return QueryResult::Error(e),
QueryResult::Break(label) => return QueryResult::Break(label),
_ => {
return QueryResult::Error(EvalError::new("key must be a string"));
}
}
}
};
let val_result = eval_single::<W, S>(&entry.value, value.clone(), optional);
let owned_val = match val_result.materialize_cursor() {
QueryResult::One(v) => to_owned(&v),
QueryResult::OneCursor(_) => unreachable!(),
QueryResult::Owned(v) => v,
QueryResult::Many(vs) => {
if let Some(v) = vs.first() {
to_owned(v)
} else {
OwnedValue::Null
}
}
QueryResult::ManyOwned(vs) => {
if let Some(v) = vs.into_iter().next() {
v
} else {
OwnedValue::Null
}
}
QueryResult::None => OwnedValue::Null,
QueryResult::Error(e) => return QueryResult::Error(e),
QueryResult::Break(label) => return QueryResult::Break(label),
};
map.insert(key_str, owned_val);
}
QueryResult::Owned(OwnedValue::Object(map))
}
fn eval_recursive_descent<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
value: StandardJson<'a, W>,
) -> QueryResult<'a, W> {
let mut results = Vec::new();
collect_recursive::<W, S>(&value, &mut results);
QueryResult::Many(results)
}
fn collect_recursive<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
value: &StandardJson<'a, W>,
results: &mut Vec<StandardJson<'a, W>>,
) {
results.push(value.clone());
match value {
StandardJson::Array(elements) => {
for elem in *elements {
collect_recursive::<W, S>(&elem, results);
}
}
StandardJson::Object(fields) => {
for field in *fields {
collect_recursive::<W, S>(&field.value(), results);
}
}
_ => {}
}
}
fn result_to_owned<W: Clone + AsRef<[u64]>>(
result: QueryResult<'_, W>,
) -> Result<OwnedValue, EvalError> {
match result.materialize_cursor() {
QueryResult::One(v) => Ok(to_owned(&v)),
QueryResult::OneCursor(_) => unreachable!(),
QueryResult::Owned(v) => Ok(v),
QueryResult::Many(vs) => {
if let Some(v) = vs.first() {
Ok(to_owned(v))
} else {
Err(EvalError::new("empty result"))
}
}
QueryResult::ManyOwned(vs) => {
if let Some(v) = vs.into_iter().next() {
Ok(v)
} else {
Err(EvalError::new("empty result"))
}
}
QueryResult::None => Err(EvalError::new("no value")),
QueryResult::Error(e) => Err(e),
QueryResult::Break(label) => Err(EvalError::new(format!("break ${} not in label", label))),
}
}
fn eval_arithmetic<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
op: ArithOp,
left: &Expr,
right: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let left_val = match result_to_owned(eval_single::<W, S>(left, value.clone(), optional)) {
Ok(v) => v,
Err(e) => return QueryResult::Error(e),
};
let right_val = match result_to_owned(eval_single::<W, S>(right, value, optional)) {
Ok(v) => v,
Err(e) => return QueryResult::Error(e),
};
let result = match op {
ArithOp::Add => arith_add::<S>(left_val, right_val),
ArithOp::Sub => arith_sub::<S>(left_val, right_val),
ArithOp::Mul => arith_mul::<S>(left_val, right_val),
ArithOp::Div => arith_div::<S>(left_val, right_val),
ArithOp::Mod => arith_mod::<S>(left_val, right_val),
};
match result {
Ok(v) => QueryResult::Owned(v),
Err(e) => QueryResult::Error(e),
}
}
fn arith_add<S: EvalSemantics>(
left: OwnedValue,
right: OwnedValue,
) -> Result<OwnedValue, EvalError> {
match (left, right) {
(OwnedValue::Int(a), OwnedValue::Int(b)) => {
if S::OVERFLOW_WRAPS {
Ok(OwnedValue::Int(a.wrapping_add(b)))
} else {
match a.checked_add(b) {
Some(result) => Ok(OwnedValue::Int(result)),
None => Ok(OwnedValue::Float(a as f64 + b as f64)),
}
}
}
(OwnedValue::Int(a), OwnedValue::Float(b)) => Ok(OwnedValue::Float(a as f64 + b)),
(OwnedValue::Float(a), OwnedValue::Int(b)) => Ok(OwnedValue::Float(a + b as f64)),
(OwnedValue::Float(a), OwnedValue::Float(b)) => Ok(OwnedValue::Float(a + b)),
(OwnedValue::String(mut a), OwnedValue::String(b)) => {
a.push_str(&b);
Ok(OwnedValue::String(a))
}
(OwnedValue::Array(mut a), OwnedValue::Array(b)) => {
a.extend(b);
Ok(OwnedValue::Array(a))
}
(OwnedValue::Object(mut a), OwnedValue::Object(b)) => {
a.extend(b);
Ok(OwnedValue::Object(a))
}
(OwnedValue::Null, other) | (other, OwnedValue::Null) => Ok(other),
(a, b) => Err(EvalError::new(format!(
"cannot add {} and {}",
a.type_name(),
b.type_name()
))),
}
}
fn arith_sub<S: EvalSemantics>(
left: OwnedValue,
right: OwnedValue,
) -> Result<OwnedValue, EvalError> {
match (left, right) {
(OwnedValue::Int(a), OwnedValue::Int(b)) => {
if S::OVERFLOW_WRAPS {
Ok(OwnedValue::Int(a.wrapping_sub(b)))
} else {
match a.checked_sub(b) {
Some(result) => Ok(OwnedValue::Int(result)),
None => Ok(OwnedValue::Float(a as f64 - b as f64)),
}
}
}
(OwnedValue::Int(a), OwnedValue::Float(b)) => Ok(OwnedValue::Float(a as f64 - b)),
(OwnedValue::Float(a), OwnedValue::Int(b)) => Ok(OwnedValue::Float(a - b as f64)),
(OwnedValue::Float(a), OwnedValue::Float(b)) => Ok(OwnedValue::Float(a - b)),
(OwnedValue::Array(a), OwnedValue::Array(b)) => {
let result: Vec<_> = a.into_iter().filter(|x| !b.contains(x)).collect();
Ok(OwnedValue::Array(result))
}
(a, b) => Err(EvalError::new(format!(
"cannot subtract {} from {}",
b.type_name(),
a.type_name()
))),
}
}
fn arith_mul<S: EvalSemantics>(
left: OwnedValue,
right: OwnedValue,
) -> Result<OwnedValue, EvalError> {
match (left, right) {
(OwnedValue::Int(a), OwnedValue::Int(b)) => {
if S::OVERFLOW_WRAPS {
Ok(OwnedValue::Int(a.wrapping_mul(b)))
} else {
match a.checked_mul(b) {
Some(result) => Ok(OwnedValue::Int(result)),
None => Ok(OwnedValue::Float(a as f64 * b as f64)),
}
}
}
(OwnedValue::Int(a), OwnedValue::Float(b)) => Ok(OwnedValue::Float(a as f64 * b)),
(OwnedValue::Float(a), OwnedValue::Int(b)) => Ok(OwnedValue::Float(a * b as f64)),
(OwnedValue::Float(a), OwnedValue::Float(b)) => Ok(OwnedValue::Float(a * b)),
(OwnedValue::String(s), OwnedValue::Int(n))
| (OwnedValue::Int(n), OwnedValue::String(s)) => {
if n < 0 {
Ok(OwnedValue::Null)
} else {
Ok(OwnedValue::String(s.repeat(n as usize)))
}
}
(OwnedValue::Object(a), OwnedValue::Object(b)) => {
Ok(OwnedValue::Object(merge_objects(a, b)))
}
(OwnedValue::Null, _) | (_, OwnedValue::Null) => Ok(OwnedValue::Null),
(a, b) => Err(EvalError::new(format!(
"cannot multiply {} and {}",
a.type_name(),
b.type_name()
))),
}
}
fn merge_objects(
mut left: IndexMap<String, OwnedValue>,
right: IndexMap<String, OwnedValue>,
) -> IndexMap<String, OwnedValue> {
for (k, v) in right {
match (left.get(&k).cloned(), v) {
(Some(OwnedValue::Object(a)), OwnedValue::Object(b)) => {
left.insert(k, OwnedValue::Object(merge_objects(a, b)));
}
(_, v) => {
left.insert(k, v);
}
}
}
left
}
fn arith_div<S: EvalSemantics>(
left: OwnedValue,
right: OwnedValue,
) -> Result<OwnedValue, EvalError> {
match (left, right) {
(OwnedValue::Int(a), OwnedValue::Int(b)) => {
if b == 0 {
if S::DIV_BY_ZERO_IS_INFINITY {
Ok(OwnedValue::Float(a as f64 / b as f64))
} else {
Err(EvalError::new("division by zero"))
}
} else {
Ok(OwnedValue::Float(a as f64 / b as f64))
}
}
(OwnedValue::Int(a), OwnedValue::Float(b)) => {
if b == 0.0 && !S::DIV_BY_ZERO_IS_INFINITY {
Err(EvalError::new("division by zero"))
} else {
Ok(OwnedValue::Float(a as f64 / b))
}
}
(OwnedValue::Float(a), OwnedValue::Int(b)) => {
if b == 0 && !S::DIV_BY_ZERO_IS_INFINITY {
Err(EvalError::new("division by zero"))
} else {
Ok(OwnedValue::Float(a / b as f64))
}
}
(OwnedValue::Float(a), OwnedValue::Float(b)) => {
if b == 0.0 && !S::DIV_BY_ZERO_IS_INFINITY {
Err(EvalError::new("division by zero"))
} else {
Ok(OwnedValue::Float(a / b))
}
}
(OwnedValue::String(s), OwnedValue::String(sep)) => {
let parts: Vec<OwnedValue> = s
.split(&sep)
.map(|p| OwnedValue::String(p.to_string()))
.collect();
Ok(OwnedValue::Array(parts))
}
(a, b) => Err(EvalError::new(format!(
"cannot divide {} by {}",
a.type_name(),
b.type_name()
))),
}
}
fn arith_mod<S: EvalSemantics>(
left: OwnedValue,
right: OwnedValue,
) -> Result<OwnedValue, EvalError> {
match (left, right) {
(OwnedValue::Int(a), OwnedValue::Int(b)) => {
if b == 0 {
if S::DIV_BY_ZERO_IS_INFINITY {
Ok(OwnedValue::Float(f64::NAN))
} else {
Err(EvalError::new("modulo by zero"))
}
} else {
Ok(OwnedValue::Int(a % b))
}
}
(OwnedValue::Float(a), OwnedValue::Float(b)) => {
if b == 0.0 && !S::DIV_BY_ZERO_IS_INFINITY {
Err(EvalError::new("modulo by zero"))
} else {
Ok(OwnedValue::Float(a % b))
}
}
(OwnedValue::Int(a), OwnedValue::Float(b)) => {
if b == 0.0 && !S::DIV_BY_ZERO_IS_INFINITY {
Err(EvalError::new("modulo by zero"))
} else {
Ok(OwnedValue::Float(a as f64 % b))
}
}
(OwnedValue::Float(a), OwnedValue::Int(b)) => {
if b == 0 && !S::DIV_BY_ZERO_IS_INFINITY {
Err(EvalError::new("modulo by zero"))
} else {
Ok(OwnedValue::Float(a % b as f64))
}
}
(a, b) => Err(EvalError::new(format!(
"cannot compute modulo of {} and {}",
a.type_name(),
b.type_name()
))),
}
}
fn eval_compare<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
op: CompareOp,
left: &Expr,
right: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let left_val = match result_to_owned(eval_single::<W, S>(left, value.clone(), optional)) {
Ok(v) => v,
Err(e) => return QueryResult::Error(e),
};
let right_val = match result_to_owned(eval_single::<W, S>(right, value, optional)) {
Ok(v) => v,
Err(e) => return QueryResult::Error(e),
};
let result = match op {
CompareOp::Eq => left_val == right_val,
CompareOp::Ne => left_val != right_val,
CompareOp::Lt => compare_values(&left_val, &right_val) == core::cmp::Ordering::Less,
CompareOp::Le => compare_values(&left_val, &right_val) != core::cmp::Ordering::Greater,
CompareOp::Gt => compare_values(&left_val, &right_val) == core::cmp::Ordering::Greater,
CompareOp::Ge => compare_values(&left_val, &right_val) != core::cmp::Ordering::Less,
};
QueryResult::Owned(OwnedValue::Bool(result))
}
fn compare_values(left: &OwnedValue, right: &OwnedValue) -> core::cmp::Ordering {
use core::cmp::Ordering;
fn type_order(v: &OwnedValue) -> u8 {
match v {
OwnedValue::Null => 0,
OwnedValue::Bool(_) => 1,
OwnedValue::Int(_) | OwnedValue::Float(_) => 2,
OwnedValue::String(_) => 3,
OwnedValue::Array(_) => 4,
OwnedValue::Object(_) => 5,
}
}
let left_type = type_order(left);
let right_type = type_order(right);
if left_type != right_type {
return left_type.cmp(&right_type);
}
match (left, right) {
(OwnedValue::Null, OwnedValue::Null) => Ordering::Equal,
(OwnedValue::Bool(a), OwnedValue::Bool(b)) => a.cmp(b),
(OwnedValue::Int(a), OwnedValue::Int(b)) => a.cmp(b),
(OwnedValue::Float(a), OwnedValue::Float(b)) => a.partial_cmp(b).unwrap_or(Ordering::Equal),
(OwnedValue::Int(a), OwnedValue::Float(b)) => {
(*a as f64).partial_cmp(b).unwrap_or(Ordering::Equal)
}
(OwnedValue::Float(a), OwnedValue::Int(b)) => {
a.partial_cmp(&(*b as f64)).unwrap_or(Ordering::Equal)
}
(OwnedValue::String(a), OwnedValue::String(b)) => a.cmp(b),
(OwnedValue::Array(a), OwnedValue::Array(b)) => {
for (av, bv) in a.iter().zip(b.iter()) {
match compare_values(av, bv) {
Ordering::Equal => continue,
other => return other,
}
}
a.len().cmp(&b.len())
}
(OwnedValue::Object(a), OwnedValue::Object(b)) => {
let mut a_keys: Vec<_> = a.keys().collect();
let mut b_keys: Vec<_> = b.keys().collect();
a_keys.sort();
b_keys.sort();
for (ak, bk) in a_keys.iter().zip(b_keys.iter()) {
match ak.cmp(bk) {
Ordering::Equal => match compare_values(&a[*ak], &b[*bk]) {
Ordering::Equal => continue,
other => return other,
},
other => return other,
}
}
a.len().cmp(&b.len())
}
_ => Ordering::Equal,
}
}
fn eval_and<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
left: &Expr,
right: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let left_val = match result_to_owned(eval_single::<W, S>(left, value.clone(), optional)) {
Ok(v) => v,
Err(e) => return QueryResult::Error(e),
};
if !left_val.is_truthy() {
return QueryResult::Owned(OwnedValue::Bool(false));
}
let right_val = match result_to_owned(eval_single::<W, S>(right, value, optional)) {
Ok(v) => v,
Err(e) => return QueryResult::Error(e),
};
QueryResult::Owned(OwnedValue::Bool(right_val.is_truthy()))
}
fn eval_or<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
left: &Expr,
right: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let left_val = match result_to_owned(eval_single::<W, S>(left, value.clone(), optional)) {
Ok(v) => v,
Err(e) => return QueryResult::Error(e),
};
if left_val.is_truthy() {
return QueryResult::Owned(OwnedValue::Bool(true));
}
let right_val = match result_to_owned(eval_single::<W, S>(right, value, optional)) {
Ok(v) => v,
Err(e) => return QueryResult::Error(e),
};
QueryResult::Owned(OwnedValue::Bool(right_val.is_truthy()))
}
fn eval_not<'a, W: Clone + AsRef<[u64]>>(value: StandardJson<'a, W>) -> QueryResult<'a, W> {
let owned = to_owned(&value);
QueryResult::Owned(OwnedValue::Bool(!owned.is_truthy()))
}
fn eval_alternative<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
left: &Expr,
right: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let left_result = eval_single::<W, S>(left, value.clone(), optional);
let is_truthy = match &left_result {
QueryResult::One(v) => to_owned(v).is_truthy(),
QueryResult::OneCursor(_) => unreachable!("eval_single never produces OneCursor"),
QueryResult::Owned(v) => v.is_truthy(),
QueryResult::Many(vs) => vs.first().map(|v| to_owned(v).is_truthy()).unwrap_or(false),
QueryResult::ManyOwned(vs) => vs.first().map(|v| v.is_truthy()).unwrap_or(false),
QueryResult::None => false,
QueryResult::Error(_) => false,
QueryResult::Break(label) => return QueryResult::Break(label.clone()),
};
if is_truthy {
left_result
} else {
eval_single::<W, S>(right, value, optional)
}
}
fn eval_if<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
cond: &Expr,
then_branch: &Expr,
else_branch: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let cond_result = eval_single::<W, S>(cond, value.clone(), optional);
let is_truthy = match &cond_result {
QueryResult::One(v) => to_owned(v).is_truthy(),
QueryResult::OneCursor(_) => unreachable!("eval_single never produces OneCursor"),
QueryResult::Owned(v) => v.is_truthy(),
QueryResult::Many(vs) => vs.first().map(|v| to_owned(v).is_truthy()).unwrap_or(false),
QueryResult::ManyOwned(vs) => vs.first().map(|v| v.is_truthy()).unwrap_or(false),
QueryResult::None => false,
QueryResult::Error(e) => return QueryResult::Error(e.clone()),
QueryResult::Break(label) => return QueryResult::Break(label.clone()),
};
if is_truthy {
eval_single::<W, S>(then_branch, value, optional)
} else {
eval_single::<W, S>(else_branch, value, optional)
}
}
fn eval_try<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
expr: &Expr,
catch: Option<&Expr>,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let result = eval_single::<W, S>(expr, value.clone(), optional);
match result {
QueryResult::Error(_) => match catch {
Some(catch_expr) => eval_single::<W, S>(catch_expr, value, optional),
None => QueryResult::None,
},
other => other,
}
}
fn eval_label<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
name: &str,
body: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let result = eval_single::<W, S>(body, value, optional);
match result {
QueryResult::Break(label) if label == name => QueryResult::None,
other => other,
}
}
fn eval_error<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
msg: Option<&Expr>,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let message = match msg {
Some(msg_expr) => {
let msg_result = eval_single::<W, S>(msg_expr, value, optional);
match result_to_owned(msg_result) {
Ok(OwnedValue::String(s)) => s,
Ok(v) => v.to_json(),
Err(e) => return QueryResult::Error(e),
}
}
None => "null".into(),
};
QueryResult::Error(EvalError::new(message))
}
fn eval_builtin<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
builtin: &Builtin,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
match builtin {
Builtin::Type => {
let type_name = match &value {
StandardJson::Null => "null",
StandardJson::Bool(_) => "boolean",
StandardJson::Number(_) => "number",
StandardJson::String(_) => "string",
StandardJson::Array(_) => "array",
StandardJson::Object(_) => "object",
StandardJson::Error(_) => "error",
};
QueryResult::Owned(OwnedValue::String(type_name.into()))
}
Builtin::IsNull => {
QueryResult::Owned(OwnedValue::Bool(matches!(value, StandardJson::Null)))
}
Builtin::IsBoolean => {
QueryResult::Owned(OwnedValue::Bool(matches!(value, StandardJson::Bool(_))))
}
Builtin::IsNumber => {
QueryResult::Owned(OwnedValue::Bool(matches!(value, StandardJson::Number(_))))
}
Builtin::IsString => {
QueryResult::Owned(OwnedValue::Bool(matches!(value, StandardJson::String(_))))
}
Builtin::IsArray => {
QueryResult::Owned(OwnedValue::Bool(matches!(value, StandardJson::Array(_))))
}
Builtin::IsObject => {
QueryResult::Owned(OwnedValue::Bool(matches!(value, StandardJson::Object(_))))
}
Builtin::Values => {
if matches!(value, StandardJson::Null) {
QueryResult::None
} else {
QueryResult::One(value)
}
}
Builtin::Nulls => {
if matches!(value, StandardJson::Null) {
QueryResult::One(value)
} else {
QueryResult::None
}
}
Builtin::Booleans => {
if matches!(value, StandardJson::Bool(_)) {
QueryResult::One(value)
} else {
QueryResult::None
}
}
Builtin::Numbers => {
if matches!(value, StandardJson::Number(_)) {
QueryResult::One(value)
} else {
QueryResult::None
}
}
Builtin::Strings => {
if matches!(value, StandardJson::String(_)) {
QueryResult::One(value)
} else {
QueryResult::None
}
}
Builtin::Arrays => {
if matches!(value, StandardJson::Array(_)) {
QueryResult::One(value)
} else {
QueryResult::None
}
}
Builtin::Objects => {
if matches!(value, StandardJson::Object(_)) {
QueryResult::One(value)
} else {
QueryResult::None
}
}
Builtin::Iterables => {
if matches!(value, StandardJson::Array(_) | StandardJson::Object(_)) {
QueryResult::One(value)
} else {
QueryResult::None
}
}
Builtin::Scalars => {
if matches!(
value,
StandardJson::Null
| StandardJson::Bool(_)
| StandardJson::Number(_)
| StandardJson::String(_)
) {
QueryResult::One(value)
} else {
QueryResult::None
}
}
Builtin::Length => builtin_length::<W>(value, optional),
Builtin::Utf8ByteLength => builtin_utf8bytelength(value, optional),
Builtin::Keys => builtin_keys::<W>(value, optional, true),
Builtin::KeysUnsorted => builtin_keys::<W>(value, optional, false),
Builtin::Has(key_expr) => builtin_has::<W, S>(key_expr, value, optional),
Builtin::In(obj_expr) => builtin_in::<W, S>(obj_expr, value, optional),
Builtin::Select(cond) => builtin_select::<W, S>(cond, value, optional),
Builtin::Empty => QueryResult::None,
Builtin::Map(f) => builtin_map::<W, S>(f, value, optional),
Builtin::MapValues(f) => builtin_map_values::<W, S>(f, value, optional),
Builtin::Add => builtin_add::<W, S>(value, optional),
Builtin::Any => builtin_any::<W>(value, optional),
Builtin::All => builtin_all::<W>(value, optional),
Builtin::Min => builtin_min::<W>(value, optional),
Builtin::Max => builtin_max::<W>(value, optional),
Builtin::MinBy(f) => builtin_min_by::<W, S>(f, value, optional),
Builtin::MaxBy(f) => builtin_max_by::<W, S>(f, value, optional),
Builtin::AsciiDowncase => builtin_ascii_downcase::<W>(value, optional),
Builtin::AsciiUpcase => builtin_ascii_upcase::<W>(value, optional),
Builtin::Ltrimstr(s) => builtin_ltrimstr::<W, S>(s, value, optional),
Builtin::Rtrimstr(s) => builtin_rtrimstr::<W, S>(s, value, optional),
Builtin::Startswith(s) => builtin_startswith::<W, S>(s, value, optional),
Builtin::Endswith(s) => builtin_endswith::<W, S>(s, value, optional),
Builtin::Split(sep) => builtin_split::<W, S>(sep, value, optional),
Builtin::Join(sep) => builtin_join::<W, S>(sep, value, optional),
Builtin::Contains(b) => builtin_contains::<W, S>(b, value, optional),
Builtin::Inside(b) => builtin_inside::<W, S>(b, value, optional),
Builtin::First => builtin_first::<W>(value, optional),
Builtin::Last => builtin_last::<W>(value, optional),
Builtin::Nth(n) => builtin_nth::<W, S>(n, value, optional),
Builtin::Reverse => builtin_reverse::<W>(value, optional),
Builtin::Flatten => builtin_flatten::<W>(value, optional, 1),
Builtin::FlattenDepth(depth) => builtin_flatten_depth::<W, S>(depth, value, optional),
Builtin::GroupBy(f) => builtin_group_by::<W, S>(f, value, optional),
Builtin::Unique => builtin_unique::<W>(value, optional),
Builtin::UniqueBy(f) => builtin_unique_by::<W, S>(f, value, optional),
Builtin::Sort => builtin_sort::<W>(value, optional),
Builtin::SortBy(f) => builtin_sort_by::<W, S>(f, value, optional),
Builtin::ToEntries => builtin_to_entries::<W>(value, optional),
Builtin::FromEntries => builtin_from_entries::<W>(value, optional),
Builtin::WithEntries(f) => builtin_with_entries::<W, S>(f, value, optional),
Builtin::ToString => builtin_tostring::<W>(value, optional),
Builtin::ToNumber => builtin_tonumber::<W>(value, optional),
Builtin::ToJson => builtin_tojson::<W>(value, optional),
Builtin::FromJson => builtin_fromjson::<W>(value, optional),
Builtin::Explode => builtin_explode::<W>(value, optional),
Builtin::Implode => builtin_implode::<W>(value, optional),
Builtin::Test(re) => builtin_test::<W, S>(re, value, optional),
Builtin::Indices(s) => builtin_indices::<W, S>(s, value, optional),
Builtin::Index(s) => builtin_index::<W, S>(s, value, optional),
Builtin::Rindex(s) => builtin_rindex::<W, S>(s, value, optional),
Builtin::ToJsonStream => builtin_tojsonstream::<W>(value, optional),
Builtin::FromJsonStream => builtin_fromjsonstream::<W>(value, optional),
Builtin::GetPath(path) => builtin_getpath::<W, S>(path, value, optional),
#[cfg(feature = "regex")]
Builtin::TestFlags(re, flags) => builtin_test_flags::<W, S>(re, flags, value, optional),
#[cfg(feature = "regex")]
Builtin::Match(re) => builtin_match::<W, S>(re, None, value, optional),
#[cfg(feature = "regex")]
Builtin::MatchFlags(re, flags) => builtin_match_flags::<W, S>(re, flags, value, optional),
#[cfg(feature = "regex")]
Builtin::Capture(re) => builtin_capture::<W, S>(re, value, optional),
#[cfg(feature = "regex")]
Builtin::CaptureFlags(re, flags) => {
builtin_capture_flags::<W, S>(re, flags, value, optional)
}
#[cfg(feature = "regex")]
Builtin::Sub(re, replacement) => builtin_sub::<W, S>(re, replacement, value, optional),
#[cfg(feature = "regex")]
Builtin::SubFlags(re, replacement, flags) => {
builtin_sub_flags::<W, S>(re, replacement, flags, value, optional)
}
#[cfg(feature = "regex")]
Builtin::Gsub(re, replacement) => builtin_gsub::<W, S>(re, replacement, value, optional),
#[cfg(feature = "regex")]
Builtin::GsubFlags(re, replacement, flags) => {
builtin_gsub_flags::<W, S>(re, replacement, flags, value, optional)
}
#[cfg(feature = "regex")]
Builtin::Scan(re) => builtin_scan::<W, S>(re, value, optional),
#[cfg(feature = "regex")]
Builtin::ScanFlags(re, flags) => builtin_scan_flags::<W, S>(re, flags, value, optional),
#[cfg(feature = "regex")]
Builtin::SplitRegex(re, flags) => builtin_split_regex::<W, S>(re, flags, value, optional),
#[cfg(feature = "regex")]
Builtin::Splits(re) => builtin_splits::<W, S>(re, value, optional),
#[cfg(feature = "regex")]
Builtin::SplitsFlags(re, flags) => builtin_splits_flags::<W, S>(re, flags, value, optional),
#[cfg(not(feature = "regex"))]
Builtin::TestFlags(_, _)
| Builtin::Match(_)
| Builtin::MatchFlags(_, _)
| Builtin::Capture(_)
| Builtin::CaptureFlags(_, _)
| Builtin::Sub(_, _)
| Builtin::SubFlags(_, _, _)
| Builtin::Gsub(_, _)
| Builtin::GsubFlags(_, _, _)
| Builtin::Scan(_)
| Builtin::ScanFlags(_, _)
| Builtin::SplitRegex(_, _)
| Builtin::Splits(_)
| Builtin::SplitsFlags(_, _) => {
QueryResult::Error(EvalError::new("regex feature not enabled"))
}
Builtin::Recurse => builtin_recurse::<W, S>(value, optional),
Builtin::RecurseF(f) => builtin_recurse_f::<W, S>(f, value, optional),
Builtin::RecurseCond(f, cond) => builtin_recurse_cond::<W, S>(f, cond, value, optional),
Builtin::Walk(f) => builtin_walk::<W, S>(f, value, optional),
Builtin::IsValid(expr) => builtin_isvalid::<W, S>(expr, value, optional),
Builtin::Path(expr) => builtin_path::<W>(expr, value, optional),
Builtin::PathNoArg => {
QueryResult::Owned(OwnedValue::Array(vec![]))
}
Builtin::Parent => {
QueryResult::Owned(OwnedValue::Object(IndexMap::new()))
}
Builtin::ParentN(n_expr) => {
let _ = n_expr; QueryResult::Owned(OwnedValue::Object(IndexMap::new()))
}
Builtin::Paths => builtin_paths::<W>(value, optional),
Builtin::PathsFilter(filter) => builtin_paths_filter::<W, S>(filter, value, optional),
Builtin::LeafPaths => builtin_leaf_paths::<W>(value, optional),
Builtin::SetPath(path, val) => builtin_setpath::<W, S>(path, val, value, optional),
Builtin::DelPaths(paths) => builtin_delpaths::<W, S>(paths, value, optional),
Builtin::Floor => builtin_floor::<W>(value, optional),
Builtin::Ceil => builtin_ceil::<W>(value, optional),
Builtin::Round => builtin_round::<W>(value, optional),
Builtin::Sqrt => builtin_sqrt::<W>(value, optional),
Builtin::Fabs => builtin_fabs::<W>(value, optional),
Builtin::Log => builtin_log::<W>(value, optional),
Builtin::Log10 => builtin_log10::<W>(value, optional),
Builtin::Log2 => builtin_log2::<W>(value, optional),
Builtin::Exp => builtin_exp::<W>(value, optional),
Builtin::Exp10 => builtin_exp10::<W>(value, optional),
Builtin::Exp2 => builtin_exp2::<W>(value, optional),
Builtin::Pow(base, exp) => builtin_pow::<W, S>(base, exp, value, optional),
Builtin::Sin => builtin_sin::<W>(value, optional),
Builtin::Cos => builtin_cos::<W>(value, optional),
Builtin::Tan => builtin_tan::<W>(value, optional),
Builtin::Asin => builtin_asin::<W>(value, optional),
Builtin::Acos => builtin_acos::<W>(value, optional),
Builtin::Atan => builtin_atan::<W>(value, optional),
Builtin::Atan2(y, x) => builtin_atan2::<W, S>(y, x, value, optional),
Builtin::Sinh => builtin_sinh::<W>(value, optional),
Builtin::Cosh => builtin_cosh::<W>(value, optional),
Builtin::Tanh => builtin_tanh::<W>(value, optional),
Builtin::Asinh => builtin_asinh::<W>(value, optional),
Builtin::Acosh => builtin_acosh::<W>(value, optional),
Builtin::Atanh => builtin_atanh::<W>(value, optional),
Builtin::Infinite => QueryResult::Owned(OwnedValue::Float(f64::INFINITY)),
Builtin::Nan => QueryResult::Owned(OwnedValue::Float(f64::NAN)),
Builtin::IsInfinite => builtin_isinfinite::<W>(value, optional),
Builtin::IsNan => builtin_isnan::<W>(value, optional),
Builtin::IsNormal => builtin_isnormal::<W>(value, optional),
Builtin::IsFinite => builtin_isfinite::<W>(value, optional),
Builtin::Debug => builtin_debug::<W>(value, optional),
Builtin::DebugMsg(msg) => builtin_debug_msg::<W>(msg, value, optional),
Builtin::Env => builtin_env::<W>(value, optional),
Builtin::EnvVar(var) => builtin_envvar::<W, S>(var, value, optional),
Builtin::EnvObject(name) => builtin_env_object::<W>(name, optional),
Builtin::StrEnv(name) => builtin_strenv::<W>(name, optional),
Builtin::NullLit => QueryResult::Owned(OwnedValue::Null),
Builtin::Trim => builtin_trim::<W>(value, optional),
Builtin::Ltrim => builtin_ltrim::<W>(value, optional),
Builtin::Rtrim => builtin_rtrim::<W>(value, optional),
Builtin::Transpose => builtin_transpose::<W>(value, optional),
Builtin::BSearch(x) => builtin_bsearch::<W, S>(x, value, optional),
Builtin::ModuleMeta(name) => builtin_modulemeta::<W>(name, value, optional),
Builtin::Pick(keys) => builtin_pick::<W, S>(keys, value, optional),
Builtin::Omit(keys) => builtin_omit::<W, S>(keys, value, optional),
Builtin::Tag => builtin_tag::<W>(value),
Builtin::Anchor => builtin_anchor::<W>(),
Builtin::Style => builtin_style::<W>(value),
Builtin::Kind => builtin_kind::<W>(value),
Builtin::Line => builtin_line::<W>(),
Builtin::Column => builtin_column::<W>(),
Builtin::DocumentIndex => builtin_document_index::<W>(),
Builtin::Shuffle => builtin_shuffle::<W>(value, optional),
Builtin::Pivot => builtin_pivot::<W>(value, optional),
Builtin::SplitDoc => {
QueryResult::One(value.clone())
}
Builtin::Key => {
QueryResult::Owned(OwnedValue::Null)
}
Builtin::Del(path) => builtin_del::<W>(path, value, optional),
Builtin::Now => builtin_now::<W>(),
Builtin::Abs => builtin_fabs::<W>(value, optional), Builtin::Builtins => builtin_builtins::<W>(),
Builtin::Normals => builtin_normals::<W>(value),
Builtin::Finites => builtin_finites::<W>(value),
Builtin::Limit(n_expr, expr) => builtin_limit::<W, S>(n_expr, expr, value, optional),
Builtin::FirstStream(expr) => builtin_first_stream::<W, S>(expr, value, optional),
Builtin::LastStream(expr) => builtin_last_stream::<W, S>(expr, value, optional),
Builtin::NthStream(n_expr, expr) => {
builtin_nth_stream::<W, S>(n_expr, expr, value, optional)
}
Builtin::Range(n) => builtin_range::<W, S>(n, value, optional),
Builtin::RangeFromTo(from, to) => builtin_range_from_to::<W, S>(from, to, value, optional),
Builtin::RangeFromToBy(from, to, by) => {
builtin_range_from_to_by::<W, S>(from, to, by, value, optional)
}
Builtin::IsEmpty(expr) => builtin_isempty::<W, S>(expr, value, optional),
Builtin::RecurseDown => builtin_recurse::<W, S>(value, optional),
Builtin::Gmtime => builtin_gmtime::<W>(value, optional),
Builtin::Localtime => builtin_localtime::<W>(value, optional),
Builtin::Mktime => builtin_mktime::<W>(value, optional),
Builtin::Strftime(fmt) => builtin_strftime::<W, S>(fmt, value, optional),
Builtin::Strptime(fmt) => builtin_strptime::<W, S>(fmt, value, optional),
Builtin::Todate => builtin_todate::<W>(value, optional),
Builtin::Fromdate => builtin_fromdate::<W>(value, optional),
Builtin::Todateiso8601 => builtin_todate::<W>(value, optional), Builtin::Fromdateiso8601 => builtin_fromdate::<W>(value, optional),
Builtin::Combinations => builtin_combinations::<W>(value, optional),
Builtin::CombinationsN(n) => builtin_combinations_n::<W, S>(n, value, optional),
Builtin::Trunc => builtin_trunc::<W>(value, optional),
Builtin::ToBoolean => builtin_toboolean::<W>(value, optional),
Builtin::Skip(n_expr, expr) => builtin_skip::<W, S>(n_expr, expr, value, optional),
Builtin::FromUnix => builtin_from_unix::<W>(value, optional),
Builtin::ToUnix => builtin_to_unix::<W>(value, optional),
Builtin::Tz(zone) => builtin_tz::<W, S>(zone, value, optional),
Builtin::Load(file_expr) => builtin_load::<W, S>(file_expr, value, optional),
Builtin::AtOffset(_) => QueryResult::Error(EvalError::new(
"at_offset requires document cursor context".to_string(),
)),
Builtin::AtPosition(_, _) => QueryResult::Error(EvalError::new(
"at_position requires document cursor context".to_string(),
)),
}
}
fn builtin_length<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
match &value {
StandardJson::Null => QueryResult::Owned(OwnedValue::Int(0)),
StandardJson::String(s) => {
if let Ok(cow) = s.as_str() {
QueryResult::Owned(OwnedValue::Int(cow.chars().count() as i64))
} else {
QueryResult::Owned(OwnedValue::Int(0))
}
}
StandardJson::Array(elements) => {
QueryResult::Owned(OwnedValue::Int((*elements).count() as i64))
}
StandardJson::Object(fields) => {
QueryResult::Owned(OwnedValue::Int((*fields).count() as i64))
}
StandardJson::Number(n) => {
if let Ok(i) = n.as_i64() {
QueryResult::Owned(OwnedValue::Int(i.abs()))
} else if let Ok(f) = n.as_f64() {
QueryResult::Owned(OwnedValue::Float(f.abs()))
} else {
QueryResult::Owned(OwnedValue::Int(0))
}
}
_ if optional => QueryResult::None,
_ => QueryResult::Error(EvalError::new(format!(
"{} has no length",
type_name(&value)
))),
}
}
fn builtin_utf8bytelength<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
match &value {
StandardJson::String(s) => {
if let Ok(cow) = s.as_str() {
QueryResult::Owned(OwnedValue::Int(cow.len() as i64))
} else {
QueryResult::Owned(OwnedValue::Int(0))
}
}
_ if optional => QueryResult::None,
_ => QueryResult::Error(EvalError::type_error("string", type_name(&value))),
}
}
fn builtin_keys<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
optional: bool,
sorted: bool,
) -> QueryResult<'a, W> {
match value {
StandardJson::Object(fields) => {
let mut keys: Vec<String> = Vec::new();
for field in fields {
if let StandardJson::String(k) = field.key() {
if let Ok(cow) = k.as_str() {
keys.push(cow.into_owned());
}
}
}
if sorted {
keys.sort();
}
let arr: Vec<OwnedValue> = keys.into_iter().map(OwnedValue::String).collect();
QueryResult::Owned(OwnedValue::Array(arr))
}
StandardJson::Array(elements) => {
let len = elements.count();
let arr: Vec<OwnedValue> = (0..len).map(|i| OwnedValue::Int(i as i64)).collect();
QueryResult::Owned(OwnedValue::Array(arr))
}
_ if optional => QueryResult::None,
_ => QueryResult::Error(EvalError::type_error("object or array", type_name(&value))),
}
}
fn builtin_has<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
key_expr: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let key_result = eval_single::<W, S>(key_expr, value.clone(), optional);
let key_owned = match result_to_owned(key_result) {
Ok(v) => v,
Err(e) => return QueryResult::Error(e),
};
match (&value, &key_owned) {
(StandardJson::Null, _) => QueryResult::Owned(OwnedValue::Bool(false)),
(StandardJson::Object(fields), OwnedValue::String(key)) => {
let found = (*fields).clone().any(|f| {
if let StandardJson::String(k) = f.key() {
if let Ok(cow) = k.as_str() {
return cow.as_ref() == key;
}
}
false
});
QueryResult::Owned(OwnedValue::Bool(found))
}
(StandardJson::Array(elements), OwnedValue::Int(idx)) => {
let len = (*elements).count() as i64;
let in_bounds = if S::NEGATIVE_INDEX_IN_HAS {
if *idx >= 0 {
*idx < len
} else {
idx.abs() <= len
}
} else {
*idx >= 0 && *idx < len
};
QueryResult::Owned(OwnedValue::Bool(in_bounds))
}
_ if optional => QueryResult::None,
_ => QueryResult::Error(EvalError::new(
"has() requires object+string or array+number",
)),
}
}
fn builtin_in<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
obj_expr: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let key_owned = to_owned(&value);
let obj_result = eval_single::<W, S>(obj_expr, value.clone(), optional);
let obj_owned = match obj_result {
QueryResult::One(o) => to_owned(&o),
QueryResult::Many(os) => {
if let Some(o) = os.into_iter().next() {
to_owned(&o)
} else if optional {
return QueryResult::None;
} else {
return QueryResult::Error(EvalError::new(
"in() requires an object or array argument",
));
}
}
QueryResult::Owned(o) => o,
QueryResult::Error(e) => return QueryResult::Error(e),
_ if optional => return QueryResult::None,
_ => {
return QueryResult::Error(EvalError::new("in() requires an object or array argument"));
}
};
match (&key_owned, &obj_owned) {
(OwnedValue::String(key), OwnedValue::Object(fields)) => {
let found = fields.keys().any(|k| k == key);
QueryResult::Owned(OwnedValue::Bool(found))
}
(OwnedValue::Int(idx), OwnedValue::Array(elements)) => {
let len = elements.len() as i64;
let in_bounds = if S::NEGATIVE_INDEX_IN_HAS {
if *idx >= 0 {
*idx < len
} else {
idx.abs() <= len
}
} else {
*idx >= 0 && *idx < len
};
QueryResult::Owned(OwnedValue::Bool(in_bounds))
}
_ if optional => QueryResult::None,
_ => QueryResult::Error(EvalError::new(
"in() requires string/number key and object/array",
)),
}
}
fn builtin_select<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
cond: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let cond_result = eval_single::<W, S>(cond, value.clone(), optional);
let is_truthy = match &cond_result {
QueryResult::One(v) => to_owned(v).is_truthy(),
QueryResult::OneCursor(_) => unreachable!("eval_single never produces OneCursor"),
QueryResult::Owned(v) => v.is_truthy(),
QueryResult::Many(vs) => vs.first().map(|v| to_owned(v).is_truthy()).unwrap_or(false),
QueryResult::ManyOwned(vs) => vs.first().map(|v| v.is_truthy()).unwrap_or(false),
QueryResult::None => false,
QueryResult::Error(e) => return QueryResult::Error(e.clone()),
QueryResult::Break(label) => return QueryResult::Break(label.clone()),
};
if is_truthy {
QueryResult::One(value)
} else {
QueryResult::None
}
}
fn builtin_map<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
f: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
match value {
StandardJson::Array(elements) => {
let mut results = Vec::new();
for elem in elements {
match eval_single::<W, S>(f, elem, optional).materialize_cursor() {
QueryResult::One(v) => results.push(to_owned(&v)),
QueryResult::OneCursor(_) => unreachable!(),
QueryResult::Owned(v) => results.push(v),
QueryResult::Many(vs) => results.extend(vs.iter().map(to_owned)),
QueryResult::ManyOwned(vs) => results.extend(vs),
QueryResult::None => {}
QueryResult::Error(e) => return QueryResult::Error(e),
QueryResult::Break(label) => return QueryResult::Break(label),
}
}
QueryResult::Owned(OwnedValue::Array(results))
}
_ if optional => QueryResult::None,
_ => QueryResult::Error(EvalError::type_error("array", type_name(&value))),
}
}
fn builtin_map_values<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
f: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
match value {
StandardJson::Object(fields) => {
let mut result_map = IndexMap::new();
for field in fields {
let key = if let StandardJson::String(k) = field.key() {
if let Ok(cow) = k.as_str() {
cow.into_owned()
} else {
continue;
}
} else {
continue;
};
let field_val = field.value();
match eval_single::<W, S>(f, field_val, optional).materialize_cursor() {
QueryResult::One(v) => {
result_map.insert(key, to_owned(&v));
}
QueryResult::OneCursor(_) => unreachable!(),
QueryResult::Owned(v) => {
result_map.insert(key, v);
}
QueryResult::Many(vs) => {
if let Some(v) = vs.first() {
result_map.insert(key, to_owned(v));
}
}
QueryResult::ManyOwned(vs) => {
if let Some(v) = vs.into_iter().next() {
result_map.insert(key, v);
}
}
QueryResult::None => {}
QueryResult::Error(e) => return QueryResult::Error(e),
QueryResult::Break(label) => return QueryResult::Break(label),
}
}
QueryResult::Owned(OwnedValue::Object(result_map))
}
StandardJson::Array(elements) => {
let mut results = Vec::new();
for elem in elements {
match eval_single::<W, S>(f, elem, optional).materialize_cursor() {
QueryResult::One(v) => results.push(to_owned(&v)),
QueryResult::OneCursor(_) => unreachable!(),
QueryResult::Owned(v) => results.push(v),
QueryResult::Many(vs) => {
if let Some(v) = vs.first() {
results.push(to_owned(v));
}
}
QueryResult::ManyOwned(vs) => {
if let Some(v) = vs.into_iter().next() {
results.push(v);
}
}
QueryResult::None => {}
QueryResult::Error(e) => return QueryResult::Error(e),
QueryResult::Break(label) => return QueryResult::Break(label),
}
}
QueryResult::Owned(OwnedValue::Array(results))
}
_ if optional => QueryResult::None,
_ => QueryResult::Error(EvalError::type_error("object or array", type_name(&value))),
}
}
fn builtin_add<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
match value {
StandardJson::Array(elements) => {
let items: Vec<OwnedValue> = elements.map(|e| to_owned(&e)).collect();
if items.is_empty() {
return QueryResult::Owned(OwnedValue::Null);
}
let mut acc = items.into_iter();
let first = acc.next().unwrap();
let result = acc.try_fold(first, arith_add::<S>);
match result {
Ok(v) => QueryResult::Owned(v),
Err(e) => QueryResult::Error(e),
}
}
_ if optional => QueryResult::None,
_ => QueryResult::Error(EvalError::type_error("array", type_name(&value))),
}
}
fn builtin_any<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
match value {
StandardJson::Array(elements) => {
for elem in elements {
let owned = to_owned(&elem);
if owned.is_truthy() {
return QueryResult::Owned(OwnedValue::Bool(true));
}
}
QueryResult::Owned(OwnedValue::Bool(false))
}
_ if optional => QueryResult::None,
_ => QueryResult::Error(EvalError::type_error("array", type_name(&value))),
}
}
fn builtin_all<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
match value {
StandardJson::Array(elements) => {
for elem in elements {
let owned = to_owned(&elem);
if !owned.is_truthy() {
return QueryResult::Owned(OwnedValue::Bool(false));
}
}
QueryResult::Owned(OwnedValue::Bool(true))
}
_ if optional => QueryResult::None,
_ => QueryResult::Error(EvalError::type_error("array", type_name(&value))),
}
}
fn builtin_min<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
match value {
StandardJson::Array(elements) => {
let items: Vec<OwnedValue> = elements.map(|e| to_owned(&e)).collect();
if items.is_empty() {
return QueryResult::Owned(OwnedValue::Null);
}
let min = items.into_iter().min_by(compare_values).unwrap();
QueryResult::Owned(min)
}
_ if optional => QueryResult::None,
_ => QueryResult::Error(EvalError::type_error("array", type_name(&value))),
}
}
fn builtin_max<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
match value {
StandardJson::Array(elements) => {
let items: Vec<OwnedValue> = elements.map(|e| to_owned(&e)).collect();
if items.is_empty() {
return QueryResult::Owned(OwnedValue::Null);
}
let max = items.into_iter().max_by(compare_values).unwrap();
QueryResult::Owned(max)
}
_ if optional => QueryResult::None,
_ => QueryResult::Error(EvalError::type_error("array", type_name(&value))),
}
}
fn builtin_min_by<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
f: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
match value {
StandardJson::Array(elements) => {
let items: Vec<StandardJson<'a, W>> = elements.collect();
if items.is_empty() {
return QueryResult::Owned(OwnedValue::Null);
}
let mut keyed: Vec<(OwnedValue, StandardJson<'a, W>)> = Vec::new();
for item in items {
match eval_single::<W, S>(f, item.clone(), optional) {
QueryResult::One(v) => keyed.push((to_owned(&v), item)),
QueryResult::Owned(v) => keyed.push((v, item)),
QueryResult::Error(e) => return QueryResult::Error(e),
_ => keyed.push((OwnedValue::Null, item)),
}
}
let min = keyed
.into_iter()
.min_by(|(a, _), (b, _)| compare_values(a, b))
.map(|(_, v)| to_owned(&v))
.unwrap();
QueryResult::Owned(min)
}
_ if optional => QueryResult::None,
_ => QueryResult::Error(EvalError::type_error("array", type_name(&value))),
}
}
fn builtin_max_by<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
f: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
match value {
StandardJson::Array(elements) => {
let items: Vec<StandardJson<'a, W>> = elements.collect();
if items.is_empty() {
return QueryResult::Owned(OwnedValue::Null);
}
let mut keyed: Vec<(OwnedValue, StandardJson<'a, W>)> = Vec::new();
for item in items {
match eval_single::<W, S>(f, item.clone(), optional) {
QueryResult::One(v) => keyed.push((to_owned(&v), item)),
QueryResult::Owned(v) => keyed.push((v, item)),
QueryResult::Error(e) => return QueryResult::Error(e),
_ => keyed.push((OwnedValue::Null, item)),
}
}
let max = keyed
.into_iter()
.max_by(|(a, _), (b, _)| compare_values(a, b))
.map(|(_, v)| to_owned(&v))
.unwrap();
QueryResult::Owned(max)
}
_ if optional => QueryResult::None,
_ => QueryResult::Error(EvalError::type_error("array", type_name(&value))),
}
}
fn builtin_ascii_downcase<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
match &value {
StandardJson::String(s) => {
if let Ok(cow) = s.as_str() {
let lowered: String = cow.chars().map(|c| c.to_ascii_lowercase()).collect();
QueryResult::Owned(OwnedValue::String(lowered))
} else {
QueryResult::Owned(OwnedValue::String(String::new()))
}
}
_ if optional => QueryResult::None,
_ => QueryResult::Error(EvalError::type_error("string", type_name(&value))),
}
}
fn builtin_ascii_upcase<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
match &value {
StandardJson::String(s) => {
if let Ok(cow) = s.as_str() {
let uppered: String = cow.chars().map(|c| c.to_ascii_uppercase()).collect();
QueryResult::Owned(OwnedValue::String(uppered))
} else {
QueryResult::Owned(OwnedValue::String(String::new()))
}
}
_ if optional => QueryResult::None,
_ => QueryResult::Error(EvalError::type_error("string", type_name(&value))),
}
}
fn builtin_ltrimstr<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
prefix_expr: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let prefix_result = eval_single::<W, S>(prefix_expr, value.clone(), optional);
let prefix = match result_to_owned(prefix_result) {
Ok(OwnedValue::String(s)) => s,
Ok(_) => return QueryResult::Error(EvalError::type_error("string", "non-string")),
Err(e) => return QueryResult::Error(e),
};
match &value {
StandardJson::String(s) => {
if let Ok(cow) = s.as_str() {
let result = if cow.starts_with(&prefix) {
cow[prefix.len()..].to_string()
} else {
cow.into_owned()
};
QueryResult::Owned(OwnedValue::String(result))
} else {
QueryResult::Owned(OwnedValue::String(String::new()))
}
}
_ if optional => QueryResult::None,
_ => QueryResult::Error(EvalError::type_error("string", type_name(&value))),
}
}
fn builtin_rtrimstr<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
suffix_expr: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let suffix_result = eval_single::<W, S>(suffix_expr, value.clone(), optional);
let suffix = match result_to_owned(suffix_result) {
Ok(OwnedValue::String(s)) => s,
Ok(_) => return QueryResult::Error(EvalError::type_error("string", "non-string")),
Err(e) => return QueryResult::Error(e),
};
match &value {
StandardJson::String(s) => {
if let Ok(cow) = s.as_str() {
let result = if cow.ends_with(&suffix) {
cow[..cow.len() - suffix.len()].to_string()
} else {
cow.into_owned()
};
QueryResult::Owned(OwnedValue::String(result))
} else {
QueryResult::Owned(OwnedValue::String(String::new()))
}
}
_ if optional => QueryResult::None,
_ => QueryResult::Error(EvalError::type_error("string", type_name(&value))),
}
}
fn builtin_startswith<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
prefix_expr: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let prefix_result = eval_single::<W, S>(prefix_expr, value.clone(), optional);
let prefix = match result_to_owned(prefix_result) {
Ok(OwnedValue::String(s)) => s,
Ok(_) => return QueryResult::Error(EvalError::type_error("string", "non-string")),
Err(e) => return QueryResult::Error(e),
};
match &value {
StandardJson::String(s) => {
if let Ok(cow) = s.as_str() {
QueryResult::Owned(OwnedValue::Bool(cow.starts_with(&prefix)))
} else {
QueryResult::Owned(OwnedValue::Bool(false))
}
}
_ if optional => QueryResult::None,
_ => QueryResult::Error(EvalError::type_error("string", type_name(&value))),
}
}
fn builtin_endswith<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
suffix_expr: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let suffix_result = eval_single::<W, S>(suffix_expr, value.clone(), optional);
let suffix = match result_to_owned(suffix_result) {
Ok(OwnedValue::String(s)) => s,
Ok(_) => return QueryResult::Error(EvalError::type_error("string", "non-string")),
Err(e) => return QueryResult::Error(e),
};
match &value {
StandardJson::String(s) => {
if let Ok(cow) = s.as_str() {
QueryResult::Owned(OwnedValue::Bool(cow.ends_with(&suffix)))
} else {
QueryResult::Owned(OwnedValue::Bool(false))
}
}
_ if optional => QueryResult::None,
_ => QueryResult::Error(EvalError::type_error("string", type_name(&value))),
}
}
fn builtin_split<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
sep_expr: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let sep_result = eval_single::<W, S>(sep_expr, value.clone(), optional);
let sep = match result_to_owned(sep_result) {
Ok(OwnedValue::String(s)) => s,
Ok(_) => return QueryResult::Error(EvalError::type_error("string", "non-string")),
Err(e) => return QueryResult::Error(e),
};
match &value {
StandardJson::String(s) => {
if let Ok(cow) = s.as_str() {
let parts: Vec<OwnedValue> = if sep.is_empty() {
cow.chars()
.map(|c| OwnedValue::String(c.to_string()))
.collect()
} else {
cow.split(&sep)
.map(|p| OwnedValue::String(p.to_string()))
.collect()
};
QueryResult::Owned(OwnedValue::Array(parts))
} else {
QueryResult::Owned(OwnedValue::Array(vec![]))
}
}
_ if optional => QueryResult::None,
_ => QueryResult::Error(EvalError::type_error("string", type_name(&value))),
}
}
fn builtin_join<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
sep_expr: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let sep_result = eval_single::<W, S>(sep_expr, value.clone(), optional);
let sep = match result_to_owned(sep_result) {
Ok(OwnedValue::String(s)) => s,
Ok(_) => return QueryResult::Error(EvalError::type_error("string", "non-string")),
Err(e) => return QueryResult::Error(e),
};
match value {
StandardJson::Array(elements) => {
let mut parts: Vec<String> = Vec::new();
for elem in elements {
match &elem {
StandardJson::String(s) => {
if let Ok(cow) = s.as_str() {
parts.push(cow.into_owned());
}
}
StandardJson::Null => {
}
_ => {
parts.push(to_owned(&elem).to_json());
}
}
}
QueryResult::Owned(OwnedValue::String(parts.join(&sep)))
}
_ if optional => QueryResult::None,
_ => QueryResult::Error(EvalError::type_error("array", type_name(&value))),
}
}
fn builtin_contains<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
b_expr: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let b_result = eval_single::<W, S>(b_expr, value.clone(), optional);
let b = match result_to_owned(b_result) {
Ok(v) => v,
Err(e) => return QueryResult::Error(e),
};
let input = to_owned(&value);
QueryResult::Owned(OwnedValue::Bool(owned_contains(&input, &b)))
}
fn owned_contains(a: &OwnedValue, b: &OwnedValue) -> bool {
match (a, b) {
(OwnedValue::String(a_str), OwnedValue::String(b_str)) => a_str.contains(b_str.as_str()),
(OwnedValue::Array(a_arr), OwnedValue::Array(b_arr)) => b_arr
.iter()
.all(|b_elem| a_arr.iter().any(|a_elem| owned_contains(a_elem, b_elem))),
(OwnedValue::Object(a_obj), OwnedValue::Object(b_obj)) => b_obj.iter().all(|(k, b_val)| {
a_obj
.get(k)
.is_some_and(|a_val| owned_contains(a_val, b_val))
}),
_ => a == b,
}
}
fn builtin_inside<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
b_expr: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let b_result = eval_single::<W, S>(b_expr, value.clone(), optional);
let b = match result_to_owned(b_result) {
Ok(v) => v,
Err(e) => return QueryResult::Error(e),
};
let input = to_owned(&value);
QueryResult::Owned(OwnedValue::Bool(owned_contains(&b, &input)))
}
fn builtin_first<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
_optional: bool,
) -> QueryResult<'a, W> {
match value {
StandardJson::Array(elements) => match elements.get(0) {
Some(v) => QueryResult::One(v),
None => QueryResult::Owned(OwnedValue::Null),
},
StandardJson::Null => QueryResult::Owned(OwnedValue::Null),
_ => QueryResult::Error(EvalError::type_error("array", type_name(&value))),
}
}
fn builtin_last<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
_optional: bool,
) -> QueryResult<'a, W> {
match value {
StandardJson::Array(elements) => {
let items: Vec<_> = elements.collect();
if items.is_empty() {
QueryResult::Owned(OwnedValue::Null)
} else {
QueryResult::Owned(to_owned(&items[items.len() - 1]))
}
}
StandardJson::Null => QueryResult::Owned(OwnedValue::Null),
_ => QueryResult::Error(EvalError::type_error("array", type_name(&value))),
}
}
fn builtin_nth<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
n_expr: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
if matches!(value, StandardJson::Null) {
return QueryResult::Owned(OwnedValue::Null);
}
let n_result = eval_single::<W, S>(n_expr, value.clone(), optional);
let n = match result_to_owned(n_result) {
Ok(OwnedValue::Int(i)) => i,
Ok(_) => return QueryResult::Error(EvalError::type_error("number", "non-number")),
Err(e) => return QueryResult::Error(e),
};
match value {
StandardJson::Array(elements) => match get_element_at_index::<W>(elements, n) {
Some(v) => QueryResult::One(v),
None => QueryResult::Owned(OwnedValue::Null),
},
_ if optional => QueryResult::None,
_ => QueryResult::Error(EvalError::type_error("array", type_name(&value))),
}
}
fn builtin_reverse<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
_optional: bool,
) -> QueryResult<'a, W> {
match value {
StandardJson::Array(elements) => {
let mut items: Vec<OwnedValue> = elements.map(|e| to_owned(&e)).collect();
items.reverse();
QueryResult::Owned(OwnedValue::Array(items))
}
StandardJson::String(s) => {
if let Ok(cow) = s.as_str() {
let reversed: String = cow.chars().rev().collect();
QueryResult::Owned(OwnedValue::String(reversed))
} else {
QueryResult::Owned(OwnedValue::String(String::new()))
}
}
StandardJson::Null => QueryResult::Owned(OwnedValue::Array(Vec::new())),
_ => QueryResult::Error(EvalError::type_error("array or string", type_name(&value))),
}
}
fn builtin_flatten<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
optional: bool,
depth: usize,
) -> QueryResult<'a, W> {
match value {
StandardJson::Array(elements) => {
let items: Vec<OwnedValue> = elements.map(|e| to_owned(&e)).collect();
let flattened = flatten_owned(items, depth);
QueryResult::Owned(OwnedValue::Array(flattened))
}
_ if optional => QueryResult::None,
_ => QueryResult::Error(EvalError::type_error("array", type_name(&value))),
}
}
fn flatten_owned(items: Vec<OwnedValue>, depth: usize) -> Vec<OwnedValue> {
if depth == 0 {
return items;
}
let mut result = Vec::new();
for item in items {
match item {
OwnedValue::Array(inner) => {
result.extend(flatten_owned(inner, depth - 1));
}
other => result.push(other),
}
}
result
}
fn builtin_flatten_depth<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
depth_expr: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let depth_result = eval_single::<W, S>(depth_expr, value.clone(), optional);
let depth = match result_to_owned(depth_result) {
Ok(OwnedValue::Int(d)) if d >= 0 => d as usize,
Ok(OwnedValue::Int(_)) => {
return QueryResult::Error(EvalError::new("depth must be non-negative"));
}
Ok(_) => return QueryResult::Error(EvalError::type_error("number", "non-number")),
Err(e) => return QueryResult::Error(e),
};
builtin_flatten::<W>(value, optional, depth)
}
fn builtin_group_by<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
f: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
match value {
StandardJson::Array(elements) => {
let items: Vec<StandardJson<'a, W>> = elements.collect();
let mut keyed: Vec<(OwnedValue, OwnedValue)> = Vec::new();
for item in items {
let key = match eval_single::<W, S>(f, item.clone(), optional) {
QueryResult::One(v) => to_owned(&v),
QueryResult::Owned(v) => v,
QueryResult::Error(e) => return QueryResult::Error(e),
_ => OwnedValue::Null,
};
keyed.push((key, to_owned(&item)));
}
keyed.sort_by(|(a, _), (b, _)| compare_values(a, b));
let mut groups: Vec<OwnedValue> = Vec::new();
let mut current_group: Vec<OwnedValue> = Vec::new();
let mut current_key: Option<OwnedValue> = None;
for (key, item) in keyed {
match ¤t_key {
Some(k) if compare_values(k, &key) == core::cmp::Ordering::Equal => {
current_group.push(item);
}
_ => {
if !current_group.is_empty() {
groups.push(OwnedValue::Array(current_group));
}
current_group = vec![item];
current_key = Some(key);
}
}
}
if !current_group.is_empty() {
groups.push(OwnedValue::Array(current_group));
}
QueryResult::Owned(OwnedValue::Array(groups))
}
_ if optional => QueryResult::None,
_ => QueryResult::Error(EvalError::type_error("array", type_name(&value))),
}
}
fn builtin_unique<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
match value {
StandardJson::Array(elements) => {
let mut items: Vec<OwnedValue> = elements.map(|e| to_owned(&e)).collect();
items.sort_by(compare_values);
items.dedup_by(|a, b| compare_values(a, b) == core::cmp::Ordering::Equal);
QueryResult::Owned(OwnedValue::Array(items))
}
_ if optional => QueryResult::None,
_ => QueryResult::Error(EvalError::type_error("array", type_name(&value))),
}
}
fn builtin_unique_by<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
f: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
match value {
StandardJson::Array(elements) => {
let items: Vec<StandardJson<'a, W>> = elements.collect();
let mut keyed: Vec<(OwnedValue, OwnedValue)> = Vec::new();
for item in items {
let key = match eval_single::<W, S>(f, item.clone(), optional) {
QueryResult::One(v) => to_owned(&v),
QueryResult::Owned(v) => v,
QueryResult::Error(e) => return QueryResult::Error(e),
_ => OwnedValue::Null,
};
keyed.push((key, to_owned(&item)));
}
keyed.sort_by(|(a, _), (b, _)| compare_values(a, b));
keyed.dedup_by(|(a, _), (b, _)| compare_values(a, b) == core::cmp::Ordering::Equal);
let result: Vec<OwnedValue> = keyed.into_iter().map(|(_, v)| v).collect();
QueryResult::Owned(OwnedValue::Array(result))
}
_ if optional => QueryResult::None,
_ => QueryResult::Error(EvalError::type_error("array", type_name(&value))),
}
}
fn builtin_sort<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
match value {
StandardJson::Array(elements) => {
let mut items: Vec<OwnedValue> = elements.map(|e| to_owned(&e)).collect();
items.sort_by(compare_values);
QueryResult::Owned(OwnedValue::Array(items))
}
_ if optional => QueryResult::None,
_ => QueryResult::Error(EvalError::type_error("array", type_name(&value))),
}
}
fn builtin_sort_by<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
f: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
match value {
StandardJson::Array(elements) => {
let items: Vec<StandardJson<'a, W>> = elements.collect();
let mut keyed: Vec<(OwnedValue, OwnedValue)> = Vec::new();
for item in items {
let key = match eval_single::<W, S>(f, item.clone(), optional) {
QueryResult::One(v) => to_owned(&v),
QueryResult::Owned(v) => v,
QueryResult::Error(e) => return QueryResult::Error(e),
_ => OwnedValue::Null,
};
keyed.push((key, to_owned(&item)));
}
keyed.sort_by(|(a, _), (b, _)| compare_values(a, b));
let result: Vec<OwnedValue> = keyed.into_iter().map(|(_, v)| v).collect();
QueryResult::Owned(OwnedValue::Array(result))
}
_ if optional => QueryResult::None,
_ => QueryResult::Error(EvalError::type_error("array", type_name(&value))),
}
}
fn builtin_to_entries<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
match value {
StandardJson::Object(fields) => {
let mut entries: Vec<OwnedValue> = Vec::new();
for field in fields {
let key = if let StandardJson::String(k) = field.key() {
if let Ok(cow) = k.as_str() {
cow.into_owned()
} else {
continue;
}
} else {
continue;
};
let val = to_owned(&field.value());
let mut entry = IndexMap::new();
entry.insert("key".to_string(), OwnedValue::String(key));
entry.insert("value".to_string(), val);
entries.push(OwnedValue::Object(entry));
}
QueryResult::Owned(OwnedValue::Array(entries))
}
_ if optional => QueryResult::None,
_ => QueryResult::Error(EvalError::type_error("object", type_name(&value))),
}
}
fn builtin_from_entries<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
match value {
StandardJson::Array(elements) => {
let mut result = IndexMap::new();
for elem in elements {
if let StandardJson::Object(fields) = elem {
let owned = to_owned(&StandardJson::Object(fields));
if let OwnedValue::Object(obj) = owned {
let key = obj
.get("key")
.or_else(|| obj.get("name"))
.or_else(|| obj.get("k"));
let val = obj
.get("value")
.or_else(|| obj.get("v"))
.cloned()
.unwrap_or(OwnedValue::Null);
if let Some(OwnedValue::String(k)) = key {
result.insert(k.clone(), val);
}
}
}
}
QueryResult::Owned(OwnedValue::Object(result))
}
_ if optional => QueryResult::None,
_ => QueryResult::Error(EvalError::type_error("array", type_name(&value))),
}
}
fn builtin_with_entries<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
f: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
match value {
StandardJson::Object(fields) => {
let mut entries: Vec<OwnedValue> = Vec::new();
for field in fields {
let key = if let StandardJson::String(k) = field.key() {
if let Ok(cow) = k.as_str() {
cow.into_owned()
} else {
continue;
}
} else {
continue;
};
let val = to_owned(&field.value());
let mut entry = IndexMap::new();
entry.insert("key".to_string(), OwnedValue::String(key));
entry.insert("value".to_string(), val);
entries.push(OwnedValue::Object(entry));
}
let mut transformed: Vec<OwnedValue> = Vec::new();
for entry in entries {
let entry_json = owned_to_json_bytes(&entry);
let index = crate::json::JsonIndex::build(&entry_json);
let cursor = index.root(&entry_json);
match eval_single::<Vec<u64>, S>(f, cursor.value(), optional).materialize_cursor() {
QueryResult::One(v) => transformed.push(to_owned(&v)),
QueryResult::OneCursor(_) => unreachable!(),
QueryResult::Owned(v) => transformed.push(v),
QueryResult::Many(vs) => {
for v in vs {
transformed.push(to_owned(&v));
}
}
QueryResult::ManyOwned(vs) => transformed.extend(vs),
QueryResult::None => {}
QueryResult::Error(e) => return QueryResult::Error(e),
QueryResult::Break(label) => return QueryResult::Break(label),
}
}
let mut result = IndexMap::new();
for entry in transformed {
if let OwnedValue::Object(obj) = entry {
let key = obj
.get("key")
.or_else(|| obj.get("name"))
.or_else(|| obj.get("k"));
let val = obj
.get("value")
.or_else(|| obj.get("v"))
.cloned()
.unwrap_or(OwnedValue::Null);
if let Some(OwnedValue::String(k)) = key {
result.insert(k.clone(), val);
}
}
}
QueryResult::Owned(OwnedValue::Object(result))
}
_ if optional => QueryResult::None,
_ => QueryResult::Error(EvalError::type_error("object", type_name(&value))),
}
}
fn owned_to_json_bytes(value: &OwnedValue) -> Vec<u8> {
value.to_json().into_bytes()
}
fn eval_string_interpolation<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
parts: &[StringPart],
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let mut result = String::new();
for part in parts {
match part {
StringPart::Literal(s) => result.push_str(s),
StringPart::Expr(expr) => {
let val = eval_single::<W, S>(expr, value.clone(), optional).materialize_cursor();
let s = match val {
QueryResult::One(v) => owned_to_string(&to_owned(&v)),
QueryResult::OneCursor(_) => unreachable!(),
QueryResult::Owned(v) => owned_to_string(&v),
QueryResult::Many(vs) => {
if let Some(v) = vs.first() {
owned_to_string(&to_owned(v))
} else {
String::new()
}
}
QueryResult::ManyOwned(vs) => {
if let Some(v) = vs.first() {
owned_to_string(v)
} else {
String::new()
}
}
QueryResult::None => String::new(),
QueryResult::Error(e) => return QueryResult::Error(e),
QueryResult::Break(label) => return QueryResult::Break(label),
};
result.push_str(&s);
}
}
}
QueryResult::Owned(OwnedValue::String(result))
}
fn owned_to_string(value: &OwnedValue) -> String {
match value {
OwnedValue::Null => "null".to_string(),
OwnedValue::Bool(true) => "true".to_string(),
OwnedValue::Bool(false) => "false".to_string(),
OwnedValue::Int(n) => format!("{}", n),
OwnedValue::Float(f) => format!("{}", f),
OwnedValue::String(s) => s.clone(), OwnedValue::Array(_) | OwnedValue::Object(_) => value.to_json(),
}
}
fn eval_format<'a, W: Clone + AsRef<[u64]>>(
format_type: FormatType,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let owned = to_owned(&value);
let result = match format_type {
FormatType::Text => format_text(&owned),
FormatType::Json => format_json(&owned),
FormatType::Uri => format_uri(&owned, optional),
FormatType::Csv => format_csv(&owned, optional),
FormatType::Tsv => format_tsv(&owned, optional),
FormatType::Dsv(delimiter) => format_dsv(&owned, &delimiter, optional),
FormatType::Base64 => format_base64(&owned, optional),
FormatType::Base64d => format_base64d(&owned, optional),
FormatType::Html => format_html(&owned, optional),
FormatType::Sh => format_sh(&owned, optional),
FormatType::Urid => format_urid(&owned, optional),
FormatType::Yaml => format_yaml(&owned),
FormatType::Props => format_props(&owned),
};
match result {
Ok(s) => QueryResult::Owned(OwnedValue::String(s)),
Err(e) => QueryResult::Error(e),
}
}
fn format_text(value: &OwnedValue) -> Result<String, EvalError> {
Ok(owned_to_string(value))
}
fn format_json(value: &OwnedValue) -> Result<String, EvalError> {
Ok(value.to_json())
}
fn format_uri(value: &OwnedValue, _optional: bool) -> Result<String, EvalError> {
let s = match value {
OwnedValue::String(s) => s.clone(),
OwnedValue::Int(n) => n.to_string(),
OwnedValue::Float(f) => f.to_string(),
OwnedValue::Bool(b) => {
if *b {
"true".to_string()
} else {
"false".to_string()
}
}
OwnedValue::Null => "null".to_string(),
_ => return Err(EvalError::type_error("string", value.type_name())),
};
let mut result = String::new();
for c in s.chars() {
if c.is_ascii_alphanumeric() || c == '-' || c == '_' || c == '.' || c == '~' {
result.push(c);
} else {
for b in c.to_string().as_bytes() {
result.push_str(&format!("%{:02X}", b));
}
}
}
Ok(result)
}
fn format_urid(value: &OwnedValue, optional: bool) -> Result<String, EvalError> {
match value {
OwnedValue::String(s) => {
let mut result = String::new();
let bytes = s.as_bytes();
let mut i = 0;
while i < bytes.len() {
if bytes[i] == b'%' && i + 2 < bytes.len() {
if let (Some(h1), Some(h2)) = (
(bytes[i + 1] as char).to_digit(16),
(bytes[i + 2] as char).to_digit(16),
) {
let decoded = (h1 * 16 + h2) as u8;
result.push(decoded as char);
i += 3;
continue;
}
}
result.push(bytes[i] as char);
i += 1;
}
Ok(result)
}
_ if optional => Ok(String::new()),
_ => Err(EvalError::type_error("string", value.type_name())),
}
}
fn format_csv(value: &OwnedValue, optional: bool) -> Result<String, EvalError> {
match value {
OwnedValue::Array(arr) => {
let parts: Vec<String> = arr
.iter()
.map(|v| match v {
OwnedValue::String(s) => {
if s.contains('"') || s.contains(',') || s.contains('\n') {
format!("\"{}\"", s.replace('"', "\"\""))
} else {
s.clone()
}
}
OwnedValue::Null => String::new(),
other => owned_to_string(other),
})
.collect();
Ok(parts.join(","))
}
_ if optional => Ok(String::new()),
_ => Err(EvalError::type_error("array", value.type_name())),
}
}
fn format_tsv(value: &OwnedValue, optional: bool) -> Result<String, EvalError> {
match value {
OwnedValue::Array(arr) => {
let parts: Vec<String> = arr
.iter()
.map(|v| match v {
OwnedValue::String(s) => s
.replace('\\', "\\\\")
.replace('\t', "\\t")
.replace('\n', "\\n")
.replace('\r', "\\r"),
OwnedValue::Null => String::new(),
other => owned_to_string(other),
})
.collect();
Ok(parts.join("\t"))
}
_ if optional => Ok(String::new()),
_ => Err(EvalError::type_error("array", value.type_name())),
}
}
fn format_dsv(value: &OwnedValue, delimiter: &str, optional: bool) -> Result<String, EvalError> {
match value {
OwnedValue::Array(arr) => {
let parts: Vec<String> = arr
.iter()
.map(|v| match v {
OwnedValue::String(s) => {
if s.contains(delimiter) || s.contains('"') || s.contains('\n') {
format!("\"{}\"", s.replace('"', "\"\""))
} else {
s.clone()
}
}
OwnedValue::Null => String::new(),
other => owned_to_string(other),
})
.collect();
Ok(parts.join(delimiter))
}
_ if optional => Ok(String::new()),
_ => Err(EvalError::type_error("array", value.type_name())),
}
}
fn format_base64(value: &OwnedValue, optional: bool) -> Result<String, EvalError> {
match value {
OwnedValue::String(s) => {
const ALPHABET: &[u8] =
b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
let bytes = s.as_bytes();
let mut result = String::new();
for chunk in bytes.chunks(3) {
let b0 = chunk[0] as u32;
let b1 = chunk.get(1).map(|&b| b as u32).unwrap_or(0);
let b2 = chunk.get(2).map(|&b| b as u32).unwrap_or(0);
let triple = (b0 << 16) | (b1 << 8) | b2;
result.push(ALPHABET[((triple >> 18) & 0x3F) as usize] as char);
result.push(ALPHABET[((triple >> 12) & 0x3F) as usize] as char);
if chunk.len() > 1 {
result.push(ALPHABET[((triple >> 6) & 0x3F) as usize] as char);
} else {
result.push('=');
}
if chunk.len() > 2 {
result.push(ALPHABET[(triple & 0x3F) as usize] as char);
} else {
result.push('=');
}
}
Ok(result)
}
_ if optional => Ok(String::new()),
_ => Err(EvalError::type_error("string", value.type_name())),
}
}
fn format_base64d(value: &OwnedValue, optional: bool) -> Result<String, EvalError> {
match value {
OwnedValue::String(s) => {
fn decode_char(c: u8) -> Option<u8> {
match c {
b'A'..=b'Z' => Some(c - b'A'),
b'a'..=b'z' => Some(c - b'a' + 26),
b'0'..=b'9' => Some(c - b'0' + 52),
b'+' => Some(62),
b'/' => Some(63),
b'=' => Some(0), _ => None,
}
}
let s = s.replace(|c: char| c.is_whitespace(), "");
let bytes: Vec<u8> = s.bytes().collect();
let mut result = Vec::new();
for chunk in bytes.chunks(4) {
if chunk.len() < 4 {
break;
}
let a = decode_char(chunk[0]).ok_or_else(|| EvalError::new("invalid base64"))?;
let b = decode_char(chunk[1]).ok_or_else(|| EvalError::new("invalid base64"))?;
let c_val =
decode_char(chunk[2]).ok_or_else(|| EvalError::new("invalid base64"))?;
let d = decode_char(chunk[3]).ok_or_else(|| EvalError::new("invalid base64"))?;
let triple =
((a as u32) << 18) | ((b as u32) << 12) | ((c_val as u32) << 6) | (d as u32);
result.push(((triple >> 16) & 0xFF) as u8);
if chunk[2] != b'=' {
result.push(((triple >> 8) & 0xFF) as u8);
}
if chunk[3] != b'=' {
result.push((triple & 0xFF) as u8);
}
}
String::from_utf8(result).map_err(|_| EvalError::new("invalid UTF-8 in decoded base64"))
}
_ if optional => Ok(String::new()),
_ => Err(EvalError::type_error("string", value.type_name())),
}
}
fn format_html(value: &OwnedValue, _optional: bool) -> Result<String, EvalError> {
let s = match value {
OwnedValue::String(s) => s.clone(),
OwnedValue::Int(n) => n.to_string(),
OwnedValue::Float(f) => f.to_string(),
OwnedValue::Bool(b) => {
if *b {
"true".to_string()
} else {
"false".to_string()
}
}
OwnedValue::Null => "null".to_string(),
_ => return Err(EvalError::type_error("string", value.type_name())),
};
let mut result = String::new();
for c in s.chars() {
match c {
'<' => result.push_str("<"),
'>' => result.push_str(">"),
'&' => result.push_str("&"),
'"' => result.push_str("""),
'\'' => result.push_str("'"),
_ => result.push(c),
}
}
Ok(result)
}
fn shell_quote_value(value: &OwnedValue) -> String {
match value {
OwnedValue::String(s) => {
if s.contains('\'') {
let escaped = s.replace('\'', "'\\''");
format!("'{}'", escaped)
} else {
format!("'{}'", s)
}
}
OwnedValue::Int(n) => n.to_string(),
OwnedValue::Float(f) => f.to_string(),
OwnedValue::Bool(b) => {
if *b {
"true".to_string()
} else {
"false".to_string()
}
}
OwnedValue::Null => "null".to_string(),
_ => String::new(),
}
}
fn format_sh(value: &OwnedValue, _optional: bool) -> Result<String, EvalError> {
match value {
OwnedValue::String(s) => {
if s.contains('\'') {
let escaped = s.replace('\'', "'\\''");
Ok(format!("'{}'", escaped))
} else {
Ok(format!("'{}'", s))
}
}
OwnedValue::Array(arr) => {
let parts: Vec<String> = arr.iter().map(shell_quote_value).collect();
Ok(parts.join(" "))
}
OwnedValue::Int(n) => Ok(n.to_string()),
OwnedValue::Float(f) => Ok(f.to_string()),
OwnedValue::Bool(b) => Ok(if *b {
"true".to_string()
} else {
"false".to_string()
}),
OwnedValue::Null => Ok("null".to_string()),
_ => Err(EvalError::type_error("string", value.type_name())),
}
}
fn format_yaml(value: &OwnedValue) -> Result<String, EvalError> {
Ok(owned_to_yaml(value))
}
fn format_props(value: &OwnedValue) -> Result<String, EvalError> {
let mut lines = Vec::new();
format_props_recursive(value, String::new(), &mut lines);
Ok(lines.join("\n"))
}
fn format_props_recursive(value: &OwnedValue, prefix: String, lines: &mut Vec<String>) {
match value {
OwnedValue::Object(obj) => {
for (key, val) in obj.iter() {
let new_prefix = if prefix.is_empty() {
key.clone()
} else {
format!("{}.{}", prefix, key)
};
format_props_recursive(val, new_prefix, lines);
}
}
OwnedValue::Array(arr) => {
for (idx, val) in arr.iter().enumerate() {
let new_prefix = if prefix.is_empty() {
format!("{}", idx)
} else {
format!("{}.{}", prefix, idx)
};
format_props_recursive(val, new_prefix, lines);
}
}
_ => {
let value_str = props_value_to_string(value);
if prefix.is_empty() {
lines.push(value_str);
} else {
lines.push(format!("{} = {}", prefix, value_str));
}
}
}
}
fn props_value_to_string(value: &OwnedValue) -> String {
match value {
OwnedValue::Null => "null".to_string(),
OwnedValue::Bool(true) => "true".to_string(),
OwnedValue::Bool(false) => "false".to_string(),
OwnedValue::Int(n) => format!("{}", n),
OwnedValue::Float(f) => {
if f.is_nan() {
".nan".to_string()
} else if f.is_infinite() {
if *f > 0.0 {
".inf".to_string()
} else {
"-.inf".to_string()
}
} else {
format!("{}", f)
}
}
OwnedValue::String(s) => {
s.replace(['\n', '\r'], " ")
}
OwnedValue::Object(_) | OwnedValue::Array(_) => value.to_json(),
}
}
fn owned_to_yaml(value: &OwnedValue) -> String {
match value {
OwnedValue::Null => "null".to_string(),
OwnedValue::Bool(true) => "true".to_string(),
OwnedValue::Bool(false) => "false".to_string(),
OwnedValue::Int(n) => format!("{}", n),
OwnedValue::Float(f) => {
if f.is_nan() {
".nan".to_string()
} else if f.is_infinite() {
if *f > 0.0 {
".inf".to_string()
} else {
"-.inf".to_string()
}
} else {
format!("{}", f)
}
}
OwnedValue::String(s) => yaml_quote_string(s),
OwnedValue::Array(arr) => {
let items: Vec<String> = arr.iter().map(owned_to_yaml).collect();
format!("[{}]", items.join(", "))
}
OwnedValue::Object(obj) => {
let pairs: Vec<String> = obj
.iter()
.map(|(k, v)| format!("{}: {}", yaml_quote_string(k), owned_to_yaml(v)))
.collect();
format!("{{{}}}", pairs.join(", "))
}
}
}
fn yaml_quote_string(s: &str) -> String {
let needs_quoting = s.is_empty()
|| s.starts_with(' ')
|| s.ends_with(' ')
|| s.contains(':')
|| s.contains('#')
|| s.contains('\n')
|| s.contains('\r')
|| s.contains('\t')
|| s.contains('"')
|| s.contains('\'')
|| s.contains('\\')
|| s.starts_with('-')
|| s.starts_with('?')
|| s.starts_with('*')
|| s.starts_with('&')
|| s.starts_with('!')
|| s.starts_with('|')
|| s.starts_with('>')
|| s.starts_with('%')
|| s.starts_with('@')
|| s.starts_with('`')
|| s.starts_with('{')
|| s.starts_with('}')
|| s.starts_with('[')
|| s.starts_with(']')
|| s.starts_with(',')
|| s == "true"
|| s == "false"
|| s == "null"
|| s == "~"
|| s == "yes"
|| s == "no"
|| s == "on"
|| s == "off"
|| s.parse::<i64>().is_ok()
|| s.parse::<f64>().is_ok();
if needs_quoting {
let mut result = String::with_capacity(s.len() + 2);
result.push('"');
for c in s.chars() {
match c {
'"' => result.push_str("\\\""),
'\\' => result.push_str("\\\\"),
'\n' => result.push_str("\\n"),
'\r' => result.push_str("\\r"),
'\t' => result.push_str("\\t"),
c if c.is_control() => {
result.push_str(&format!("\\x{:02x}", c as u32));
}
c => result.push(c),
}
}
result.push('"');
result
} else {
s.to_string()
}
}
fn builtin_tostring<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
_optional: bool,
) -> QueryResult<'a, W> {
let owned = to_owned(&value);
let s = match owned {
OwnedValue::String(s) => s,
OwnedValue::Null => "null".to_string(),
OwnedValue::Bool(true) => "true".to_string(),
OwnedValue::Bool(false) => "false".to_string(),
OwnedValue::Int(n) => format!("{}", n),
OwnedValue::Float(f) => format!("{}", f),
OwnedValue::Array(_) | OwnedValue::Object(_) => owned.to_json(),
};
QueryResult::Owned(OwnedValue::String(s))
}
fn builtin_tonumber<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
match &value {
StandardJson::Number(n) => {
if let Ok(i) = n.as_i64() {
QueryResult::Owned(OwnedValue::Int(i))
} else if let Ok(f) = n.as_f64() {
QueryResult::Owned(OwnedValue::Float(f))
} else {
QueryResult::Owned(OwnedValue::Int(0))
}
}
StandardJson::String(s) => {
if let Ok(cow) = s.as_str() {
let s = cow.as_ref().trim();
if let Ok(i) = s.parse::<i64>() {
QueryResult::Owned(OwnedValue::Int(i))
} else if let Ok(f) = s.parse::<f64>() {
QueryResult::Owned(OwnedValue::Float(f))
} else if optional {
QueryResult::None
} else {
QueryResult::Error(EvalError::new(format!("cannot parse '{}' as number", s)))
}
} else if optional {
QueryResult::None
} else {
QueryResult::Error(EvalError::new("invalid string"))
}
}
_ if optional => QueryResult::None,
_ => QueryResult::Error(EvalError::type_error("string or number", type_name(&value))),
}
}
fn builtin_toboolean<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
match &value {
StandardJson::Bool(b) => QueryResult::Owned(OwnedValue::Bool(*b)),
StandardJson::String(s) => {
if let Ok(cow) = s.as_str() {
match cow.as_ref() {
"true" => QueryResult::Owned(OwnedValue::Bool(true)),
"false" => QueryResult::Owned(OwnedValue::Bool(false)),
_ if optional => QueryResult::None,
other => QueryResult::Error(EvalError::new(format!(
"string ({:?}) cannot be parsed as a boolean",
other
))),
}
} else if optional {
QueryResult::None
} else {
QueryResult::Error(EvalError::new("invalid string"))
}
}
_ if optional => QueryResult::None,
_ => QueryResult::Error(EvalError::new(format!(
"{} cannot be parsed as a boolean",
type_name(&value)
))),
}
}
fn builtin_skip<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
n_expr: &Expr,
expr: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let n_result = eval_single::<W, S>(n_expr, value.clone(), optional);
let n = match n_result {
QueryResult::One(v) => {
if let StandardJson::Number(num) = v {
num.as_i64().unwrap_or(0) as usize
} else {
return QueryResult::Error(EvalError::type_error("number", type_name(&v)));
}
}
QueryResult::Owned(OwnedValue::Int(i)) => i as usize,
QueryResult::Owned(OwnedValue::Float(f)) => f as usize,
QueryResult::Error(e) => return QueryResult::Error(e),
_ => return QueryResult::Error(EvalError::type_error("number", "null")),
};
let result = eval_single::<W, S>(expr, value, optional);
match result {
QueryResult::One(v) => {
if n == 0 {
QueryResult::Owned(to_owned(&v))
} else {
QueryResult::None
}
}
QueryResult::OneCursor(c) => {
if n == 0 {
QueryResult::Owned(to_owned(&c.value()))
} else {
QueryResult::None
}
}
QueryResult::Owned(v) => {
if n == 0 {
QueryResult::Owned(v)
} else {
QueryResult::None
}
}
QueryResult::Many(results) => {
let skipped: Vec<OwnedValue> =
results.into_iter().skip(n).map(|v| to_owned(&v)).collect();
if skipped.is_empty() {
QueryResult::None
} else if skipped.len() == 1 {
QueryResult::Owned(skipped.into_iter().next().unwrap())
} else {
QueryResult::ManyOwned(skipped)
}
}
QueryResult::ManyOwned(results) => {
let skipped: Vec<OwnedValue> = results.into_iter().skip(n).collect();
if skipped.is_empty() {
QueryResult::None
} else if skipped.len() == 1 {
QueryResult::Owned(skipped.into_iter().next().unwrap())
} else {
QueryResult::ManyOwned(skipped)
}
}
QueryResult::None => QueryResult::None,
QueryResult::Error(e) => QueryResult::Error(e),
QueryResult::Break(label) => QueryResult::Break(label),
}
}
fn builtin_tojson<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
_optional: bool,
) -> QueryResult<'a, W> {
let owned = to_owned(&value);
let json_string = owned.to_json();
QueryResult::Owned(OwnedValue::String(json_string))
}
fn builtin_fromjson<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
match &value {
StandardJson::String(s) => {
if let Ok(cow) = s.as_str() {
let json_str = cow.as_ref();
match parse_json_string(json_str) {
Ok(owned) => QueryResult::Owned(owned),
Err(_) if optional => QueryResult::None,
Err(e) => QueryResult::Error(EvalError::new(format!("fromjson: {}", e))),
}
} else if optional {
QueryResult::None
} else {
QueryResult::Error(EvalError::new("invalid string"))
}
}
_ if optional => QueryResult::None,
_ => QueryResult::Error(EvalError::type_error("string", type_name(&value))),
}
}
fn parse_json_string(s: &str) -> Result<OwnedValue, String> {
let bytes = s.as_bytes();
let mut pos = 0;
while pos < bytes.len() && matches!(bytes[pos], b' ' | b'\t' | b'\n' | b'\r') {
pos += 1;
}
if pos >= bytes.len() {
return Err("empty input".to_string());
}
parse_json_value(bytes, &mut pos)
}
fn parse_json_value(bytes: &[u8], pos: &mut usize) -> Result<OwnedValue, String> {
while *pos < bytes.len() && matches!(bytes[*pos], b' ' | b'\t' | b'\n' | b'\r') {
*pos += 1;
}
if *pos >= bytes.len() {
return Err("unexpected end of input".to_string());
}
match bytes[*pos] {
b'n' => {
if bytes[*pos..].starts_with(b"null") {
*pos += 4;
Ok(OwnedValue::Null)
} else {
Err("expected 'null'".to_string())
}
}
b't' => {
if bytes[*pos..].starts_with(b"true") {
*pos += 4;
Ok(OwnedValue::Bool(true))
} else {
Err("expected 'true'".to_string())
}
}
b'f' => {
if bytes[*pos..].starts_with(b"false") {
*pos += 5;
Ok(OwnedValue::Bool(false))
} else {
Err("expected 'false'".to_string())
}
}
b'"' => {
parse_json_string_value(bytes, pos)
}
b'[' => {
parse_json_array(bytes, pos)
}
b'{' => {
parse_json_object(bytes, pos)
}
b'-' | b'0'..=b'9' => {
parse_json_number(bytes, pos)
}
c => Err(format!("unexpected character: '{}'", c as char)),
}
}
fn parse_json_string_value(bytes: &[u8], pos: &mut usize) -> Result<OwnedValue, String> {
if bytes[*pos] != b'"' {
return Err("expected '\"'".to_string());
}
*pos += 1;
let mut result = String::new();
while *pos < bytes.len() {
match bytes[*pos] {
b'"' => {
*pos += 1;
return Ok(OwnedValue::String(result));
}
b'\\' => {
*pos += 1;
if *pos >= bytes.len() {
return Err("unexpected end of string".to_string());
}
match bytes[*pos] {
b'"' => result.push('"'),
b'\\' => result.push('\\'),
b'/' => result.push('/'),
b'b' => result.push('\x08'),
b'f' => result.push('\x0C'),
b'n' => result.push('\n'),
b'r' => result.push('\r'),
b't' => result.push('\t'),
b'u' => {
*pos += 1;
if *pos + 4 > bytes.len() {
return Err("invalid unicode escape".to_string());
}
let hex = core::str::from_utf8(&bytes[*pos..*pos + 4])
.map_err(|_| "invalid unicode escape")?;
let codepoint =
u32::from_str_radix(hex, 16).map_err(|_| "invalid unicode escape")?;
if (0xD800..=0xDBFF).contains(&codepoint) {
*pos += 4;
if *pos + 6 <= bytes.len()
&& bytes[*pos] == b'\\'
&& bytes[*pos + 1] == b'u'
{
let hex2 = core::str::from_utf8(&bytes[*pos + 2..*pos + 6])
.map_err(|_| "invalid unicode escape")?;
let low = u32::from_str_radix(hex2, 16)
.map_err(|_| "invalid unicode escape")?;
if (0xDC00..=0xDFFF).contains(&low) {
let combined =
0x10000 + ((codepoint - 0xD800) << 10) + (low - 0xDC00);
if let Some(c) = char::from_u32(combined) {
result.push(c);
}
*pos += 5; } else {
result.push('\u{FFFD}');
*pos -= 1; }
} else {
result.push('\u{FFFD}');
*pos -= 1;
}
} else if let Some(c) = char::from_u32(codepoint) {
result.push(c);
*pos += 3; } else {
return Err("invalid unicode codepoint".to_string());
}
}
c => return Err(format!("invalid escape sequence: \\{}", c as char)),
}
*pos += 1;
}
c => {
let remaining = &bytes[*pos..];
if let Ok(s) = core::str::from_utf8(remaining) {
if let Some(c) = s.chars().next() {
result.push(c);
*pos += c.len_utf8();
} else {
return Err("unexpected end of string".to_string());
}
} else {
result.push(c as char);
*pos += 1;
}
}
}
}
Err("unterminated string".to_string())
}
fn parse_json_array(bytes: &[u8], pos: &mut usize) -> Result<OwnedValue, String> {
if bytes[*pos] != b'[' {
return Err("expected '['".to_string());
}
*pos += 1;
let mut elements = Vec::new();
while *pos < bytes.len() && matches!(bytes[*pos], b' ' | b'\t' | b'\n' | b'\r') {
*pos += 1;
}
if *pos < bytes.len() && bytes[*pos] == b']' {
*pos += 1;
return Ok(OwnedValue::Array(elements));
}
loop {
let value = parse_json_value(bytes, pos)?;
elements.push(value);
while *pos < bytes.len() && matches!(bytes[*pos], b' ' | b'\t' | b'\n' | b'\r') {
*pos += 1;
}
if *pos >= bytes.len() {
return Err("unterminated array".to_string());
}
match bytes[*pos] {
b']' => {
*pos += 1;
return Ok(OwnedValue::Array(elements));
}
b',' => {
*pos += 1;
}
c => return Err(format!("expected ',' or ']', got '{}'", c as char)),
}
}
}
fn parse_json_object(bytes: &[u8], pos: &mut usize) -> Result<OwnedValue, String> {
if bytes[*pos] != b'{' {
return Err("expected '{{'".to_string());
}
*pos += 1;
let mut entries = IndexMap::new();
while *pos < bytes.len() && matches!(bytes[*pos], b' ' | b'\t' | b'\n' | b'\r') {
*pos += 1;
}
if *pos < bytes.len() && bytes[*pos] == b'}' {
*pos += 1;
return Ok(OwnedValue::Object(entries));
}
loop {
while *pos < bytes.len() && matches!(bytes[*pos], b' ' | b'\t' | b'\n' | b'\r') {
*pos += 1;
}
let key = match parse_json_string_value(bytes, pos)? {
OwnedValue::String(s) => s,
_ => return Err("object key must be a string".to_string()),
};
while *pos < bytes.len() && matches!(bytes[*pos], b' ' | b'\t' | b'\n' | b'\r') {
*pos += 1;
}
if *pos >= bytes.len() || bytes[*pos] != b':' {
return Err("expected ':'".to_string());
}
*pos += 1;
let value = parse_json_value(bytes, pos)?;
entries.insert(key, value);
while *pos < bytes.len() && matches!(bytes[*pos], b' ' | b'\t' | b'\n' | b'\r') {
*pos += 1;
}
if *pos >= bytes.len() {
return Err("unterminated object".to_string());
}
match bytes[*pos] {
b'}' => {
*pos += 1;
return Ok(OwnedValue::Object(entries));
}
b',' => {
*pos += 1;
}
c => return Err(format!("expected ',' or '}}', got '{}'", c as char)),
}
}
}
fn parse_json_number(bytes: &[u8], pos: &mut usize) -> Result<OwnedValue, String> {
let start = *pos;
if *pos < bytes.len() && bytes[*pos] == b'-' {
*pos += 1;
}
if *pos >= bytes.len() {
return Err("expected number".to_string());
}
if bytes[*pos] == b'0' {
*pos += 1;
} else if bytes[*pos].is_ascii_digit() {
while *pos < bytes.len() && bytes[*pos].is_ascii_digit() {
*pos += 1;
}
} else {
return Err("expected digit".to_string());
}
let mut is_float = false;
if *pos < bytes.len() && bytes[*pos] == b'.' {
is_float = true;
*pos += 1;
if *pos >= bytes.len() || !bytes[*pos].is_ascii_digit() {
return Err("expected digit after decimal point".to_string());
}
while *pos < bytes.len() && bytes[*pos].is_ascii_digit() {
*pos += 1;
}
}
if *pos < bytes.len() && matches!(bytes[*pos], b'e' | b'E') {
is_float = true;
*pos += 1;
if *pos < bytes.len() && matches!(bytes[*pos], b'+' | b'-') {
*pos += 1;
}
if *pos >= bytes.len() || !bytes[*pos].is_ascii_digit() {
return Err("expected digit in exponent".to_string());
}
while *pos < bytes.len() && bytes[*pos].is_ascii_digit() {
*pos += 1;
}
}
let num_str =
core::str::from_utf8(&bytes[start..*pos]).map_err(|_| "invalid number encoding")?;
if is_float {
let f: f64 = num_str.parse().map_err(|_| "invalid float")?;
Ok(OwnedValue::Float(f))
} else {
if let Ok(i) = num_str.parse::<i64>() {
Ok(OwnedValue::Int(i))
} else {
let f: f64 = num_str.parse().map_err(|_| "invalid number")?;
Ok(OwnedValue::Float(f))
}
}
}
fn builtin_explode<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
match &value {
StandardJson::String(s) => {
if let Ok(cow) = s.as_str() {
let codepoints: Vec<OwnedValue> = cow
.chars()
.map(|c| OwnedValue::Int(c as u32 as i64))
.collect();
QueryResult::Owned(OwnedValue::Array(codepoints))
} else if optional {
QueryResult::None
} else {
QueryResult::Error(EvalError::new("invalid string"))
}
}
_ if optional => QueryResult::None,
_ => QueryResult::Error(EvalError::type_error("string", type_name(&value))),
}
}
fn builtin_implode<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
match &value {
StandardJson::Array(elements) => {
let mut result = String::new();
for elem in *elements {
if let StandardJson::Number(n) = elem {
if let Ok(codepoint) = n.as_i64() {
if let Some(c) = char::from_u32(codepoint as u32) {
result.push(c);
} else if optional {
continue;
} else {
return QueryResult::Error(EvalError::new(format!(
"invalid codepoint: {}",
codepoint
)));
}
}
}
}
QueryResult::Owned(OwnedValue::String(result))
}
_ if optional => QueryResult::None,
_ => QueryResult::Error(EvalError::type_error("array", type_name(&value))),
}
}
fn builtin_test<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
re_expr: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let pattern = match result_to_owned(eval_single::<W, S>(re_expr, value.clone(), optional)) {
Ok(OwnedValue::String(s)) => s,
Ok(_) if optional => return QueryResult::None,
Ok(_) => return QueryResult::Error(EvalError::type_error("string", "pattern")),
Err(e) => return QueryResult::Error(e),
};
match &value {
StandardJson::String(s) => {
if let Ok(cow) = s.as_str() {
QueryResult::Owned(OwnedValue::Bool(cow.contains(&pattern)))
} else if optional {
QueryResult::None
} else {
QueryResult::Error(EvalError::new("invalid string"))
}
}
_ if optional => QueryResult::None,
_ => QueryResult::Error(EvalError::type_error("string", type_name(&value))),
}
}
fn builtin_indices<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
s_expr: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let pattern = match result_to_owned(eval_single::<W, S>(s_expr, value.clone(), optional)) {
Ok(v) => v,
Err(e) => return QueryResult::Error(e),
};
match &value {
StandardJson::String(s) => {
let pattern_str = match &pattern {
OwnedValue::String(p) => p,
_ if optional => return QueryResult::None,
_ => return QueryResult::Error(EvalError::type_error("string", "pattern")),
};
if let Ok(cow) = s.as_str() {
let mut indices = Vec::new();
let mut start = 0;
while let Some(pos) = cow[start..].find(pattern_str.as_str()) {
indices.push(OwnedValue::Int((start + pos) as i64));
start += pos + 1;
if start >= cow.len() {
break;
}
}
QueryResult::Owned(OwnedValue::Array(indices))
} else if optional {
QueryResult::None
} else {
QueryResult::Error(EvalError::new("invalid string"))
}
}
StandardJson::Array(elements) => {
let mut indices = Vec::new();
for (i, elem) in (*elements).enumerate() {
if to_owned(&elem) == pattern {
indices.push(OwnedValue::Int(i as i64));
}
}
QueryResult::Owned(OwnedValue::Array(indices))
}
_ if optional => QueryResult::None,
_ => QueryResult::Error(EvalError::type_error("string or array", type_name(&value))),
}
}
fn builtin_index<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
s_expr: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let pattern = match result_to_owned(eval_single::<W, S>(s_expr, value.clone(), optional)) {
Ok(v) => v,
Err(e) => return QueryResult::Error(e),
};
match &value {
StandardJson::String(s) => {
let pattern_str = match &pattern {
OwnedValue::String(p) => p,
_ if optional => return QueryResult::None,
_ => return QueryResult::Error(EvalError::type_error("string", "pattern")),
};
if let Ok(cow) = s.as_str() {
if let Some(pos) = cow.find(pattern_str.as_str()) {
QueryResult::Owned(OwnedValue::Int(pos as i64))
} else {
QueryResult::Owned(OwnedValue::Null)
}
} else if optional {
QueryResult::None
} else {
QueryResult::Error(EvalError::new("invalid string"))
}
}
StandardJson::Array(elements) => {
for (i, elem) in (*elements).enumerate() {
if to_owned(&elem) == pattern {
return QueryResult::Owned(OwnedValue::Int(i as i64));
}
}
QueryResult::Owned(OwnedValue::Null)
}
_ if optional => QueryResult::None,
_ => QueryResult::Error(EvalError::type_error("string or array", type_name(&value))),
}
}
fn builtin_rindex<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
s_expr: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let pattern = match result_to_owned(eval_single::<W, S>(s_expr, value.clone(), optional)) {
Ok(v) => v,
Err(e) => return QueryResult::Error(e),
};
match &value {
StandardJson::String(s) => {
let pattern_str = match &pattern {
OwnedValue::String(p) => p,
_ if optional => return QueryResult::None,
_ => return QueryResult::Error(EvalError::type_error("string", "pattern")),
};
if let Ok(cow) = s.as_str() {
if let Some(pos) = cow.rfind(pattern_str.as_str()) {
QueryResult::Owned(OwnedValue::Int(pos as i64))
} else {
QueryResult::Owned(OwnedValue::Null)
}
} else if optional {
QueryResult::None
} else {
QueryResult::Error(EvalError::new("invalid string"))
}
}
StandardJson::Array(elements) => {
let items: Vec<_> = (*elements).collect();
for (i, elem) in items.iter().enumerate().rev() {
if to_owned(elem) == pattern {
return QueryResult::Owned(OwnedValue::Int(i as i64));
}
}
QueryResult::Owned(OwnedValue::Null)
}
_ if optional => QueryResult::None,
_ => QueryResult::Error(EvalError::type_error("string or array", type_name(&value))),
}
}
fn builtin_tojsonstream<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
_optional: bool,
) -> QueryResult<'a, W> {
let owned = to_owned(&value);
fn collect_stream(value: &OwnedValue, path: &[OwnedValue], results: &mut Vec<OwnedValue>) {
match value {
OwnedValue::Array(arr) => {
for (i, v) in arr.iter().enumerate() {
let mut new_path = path.to_vec();
new_path.push(OwnedValue::Int(i as i64));
collect_stream(v, &new_path, results);
}
}
OwnedValue::Object(obj) => {
for (k, v) in obj.iter() {
let mut new_path = path.to_vec();
new_path.push(OwnedValue::String(k.clone()));
collect_stream(v, &new_path, results);
}
}
_ => {
let entry =
OwnedValue::Array(vec![OwnedValue::Array(path.to_vec()), value.clone()]);
results.push(entry);
}
}
}
let mut results = Vec::new();
collect_stream(&owned, &[], &mut results);
QueryResult::Owned(OwnedValue::Array(results))
}
fn builtin_fromjsonstream<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
match &value {
StandardJson::Array(_) => {
QueryResult::Owned(to_owned(&value))
}
_ if optional => QueryResult::None,
_ => QueryResult::Error(EvalError::type_error("array", type_name(&value))),
}
}
fn builtin_getpath<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
path_expr: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let path = match result_to_owned(eval_single::<W, S>(path_expr, value.clone(), optional)) {
Ok(OwnedValue::Array(arr)) => arr,
Ok(_) if optional => return QueryResult::None,
Ok(_) => return QueryResult::Error(EvalError::type_error("array", "path")),
Err(e) => return QueryResult::Error(e),
};
let mut current = to_owned(&value);
for segment in path {
match (¤t, &segment) {
(OwnedValue::Null, _) => {
return QueryResult::Owned(OwnedValue::Null);
}
(OwnedValue::Object(obj), OwnedValue::String(key)) => {
current = obj.get(key).cloned().unwrap_or(OwnedValue::Null);
}
(OwnedValue::Array(arr), OwnedValue::Int(idx)) => {
let index = if *idx < 0 {
(arr.len() as i64 + *idx) as usize
} else {
*idx as usize
};
current = arr.get(index).cloned().unwrap_or(OwnedValue::Null);
}
_ if optional => return QueryResult::None,
_ => {
return QueryResult::Error(EvalError::new(format!(
"cannot index {} with {}",
current.type_name(),
segment.type_name()
)));
}
}
}
QueryResult::Owned(current)
}
#[cfg(feature = "regex")]
fn build_regex(pattern: &str, flags: Option<&str>) -> Result<regex::Regex, EvalError> {
let mut pattern = pattern.to_string();
if let Some(flags) = flags {
let mut prefix = String::from("(?");
for c in flags.chars() {
match c {
'i' => prefix.push('i'), 'x' => prefix.push('x'), 's' => prefix.push('s'), 'm' => prefix.push('m'), 'g' => {} 'p' => {} _ => {}
}
}
if prefix.len() > 2 {
prefix.push(')');
pattern = format!("{}{}", prefix, pattern);
}
}
regex::Regex::new(&pattern).map_err(|e| EvalError::new(format!("invalid regex: {}", e)))
}
#[cfg(feature = "regex")]
fn builtin_match<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
re_expr: &Expr,
flags: Option<&str>,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let pattern = match result_to_owned(eval_single::<W, S>(re_expr, value.clone(), optional)) {
Ok(OwnedValue::String(s)) => s,
Ok(_) if optional => return QueryResult::None,
Ok(_) => return QueryResult::Error(EvalError::type_error("string", "pattern")),
Err(e) => return QueryResult::Error(e),
};
let input = match &value {
StandardJson::String(s) => match s.as_str() {
Ok(cow) => cow.into_owned(),
Err(_) if optional => return QueryResult::None,
Err(_) => return QueryResult::Error(EvalError::new("invalid string")),
},
_ if optional => return QueryResult::None,
_ => return QueryResult::Error(EvalError::type_error("string", type_name(&value))),
};
let re = match build_regex(&pattern, flags) {
Ok(r) => r,
Err(_e) if optional => return QueryResult::None,
Err(e) => return QueryResult::Error(e),
};
let global = flags.map(|f| f.contains('g')).unwrap_or(false);
if global {
let matches: Vec<OwnedValue> = re
.find_iter(&input)
.map(|m| build_match_object(&re, m.as_str(), m.start(), &input))
.collect();
QueryResult::Owned(OwnedValue::Array(matches))
} else {
match re.find(&input) {
Some(m) => QueryResult::Owned(build_match_object(&re, m.as_str(), m.start(), &input)),
None => QueryResult::Owned(OwnedValue::Null),
}
}
}
#[cfg(feature = "regex")]
fn build_match_object(re: ®ex::Regex, matched: &str, offset: usize, input: &str) -> OwnedValue {
let mut obj = IndexMap::new();
obj.insert("offset".to_string(), OwnedValue::Int(offset as i64));
obj.insert("length".to_string(), OwnedValue::Int(matched.len() as i64));
obj.insert(
"string".to_string(),
OwnedValue::String(matched.to_string()),
);
let mut captures = Vec::new();
if let Some(caps) = re.captures(input) {
for (i, name) in re.capture_names().enumerate() {
if i == 0 {
continue; }
let cap = caps.get(i);
let mut cap_obj = IndexMap::new();
cap_obj.insert(
"offset".to_string(),
cap.map(|m| OwnedValue::Int(m.start() as i64))
.unwrap_or(OwnedValue::Null),
);
cap_obj.insert(
"length".to_string(),
cap.map(|m| OwnedValue::Int(m.len() as i64))
.unwrap_or(OwnedValue::Int(0)),
);
cap_obj.insert(
"string".to_string(),
cap.map(|m| OwnedValue::String(m.as_str().to_string()))
.unwrap_or(OwnedValue::Null),
);
cap_obj.insert(
"name".to_string(),
name.map(|n| OwnedValue::String(n.to_string()))
.unwrap_or(OwnedValue::Null),
);
captures.push(OwnedValue::Object(cap_obj));
}
}
obj.insert("captures".to_string(), OwnedValue::Array(captures));
OwnedValue::Object(obj)
}
#[cfg(feature = "regex")]
fn builtin_capture<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
re_expr: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let pattern = match result_to_owned(eval_single::<W, S>(re_expr, value.clone(), optional)) {
Ok(OwnedValue::String(s)) => s,
Ok(_) if optional => return QueryResult::None,
Ok(_) => return QueryResult::Error(EvalError::type_error("string", "pattern")),
Err(e) => return QueryResult::Error(e),
};
let input = match &value {
StandardJson::String(s) => match s.as_str() {
Ok(cow) => cow.into_owned(),
Err(_) if optional => return QueryResult::None,
Err(_) => return QueryResult::Error(EvalError::new("invalid string")),
},
_ if optional => return QueryResult::None,
_ => return QueryResult::Error(EvalError::type_error("string", type_name(&value))),
};
let re = match build_regex(&pattern, None) {
Ok(r) => r,
Err(_e) if optional => return QueryResult::None,
Err(e) => return QueryResult::Error(e),
};
match re.captures(&input) {
Some(caps) => {
let mut result = IndexMap::new();
for name in re.capture_names().flatten() {
if let Some(m) = caps.name(name) {
result.insert(name.to_string(), OwnedValue::String(m.as_str().to_string()));
}
}
QueryResult::Owned(OwnedValue::Object(result))
}
None => QueryResult::Owned(OwnedValue::Null),
}
}
#[cfg(feature = "regex")]
fn builtin_scan<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
re_expr: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let pattern = match result_to_owned(eval_single::<W, S>(re_expr, value.clone(), optional)) {
Ok(OwnedValue::String(s)) => s,
Ok(_) if optional => return QueryResult::None,
Ok(_) => return QueryResult::Error(EvalError::type_error("string", "pattern")),
Err(e) => return QueryResult::Error(e),
};
let input = match &value {
StandardJson::String(s) => match s.as_str() {
Ok(cow) => cow.into_owned(),
Err(_) if optional => return QueryResult::None,
Err(_) => return QueryResult::Error(EvalError::new("invalid string")),
},
_ if optional => return QueryResult::None,
_ => return QueryResult::Error(EvalError::type_error("string", type_name(&value))),
};
let re = match build_regex(&pattern, None) {
Ok(r) => r,
Err(_e) if optional => return QueryResult::None,
Err(e) => return QueryResult::Error(e),
};
let matches: Vec<OwnedValue> = re
.find_iter(&input)
.map(|m| OwnedValue::String(m.as_str().to_string()))
.collect();
QueryResult::ManyOwned(matches)
}
#[cfg(feature = "regex")]
fn builtin_splits<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
re_expr: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let pattern = match result_to_owned(eval_single::<W, S>(re_expr, value.clone(), optional)) {
Ok(OwnedValue::String(s)) => s,
Ok(_) if optional => return QueryResult::None,
Ok(_) => return QueryResult::Error(EvalError::type_error("string", "pattern")),
Err(e) => return QueryResult::Error(e),
};
let input = match &value {
StandardJson::String(s) => match s.as_str() {
Ok(cow) => cow.into_owned(),
Err(_) if optional => return QueryResult::None,
Err(_) => return QueryResult::Error(EvalError::new("invalid string")),
},
_ if optional => return QueryResult::None,
_ => return QueryResult::Error(EvalError::type_error("string", type_name(&value))),
};
let re = match build_regex(&pattern, None) {
Ok(r) => r,
Err(_e) if optional => return QueryResult::None,
Err(e) => return QueryResult::Error(e),
};
let parts: Vec<OwnedValue> = re
.split(&input)
.map(|s| OwnedValue::String(s.to_string()))
.collect();
QueryResult::Owned(OwnedValue::Array(parts))
}
#[cfg(feature = "regex")]
fn builtin_sub<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
re_expr: &Expr,
replacement_expr: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let pattern = match result_to_owned(eval_single::<W, S>(re_expr, value.clone(), optional)) {
Ok(OwnedValue::String(s)) => s,
Ok(_) if optional => return QueryResult::None,
Ok(_) => return QueryResult::Error(EvalError::type_error("string", "pattern")),
Err(e) => return QueryResult::Error(e),
};
let replacement = match result_to_owned(eval_single::<W, S>(
replacement_expr,
value.clone(),
optional,
)) {
Ok(OwnedValue::String(s)) => s,
Ok(_) if optional => return QueryResult::None,
Ok(_) => return QueryResult::Error(EvalError::type_error("string", "replacement")),
Err(e) => return QueryResult::Error(e),
};
let input = match &value {
StandardJson::String(s) => match s.as_str() {
Ok(cow) => cow.into_owned(),
Err(_) if optional => return QueryResult::None,
Err(_) => return QueryResult::Error(EvalError::new("invalid string")),
},
_ if optional => return QueryResult::None,
_ => return QueryResult::Error(EvalError::type_error("string", type_name(&value))),
};
let re = match build_regex(&pattern, None) {
Ok(r) => r,
Err(_e) if optional => return QueryResult::None,
Err(e) => return QueryResult::Error(e),
};
let result = re.replace(&input, replacement.as_str());
QueryResult::Owned(OwnedValue::String(result.into_owned()))
}
#[cfg(feature = "regex")]
fn builtin_gsub<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
re_expr: &Expr,
replacement_expr: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let pattern = match result_to_owned(eval_single::<W, S>(re_expr, value.clone(), optional)) {
Ok(OwnedValue::String(s)) => s,
Ok(_) if optional => return QueryResult::None,
Ok(_) => return QueryResult::Error(EvalError::type_error("string", "pattern")),
Err(e) => return QueryResult::Error(e),
};
let replacement = match result_to_owned(eval_single::<W, S>(
replacement_expr,
value.clone(),
optional,
)) {
Ok(OwnedValue::String(s)) => s,
Ok(_) if optional => return QueryResult::None,
Ok(_) => return QueryResult::Error(EvalError::type_error("string", "replacement")),
Err(e) => return QueryResult::Error(e),
};
let input = match &value {
StandardJson::String(s) => match s.as_str() {
Ok(cow) => cow.into_owned(),
Err(_) if optional => return QueryResult::None,
Err(_) => return QueryResult::Error(EvalError::new("invalid string")),
},
_ if optional => return QueryResult::None,
_ => return QueryResult::Error(EvalError::type_error("string", type_name(&value))),
};
let re = match build_regex(&pattern, None) {
Ok(r) => r,
Err(_e) if optional => return QueryResult::None,
Err(e) => return QueryResult::Error(e),
};
let result = re.replace_all(&input, replacement.as_str());
QueryResult::Owned(OwnedValue::String(result.into_owned()))
}
#[cfg(feature = "regex")]
fn builtin_test_flags<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
re_expr: &Expr,
flags_expr: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let flags = match result_to_owned(eval_single::<W, S>(flags_expr, value.clone(), optional)) {
Ok(OwnedValue::String(s)) => s,
Ok(_) if optional => return QueryResult::None,
Ok(_) => return QueryResult::Error(EvalError::type_error("string", "flags")),
Err(e) => return QueryResult::Error(e),
};
let pattern = match result_to_owned(eval_single::<W, S>(re_expr, value.clone(), optional)) {
Ok(OwnedValue::String(s)) => s,
Ok(_) if optional => return QueryResult::None,
Ok(_) => return QueryResult::Error(EvalError::type_error("string", "pattern")),
Err(e) => return QueryResult::Error(e),
};
let input = match &value {
StandardJson::String(s) => match s.as_str() {
Ok(cow) => cow.into_owned(),
Err(_) if optional => return QueryResult::None,
Err(_) => return QueryResult::Error(EvalError::new("invalid string")),
},
_ if optional => return QueryResult::None,
_ => return QueryResult::Error(EvalError::type_error("string", type_name(&value))),
};
let re = match build_regex(&pattern, Some(&flags)) {
Ok(r) => r,
Err(_e) if optional => return QueryResult::None,
Err(e) => return QueryResult::Error(e),
};
QueryResult::Owned(OwnedValue::Bool(re.is_match(&input)))
}
#[cfg(feature = "regex")]
fn builtin_match_flags<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
re_expr: &Expr,
flags_expr: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let flags = match result_to_owned(eval_single::<W, S>(flags_expr, value.clone(), optional)) {
Ok(OwnedValue::String(s)) => s,
Ok(_) if optional => return QueryResult::None,
Ok(_) => return QueryResult::Error(EvalError::type_error("string", "flags")),
Err(e) => return QueryResult::Error(e),
};
builtin_match::<W, S>(re_expr, Some(&flags), value, optional)
}
#[cfg(feature = "regex")]
fn builtin_capture_flags<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
re_expr: &Expr,
flags_expr: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let flags = match result_to_owned(eval_single::<W, S>(flags_expr, value.clone(), optional)) {
Ok(OwnedValue::String(s)) => s,
Ok(_) if optional => return QueryResult::None,
Ok(_) => return QueryResult::Error(EvalError::type_error("string", "flags")),
Err(e) => return QueryResult::Error(e),
};
builtin_capture_with_flags::<W, S>(re_expr, Some(&flags), value, optional)
}
#[cfg(feature = "regex")]
fn builtin_capture_with_flags<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
re_expr: &Expr,
flags: Option<&str>,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let pattern = match result_to_owned(eval_single::<W, S>(re_expr, value.clone(), optional)) {
Ok(OwnedValue::String(s)) => s,
Ok(_) if optional => return QueryResult::None,
Ok(_) => return QueryResult::Error(EvalError::type_error("string", "pattern")),
Err(e) => return QueryResult::Error(e),
};
let input = match &value {
StandardJson::String(s) => match s.as_str() {
Ok(cow) => cow.into_owned(),
Err(_) if optional => return QueryResult::None,
Err(_) => return QueryResult::Error(EvalError::new("invalid string")),
},
_ if optional => return QueryResult::None,
_ => return QueryResult::Error(EvalError::type_error("string", type_name(&value))),
};
let re = match build_regex(&pattern, flags) {
Ok(r) => r,
Err(_e) if optional => return QueryResult::None,
Err(e) => return QueryResult::Error(e),
};
if let Some(caps) = re.captures(&input) {
let mut entries = IndexMap::new();
for name in re.capture_names().flatten() {
if let Some(m) = caps.name(name) {
entries.insert(name.to_string(), OwnedValue::String(m.as_str().to_string()));
}
}
QueryResult::Owned(OwnedValue::Object(entries))
} else if optional {
QueryResult::None
} else {
QueryResult::Owned(OwnedValue::Object(IndexMap::new()))
}
}
#[cfg(feature = "regex")]
fn builtin_sub_flags<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
re_expr: &Expr,
replacement_expr: &Expr,
flags_expr: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let flags = match result_to_owned(eval_single::<W, S>(flags_expr, value.clone(), optional)) {
Ok(OwnedValue::String(s)) => s,
Ok(_) if optional => return QueryResult::None,
Ok(_) => return QueryResult::Error(EvalError::type_error("string", "flags")),
Err(e) => return QueryResult::Error(e),
};
builtin_sub_with_flags::<W, S>(re_expr, replacement_expr, Some(&flags), value, optional)
}
#[cfg(feature = "regex")]
fn builtin_sub_with_flags<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
re_expr: &Expr,
replacement_expr: &Expr,
flags: Option<&str>,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let pattern = match result_to_owned(eval_single::<W, S>(re_expr, value.clone(), optional)) {
Ok(OwnedValue::String(s)) => s,
Ok(_) if optional => return QueryResult::None,
Ok(_) => return QueryResult::Error(EvalError::type_error("string", "pattern")),
Err(e) => return QueryResult::Error(e),
};
let replacement = match result_to_owned(eval_single::<W, S>(
replacement_expr,
value.clone(),
optional,
)) {
Ok(OwnedValue::String(s)) => s,
Ok(_) if optional => return QueryResult::None,
Ok(_) => return QueryResult::Error(EvalError::type_error("string", "replacement")),
Err(e) => return QueryResult::Error(e),
};
let input = match &value {
StandardJson::String(s) => match s.as_str() {
Ok(cow) => cow.into_owned(),
Err(_) if optional => return QueryResult::None,
Err(_) => return QueryResult::Error(EvalError::new("invalid string")),
},
_ if optional => return QueryResult::None,
_ => return QueryResult::Error(EvalError::type_error("string", type_name(&value))),
};
let re = match build_regex(&pattern, flags) {
Ok(r) => r,
Err(_e) if optional => return QueryResult::None,
Err(e) => return QueryResult::Error(e),
};
let replacement = convert_jq_replacement(&replacement);
let result = re.replace(&input, replacement.as_str());
QueryResult::Owned(OwnedValue::String(result.into_owned()))
}
#[cfg(feature = "regex")]
fn builtin_gsub_flags<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
re_expr: &Expr,
replacement_expr: &Expr,
flags_expr: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let flags = match result_to_owned(eval_single::<W, S>(flags_expr, value.clone(), optional)) {
Ok(OwnedValue::String(s)) => s,
Ok(_) if optional => return QueryResult::None,
Ok(_) => return QueryResult::Error(EvalError::type_error("string", "flags")),
Err(e) => return QueryResult::Error(e),
};
builtin_gsub_with_flags::<W, S>(re_expr, replacement_expr, Some(&flags), value, optional)
}
#[cfg(feature = "regex")]
fn builtin_gsub_with_flags<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
re_expr: &Expr,
replacement_expr: &Expr,
flags: Option<&str>,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let pattern = match result_to_owned(eval_single::<W, S>(re_expr, value.clone(), optional)) {
Ok(OwnedValue::String(s)) => s,
Ok(_) if optional => return QueryResult::None,
Ok(_) => return QueryResult::Error(EvalError::type_error("string", "pattern")),
Err(e) => return QueryResult::Error(e),
};
let replacement = match result_to_owned(eval_single::<W, S>(
replacement_expr,
value.clone(),
optional,
)) {
Ok(OwnedValue::String(s)) => s,
Ok(_) if optional => return QueryResult::None,
Ok(_) => return QueryResult::Error(EvalError::type_error("string", "replacement")),
Err(e) => return QueryResult::Error(e),
};
let input = match &value {
StandardJson::String(s) => match s.as_str() {
Ok(cow) => cow.into_owned(),
Err(_) if optional => return QueryResult::None,
Err(_) => return QueryResult::Error(EvalError::new("invalid string")),
},
_ if optional => return QueryResult::None,
_ => return QueryResult::Error(EvalError::type_error("string", type_name(&value))),
};
let re = match build_regex(&pattern, flags) {
Ok(r) => r,
Err(_e) if optional => return QueryResult::None,
Err(e) => return QueryResult::Error(e),
};
let replacement = convert_jq_replacement(&replacement);
let result = re.replace_all(&input, replacement.as_str());
QueryResult::Owned(OwnedValue::String(result.into_owned()))
}
#[cfg(feature = "regex")]
fn builtin_scan_flags<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
re_expr: &Expr,
flags_expr: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let flags = match result_to_owned(eval_single::<W, S>(flags_expr, value.clone(), optional)) {
Ok(OwnedValue::String(s)) => s,
Ok(_) if optional => return QueryResult::None,
Ok(_) => return QueryResult::Error(EvalError::type_error("string", "flags")),
Err(e) => return QueryResult::Error(e),
};
builtin_scan_with_flags::<W, S>(re_expr, Some(&flags), value, optional)
}
#[cfg(feature = "regex")]
fn builtin_scan_with_flags<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
re_expr: &Expr,
flags: Option<&str>,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let pattern = match result_to_owned(eval_single::<W, S>(re_expr, value.clone(), optional)) {
Ok(OwnedValue::String(s)) => s,
Ok(_) if optional => return QueryResult::None,
Ok(_) => return QueryResult::Error(EvalError::type_error("string", "pattern")),
Err(e) => return QueryResult::Error(e),
};
let input = match &value {
StandardJson::String(s) => match s.as_str() {
Ok(cow) => cow.into_owned(),
Err(_) if optional => return QueryResult::None,
Err(_) => return QueryResult::Error(EvalError::new("invalid string")),
},
_ if optional => return QueryResult::None,
_ => return QueryResult::Error(EvalError::type_error("string", type_name(&value))),
};
let re = match build_regex(&pattern, flags) {
Ok(r) => r,
Err(_e) if optional => return QueryResult::None,
Err(e) => return QueryResult::Error(e),
};
let mut results = Vec::new();
let capture_count = re.captures_len();
for caps in re.captures_iter(&input) {
if capture_count > 1 {
let mut captured = Vec::new();
for i in 1..capture_count {
if let Some(m) = caps.get(i) {
captured.push(OwnedValue::String(m.as_str().to_string()));
}
}
results.push(OwnedValue::Array(captured));
} else {
if let Some(m) = caps.get(0) {
results.push(OwnedValue::String(m.as_str().to_string()));
}
}
}
if results.is_empty() {
QueryResult::None
} else {
QueryResult::ManyOwned(results)
}
}
#[cfg(feature = "regex")]
fn builtin_split_regex<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
re_expr: &Expr,
flags_expr: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let flags = match result_to_owned(eval_single::<W, S>(flags_expr, value.clone(), optional)) {
Ok(OwnedValue::String(s)) => s,
Ok(_) if optional => return QueryResult::None,
Ok(_) => return QueryResult::Error(EvalError::type_error("string", "flags")),
Err(e) => return QueryResult::Error(e),
};
let pattern = match result_to_owned(eval_single::<W, S>(re_expr, value.clone(), optional)) {
Ok(OwnedValue::String(s)) => s,
Ok(_) if optional => return QueryResult::None,
Ok(_) => return QueryResult::Error(EvalError::type_error("string", "pattern")),
Err(e) => return QueryResult::Error(e),
};
let input = match &value {
StandardJson::String(s) => match s.as_str() {
Ok(cow) => cow.into_owned(),
Err(_) if optional => return QueryResult::None,
Err(_) => return QueryResult::Error(EvalError::new("invalid string")),
},
_ if optional => return QueryResult::None,
_ => return QueryResult::Error(EvalError::type_error("string", type_name(&value))),
};
let re = match build_regex(&pattern, Some(&flags)) {
Ok(r) => r,
Err(_e) if optional => return QueryResult::None,
Err(e) => return QueryResult::Error(e),
};
let parts: Vec<OwnedValue> = re
.split(&input)
.map(|s| OwnedValue::String(s.to_string()))
.collect();
QueryResult::Owned(OwnedValue::Array(parts))
}
#[cfg(feature = "regex")]
fn builtin_splits_flags<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
re_expr: &Expr,
flags_expr: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let flags = match result_to_owned(eval_single::<W, S>(flags_expr, value.clone(), optional)) {
Ok(OwnedValue::String(s)) => s,
Ok(_) if optional => return QueryResult::None,
Ok(_) => return QueryResult::Error(EvalError::type_error("string", "flags")),
Err(e) => return QueryResult::Error(e),
};
builtin_splits_with_flags::<W, S>(re_expr, Some(&flags), value, optional)
}
#[cfg(feature = "regex")]
fn builtin_splits_with_flags<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
re_expr: &Expr,
flags: Option<&str>,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let pattern = match result_to_owned(eval_single::<W, S>(re_expr, value.clone(), optional)) {
Ok(OwnedValue::String(s)) => s,
Ok(_) if optional => return QueryResult::None,
Ok(_) => return QueryResult::Error(EvalError::type_error("string", "pattern")),
Err(e) => return QueryResult::Error(e),
};
let input = match &value {
StandardJson::String(s) => match s.as_str() {
Ok(cow) => cow.into_owned(),
Err(_) if optional => return QueryResult::None,
Err(_) => return QueryResult::Error(EvalError::new("invalid string")),
},
_ if optional => return QueryResult::None,
_ => return QueryResult::Error(EvalError::type_error("string", type_name(&value))),
};
let re = match build_regex(&pattern, flags) {
Ok(r) => r,
Err(_e) if optional => return QueryResult::None,
Err(e) => return QueryResult::Error(e),
};
let parts: Vec<OwnedValue> = re
.split(&input)
.map(|s| OwnedValue::String(s.to_string()))
.collect();
if parts.is_empty() {
QueryResult::None
} else {
QueryResult::ManyOwned(parts)
}
}
#[cfg(feature = "regex")]
fn convert_jq_replacement(replacement: &str) -> String {
let mut result = String::new();
let mut chars = replacement.chars().peekable();
while let Some(c) = chars.next() {
if c == '\\' {
if chars.peek() == Some(&'(') {
chars.next(); let mut name = String::new();
while let Some(&nc) = chars.peek() {
if nc == ')' {
chars.next(); break;
}
name.push(nc);
chars.next();
}
if let Some(stripped) = name.strip_prefix('.') {
result.push_str("${");
result.push_str(stripped);
result.push('}');
} else {
result.push_str("\\(");
result.push_str(&name);
result.push(')');
}
} else {
result.push(c);
}
} else {
result.push(c);
}
}
result
}
fn eval_pipe<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
exprs: &[Expr],
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
if exprs.iter().any(needs_path_context) {
let owned = to_owned(&value);
return eval_pipe_with_path_context::<W, S>(exprs, &owned, &[], optional);
}
if exprs.is_empty() {
return QueryResult::One(value);
}
let (first, rest) = exprs.split_first().unwrap();
let result = eval_single::<W, S>(first, value, optional);
if rest.is_empty() {
return result;
}
match result.materialize_cursor() {
QueryResult::One(v) => eval_pipe::<W, S>(rest, v, optional),
QueryResult::OneCursor(_) => unreachable!(),
QueryResult::Many(values) => {
let mut all_results = Vec::new();
for v in values {
match eval_pipe::<W, S>(rest, v, optional).materialize_cursor() {
QueryResult::One(r) => all_results.push(r),
QueryResult::OneCursor(_) => unreachable!(),
QueryResult::Many(rs) => all_results.extend(rs),
QueryResult::None => {}
QueryResult::Error(e) => return QueryResult::Error(e),
QueryResult::Break(label) => return QueryResult::Break(label),
QueryResult::Owned(_) | QueryResult::ManyOwned(_) => {
}
}
}
QueryResult::Many(all_results)
}
QueryResult::None => QueryResult::None,
QueryResult::Error(e) => QueryResult::Error(e),
QueryResult::Break(label) => QueryResult::Break(label),
QueryResult::Owned(v) => {
match eval_owned_pipe::<Vec<u64>, S>(rest, v, optional) {
QueryResult::Owned(o) => QueryResult::Owned(o),
QueryResult::Error(e) => QueryResult::Error(e),
QueryResult::None => QueryResult::None,
QueryResult::ManyOwned(vs) => QueryResult::ManyOwned(vs),
QueryResult::Break(label) => QueryResult::Break(label),
_ => unreachable!("eval_owned_pipe only returns Owned variants"),
}
}
QueryResult::ManyOwned(vs) => {
let mut all_results: Vec<OwnedValue> = Vec::new();
for v in vs {
match eval_owned_pipe::<Vec<u64>, S>(rest, v, optional).materialize_cursor() {
QueryResult::Owned(r) => all_results.push(r),
QueryResult::OneCursor(_) => unreachable!(),
QueryResult::ManyOwned(rs) => all_results.extend(rs),
QueryResult::One(r) => all_results.push(to_owned(&r)),
QueryResult::Many(rs) => all_results.extend(rs.iter().map(to_owned)),
QueryResult::None => {}
QueryResult::Error(e) => return QueryResult::Error(e),
QueryResult::Break(label) => return QueryResult::Break(label),
}
}
if all_results.is_empty() {
QueryResult::None
} else if all_results.len() == 1 {
QueryResult::Owned(all_results.pop().unwrap())
} else {
QueryResult::ManyOwned(all_results)
}
}
}
}
fn eval_owned_pipe<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
exprs: &[Expr],
value: OwnedValue,
optional: bool,
) -> QueryResult<'a, W> {
if exprs.is_empty() {
return QueryResult::Owned(value);
}
let rest_expr = if exprs.len() == 1 {
exprs[0].clone()
} else {
Expr::Pipe(exprs.to_vec())
};
match eval_owned_expr::<S>(&rest_expr, &value, optional) {
Ok(v) => QueryResult::Owned(v),
Err(e) => QueryResult::Error(e),
}
}
fn find_field<'a, W: Clone + AsRef<[u64]>>(
fields: JsonFields<'a, W>,
name: &str,
) -> Option<StandardJson<'a, W>> {
fields.find(name)
}
fn get_element_at_index<'a, W: Clone + AsRef<[u64]>>(
elements: JsonElements<'a, W>,
idx: i64,
) -> Option<StandardJson<'a, W>> {
if idx >= 0 {
elements.get_fast(idx as usize)
} else {
let len = count_elements(elements);
let positive_idx = len as i64 + idx;
if positive_idx >= 0 {
elements.get_fast(positive_idx as usize)
} else {
None
}
}
}
fn count_elements<W: Clone + AsRef<[u64]>>(elements: JsonElements<'_, W>) -> usize {
elements.count()
}
fn slice_elements<'a, W: Clone + AsRef<[u64]>>(
elements: JsonElements<'a, W>,
start: Option<i64>,
end: Option<i64>,
) -> Vec<StandardJson<'a, W>> {
let all: Vec<_> = elements.collect();
let len = all.len();
let resolve_idx = |idx: i64, _default: usize| -> usize {
if idx >= 0 {
(idx as usize).min(len)
} else {
let pos = len as i64 + idx;
if pos < 0 {
0
} else {
pos as usize
}
}
};
let start_idx = start.map(|i| resolve_idx(i, 0)).unwrap_or(0);
let end_idx = end.map(|i| resolve_idx(i, len)).unwrap_or(len);
if start_idx >= end_idx || start_idx >= len {
return Vec::new();
}
all.into_iter()
.skip(start_idx)
.take(end_idx - start_idx)
.collect()
}
pub fn eval<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
expr: &Expr,
cursor: JsonCursor<'a, W>,
) -> QueryResult<'a, W> {
if matches!(expr, Expr::Identity) {
return QueryResult::OneCursor(cursor);
}
eval_single::<W, S>(expr, cursor.value(), false)
}
pub fn eval_lenient<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
expr: &Expr,
cursor: JsonCursor<'a, W>,
) -> Vec<StandardJson<'a, W>> {
match eval::<W, S>(expr, cursor) {
QueryResult::One(v) => vec![v],
QueryResult::OneCursor(c) => vec![c.value()],
QueryResult::Many(vs) => vs,
QueryResult::None => Vec::new(),
QueryResult::Error(_) => Vec::new(),
QueryResult::Owned(_) => Vec::new(), QueryResult::ManyOwned(_) => Vec::new(),
QueryResult::Break(_) => Vec::new(), }
}
fn eval_assign<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
path_expr: &Expr,
value_expr: &Expr,
input: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let new_value = match eval_single::<W, S>(value_expr, input.clone(), optional)
.materialize_cursor()
{
QueryResult::One(v) => to_owned(&v),
QueryResult::Owned(v) => v,
QueryResult::None => OwnedValue::Null,
QueryResult::Error(e) => return QueryResult::Error(e),
QueryResult::Many(vs) => {
if let Some(v) = vs.first() {
to_owned(v)
} else {
OwnedValue::Null
}
}
QueryResult::ManyOwned(vs) => {
if let Some(v) = vs.into_iter().next() {
v
} else {
OwnedValue::Null
}
}
QueryResult::OneCursor(_) => unreachable!("materialize_cursor should have converted this"),
QueryResult::Break(label) => return QueryResult::Break(label),
};
let mut result = to_owned(&input);
if let Err(e) = set_path(&mut result, path_expr, new_value) {
return QueryResult::Error(e);
}
QueryResult::Owned(result)
}
fn eval_update<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
path_expr: &Expr,
filter_expr: &Expr,
input: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let mut result = to_owned(&input);
if let Err(e) = update_path::<S>(&mut result, path_expr, filter_expr, optional) {
return QueryResult::Error(e);
}
QueryResult::Owned(result)
}
fn eval_compound_assign<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
op: AssignOp,
path_expr: &Expr,
value_expr: &Expr,
input: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let arith_op = match op {
AssignOp::Add => ArithOp::Add,
AssignOp::Sub => ArithOp::Sub,
AssignOp::Mul => ArithOp::Mul,
AssignOp::Div => ArithOp::Div,
AssignOp::Mod => ArithOp::Mod,
};
let filter = Expr::Arithmetic {
op: arith_op,
left: Box::new(Expr::Identity),
right: value_expr.clone().into(),
};
eval_update::<W, S>(path_expr, &filter, input, optional)
}
fn eval_alternative_assign<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
path_expr: &Expr,
value_expr: &Expr,
input: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let filter = Expr::Alternative(Box::new(Expr::Identity), Box::new(value_expr.clone()));
eval_update::<W, S>(path_expr, &filter, input, optional)
}
fn set_path(
root: &mut OwnedValue,
path_expr: &Expr,
new_value: OwnedValue,
) -> Result<(), EvalError> {
match path_expr {
Expr::Identity => {
*root = new_value;
Ok(())
}
Expr::Field(name) => {
if let OwnedValue::Object(map) = root {
map.insert(name.clone(), new_value);
Ok(())
} else {
Err(EvalError::type_error("object", owned_type_name(root)))
}
}
Expr::Index(idx) => {
if let OwnedValue::Array(arr) = root {
let len = arr.len() as i64;
let actual_idx = if *idx < 0 { len + idx } else { *idx };
if actual_idx >= 0 && (actual_idx as usize) < arr.len() {
arr[actual_idx as usize] = new_value;
Ok(())
} else {
Err(EvalError::index_out_of_bounds(*idx, arr.len()))
}
} else {
Err(EvalError::type_error("array", owned_type_name(root)))
}
}
Expr::Pipe(exprs) if !exprs.is_empty() => {
if exprs.len() == 1 {
set_path(root, &exprs[0], new_value)
} else {
let parent_path = &exprs[..exprs.len() - 1];
let last_path = &exprs[exprs.len() - 1];
let parent = get_path_mut(root, parent_path)?;
set_path(parent, last_path, new_value)
}
}
Expr::Optional(inner) => {
match set_path(root, inner, new_value) {
Ok(()) => Ok(()),
Err(_) => Ok(()), }
}
_ => Err(EvalError::new(
"cannot use expression as assignment target".to_string(),
)),
}
}
fn get_path_mut<'a>(
root: &'a mut OwnedValue,
path_parts: &[Expr],
) -> Result<&'a mut OwnedValue, EvalError> {
let mut current = root;
for part in path_parts {
current = match part {
Expr::Identity => current,
Expr::Field(name) => {
if let OwnedValue::Object(map) = current {
map.entry(name.clone()).or_insert(OwnedValue::Null)
} else {
return Err(EvalError::type_error("object", owned_type_name(current)));
}
}
Expr::Index(idx) => {
if let OwnedValue::Array(arr) = current {
let len = arr.len() as i64;
let actual_idx = if *idx < 0 { len + idx } else { *idx };
if actual_idx >= 0 && (actual_idx as usize) < arr.len() {
&mut arr[actual_idx as usize]
} else {
return Err(EvalError::index_out_of_bounds(*idx, arr.len()));
}
} else {
return Err(EvalError::type_error("array", owned_type_name(current)));
}
}
_ => return Err(EvalError::new("invalid path component")),
};
}
Ok(current)
}
fn update_path<S: EvalSemantics>(
root: &mut OwnedValue,
path_expr: &Expr,
filter_expr: &Expr,
optional: bool,
) -> Result<(), EvalError> {
match path_expr {
Expr::Identity => {
match eval_owned_expr::<S>(filter_expr, root, optional) {
Ok(v) => {
*root = v;
Ok(())
}
Err(_e) if optional => Ok(()),
Err(e) => Err(e),
}
}
Expr::Field(name) => {
if let OwnedValue::Object(map) = root {
let current = map.entry(name.clone()).or_insert(OwnedValue::Null);
update_path::<S>(current, &Expr::Identity, filter_expr, optional)
} else if optional {
Ok(())
} else {
Err(EvalError::type_error("object", owned_type_name(root)))
}
}
Expr::Index(idx) => {
if let OwnedValue::Array(arr) = root {
let len = arr.len() as i64;
let actual_idx = if *idx < 0 { len + idx } else { *idx };
if actual_idx >= 0 && (actual_idx as usize) < arr.len() {
update_path::<S>(
&mut arr[actual_idx as usize],
&Expr::Identity,
filter_expr,
optional,
)
} else if optional {
Ok(())
} else {
Err(EvalError::index_out_of_bounds(*idx, arr.len()))
}
} else if optional {
Ok(())
} else {
Err(EvalError::type_error("array", owned_type_name(root)))
}
}
Expr::Iterate => {
match root {
OwnedValue::Array(arr) => {
for elem in arr.iter_mut() {
update_path::<S>(elem, &Expr::Identity, filter_expr, optional)?;
}
Ok(())
}
OwnedValue::Object(map) => {
for value in map.values_mut() {
update_path::<S>(value, &Expr::Identity, filter_expr, optional)?;
}
Ok(())
}
_ if optional => Ok(()),
_ => Err(EvalError::type_error(
"array or object",
owned_type_name(root),
)),
}
}
Expr::Pipe(exprs) if !exprs.is_empty() => {
if exprs.len() == 1 {
update_path::<S>(root, &exprs[0], filter_expr, optional)
} else {
let first = &exprs[0];
let rest = Expr::Pipe(exprs[1..].to_vec());
match first {
Expr::Field(name) => {
if let OwnedValue::Object(map) = root {
let current = map.entry(name.clone()).or_insert(OwnedValue::Null);
update_path::<S>(current, &rest, filter_expr, optional)
} else if optional {
Ok(())
} else {
Err(EvalError::type_error("object", owned_type_name(root)))
}
}
Expr::Index(idx) => {
if let OwnedValue::Array(arr) = root {
let len = arr.len() as i64;
let actual_idx = if *idx < 0 { len + idx } else { *idx };
if actual_idx >= 0 && (actual_idx as usize) < arr.len() {
update_path::<S>(
&mut arr[actual_idx as usize],
&rest,
filter_expr,
optional,
)
} else if optional {
Ok(())
} else {
Err(EvalError::index_out_of_bounds(*idx, arr.len()))
}
} else if optional {
Ok(())
} else {
Err(EvalError::type_error("array", owned_type_name(root)))
}
}
Expr::Iterate => match root {
OwnedValue::Array(arr) => {
for elem in arr.iter_mut() {
update_path::<S>(elem, &rest, filter_expr, optional)?;
}
Ok(())
}
OwnedValue::Object(map) => {
for value in map.values_mut() {
update_path::<S>(value, &rest, filter_expr, optional)?;
}
Ok(())
}
_ if optional => Ok(()),
_ => Err(EvalError::type_error(
"array or object",
owned_type_name(root),
)),
},
_ => update_path::<S>(root, first, filter_expr, optional),
}
}
}
Expr::Optional(inner) => update_path::<S>(root, inner, filter_expr, true),
_ => Err(EvalError::new("cannot use expression as update target")),
}
}
fn owned_type_name(value: &OwnedValue) -> &'static str {
match value {
OwnedValue::Null => "null",
OwnedValue::Bool(_) => "boolean",
OwnedValue::Int(_) | OwnedValue::Float(_) => "number",
OwnedValue::String(_) => "string",
OwnedValue::Array(_) => "array",
OwnedValue::Object(_) => "object",
}
}
pub fn substitute_vars<'a, I>(expr: &Expr, vars: I) -> Expr
where
I: IntoIterator<Item = (&'a str, &'a OwnedValue)>,
{
let mut result = expr.clone();
for (name, value) in vars {
result = substitute_var(&result, name, value);
}
result
}
fn substitute_var(expr: &Expr, var_name: &str, replacement: &OwnedValue) -> Expr {
match expr {
Expr::Var(name) if name == var_name => owned_to_expr(replacement),
Expr::Var(_) => expr.clone(),
Expr::Loc { line } => Expr::Loc { line: *line },
Expr::Env => Expr::Env,
Expr::Identity => Expr::Identity,
Expr::Field(name) => Expr::Field(name.clone()),
Expr::Index(i) => Expr::Index(*i),
Expr::Slice { start, end } => Expr::Slice {
start: *start,
end: *end,
},
Expr::Iterate => Expr::Iterate,
Expr::RecursiveDescent => Expr::RecursiveDescent,
Expr::Optional(e) => Expr::Optional(Box::new(substitute_var(e, var_name, replacement))),
Expr::Pipe(exprs) => Expr::Pipe(
exprs
.iter()
.map(|e| substitute_var(e, var_name, replacement))
.collect(),
),
Expr::Comma(exprs) => Expr::Comma(
exprs
.iter()
.map(|e| substitute_var(e, var_name, replacement))
.collect(),
),
Expr::Array(e) => Expr::Array(Box::new(substitute_var(e, var_name, replacement))),
Expr::Object(entries) => Expr::Object(
entries
.iter()
.map(|entry| {
let new_key = match &entry.key {
ObjectKey::Literal(s) => ObjectKey::Literal(s.clone()),
ObjectKey::Expr(e) => {
ObjectKey::Expr(Box::new(substitute_var(e, var_name, replacement)))
}
};
ObjectEntry {
key: new_key,
value: substitute_var(&entry.value, var_name, replacement),
}
})
.collect(),
),
Expr::Literal(lit) => Expr::Literal(lit.clone()),
Expr::Paren(e) => Expr::Paren(Box::new(substitute_var(e, var_name, replacement))),
Expr::Arithmetic { op, left, right } => Expr::Arithmetic {
op: *op,
left: Box::new(substitute_var(left, var_name, replacement)),
right: Box::new(substitute_var(right, var_name, replacement)),
},
Expr::Compare { op, left, right } => Expr::Compare {
op: *op,
left: Box::new(substitute_var(left, var_name, replacement)),
right: Box::new(substitute_var(right, var_name, replacement)),
},
Expr::And(l, r) => Expr::And(
Box::new(substitute_var(l, var_name, replacement)),
Box::new(substitute_var(r, var_name, replacement)),
),
Expr::Or(l, r) => Expr::Or(
Box::new(substitute_var(l, var_name, replacement)),
Box::new(substitute_var(r, var_name, replacement)),
),
Expr::Not => Expr::Not,
Expr::Alternative(l, r) => Expr::Alternative(
Box::new(substitute_var(l, var_name, replacement)),
Box::new(substitute_var(r, var_name, replacement)),
),
Expr::If {
cond,
then_branch,
else_branch,
} => Expr::If {
cond: Box::new(substitute_var(cond, var_name, replacement)),
then_branch: Box::new(substitute_var(then_branch, var_name, replacement)),
else_branch: Box::new(substitute_var(else_branch, var_name, replacement)),
},
Expr::Try { expr, catch } => Expr::Try {
expr: Box::new(substitute_var(expr, var_name, replacement)),
catch: catch
.as_ref()
.map(|e| Box::new(substitute_var(e, var_name, replacement))),
},
Expr::Error(msg) => Expr::Error(msg.clone()),
Expr::Builtin(b) => Expr::Builtin(substitute_var_in_builtin(b, var_name, replacement)),
Expr::StringInterpolation(parts) => Expr::StringInterpolation(
parts
.iter()
.map(|p| match p {
StringPart::Literal(s) => StringPart::Literal(s.clone()),
StringPart::Expr(e) => {
StringPart::Expr(Box::new(substitute_var(e, var_name, replacement)))
}
})
.collect(),
),
Expr::Format(f) => Expr::Format(f.clone()),
Expr::As { expr, var, body } => {
if var == var_name {
Expr::As {
expr: Box::new(substitute_var(expr, var_name, replacement)),
var: var.clone(),
body: body.clone(), }
} else {
Expr::As {
expr: Box::new(substitute_var(expr, var_name, replacement)),
var: var.clone(),
body: Box::new(substitute_var(body, var_name, replacement)),
}
}
}
Expr::Reduce {
input,
var,
init,
update,
} => {
if var == var_name {
Expr::Reduce {
input: Box::new(substitute_var(input, var_name, replacement)),
var: var.clone(),
init: Box::new(substitute_var(init, var_name, replacement)),
update: update.clone(), }
} else {
Expr::Reduce {
input: Box::new(substitute_var(input, var_name, replacement)),
var: var.clone(),
init: Box::new(substitute_var(init, var_name, replacement)),
update: Box::new(substitute_var(update, var_name, replacement)),
}
}
}
Expr::Foreach {
input,
var,
init,
update,
extract,
} => {
if var == var_name {
Expr::Foreach {
input: Box::new(substitute_var(input, var_name, replacement)),
var: var.clone(),
init: Box::new(substitute_var(init, var_name, replacement)),
update: update.clone(),
extract: extract.clone(),
}
} else {
Expr::Foreach {
input: Box::new(substitute_var(input, var_name, replacement)),
var: var.clone(),
init: Box::new(substitute_var(init, var_name, replacement)),
update: Box::new(substitute_var(update, var_name, replacement)),
extract: extract
.as_ref()
.map(|e| Box::new(substitute_var(e, var_name, replacement))),
}
}
}
Expr::Limit { n, expr } => Expr::Limit {
n: Box::new(substitute_var(n, var_name, replacement)),
expr: Box::new(substitute_var(expr, var_name, replacement)),
},
Expr::FirstExpr(e) => Expr::FirstExpr(Box::new(substitute_var(e, var_name, replacement))),
Expr::LastExpr(e) => Expr::LastExpr(Box::new(substitute_var(e, var_name, replacement))),
Expr::NthExpr { n, expr } => Expr::NthExpr {
n: Box::new(substitute_var(n, var_name, replacement)),
expr: Box::new(substitute_var(expr, var_name, replacement)),
},
Expr::Until { cond, update } => Expr::Until {
cond: Box::new(substitute_var(cond, var_name, replacement)),
update: Box::new(substitute_var(update, var_name, replacement)),
},
Expr::While { cond, update } => Expr::While {
cond: Box::new(substitute_var(cond, var_name, replacement)),
update: Box::new(substitute_var(update, var_name, replacement)),
},
Expr::Repeat(e) => Expr::Repeat(Box::new(substitute_var(e, var_name, replacement))),
Expr::Range { from, to, step } => Expr::Range {
from: Box::new(substitute_var(from, var_name, replacement)),
to: to
.as_ref()
.map(|e| Box::new(substitute_var(e, var_name, replacement))),
step: step
.as_ref()
.map(|e| Box::new(substitute_var(e, var_name, replacement))),
},
Expr::AsPattern {
expr,
pattern,
body,
} => {
let shadowed = pattern_binds_var(pattern, var_name);
Expr::AsPattern {
expr: Box::new(substitute_var(expr, var_name, replacement)),
pattern: pattern.clone(),
body: if shadowed {
body.clone()
} else {
Box::new(substitute_var(body, var_name, replacement))
},
}
}
Expr::FuncDef {
name,
params,
body,
then,
} => {
let shadowed = params.contains(&var_name.to_string());
Expr::FuncDef {
name: name.clone(),
params: params.clone(),
body: if shadowed {
body.clone()
} else {
Box::new(substitute_var(body, var_name, replacement))
},
then: Box::new(substitute_var(then, var_name, replacement)),
}
}
Expr::FuncCall { name, args } => Expr::FuncCall {
name: name.clone(),
args: args
.iter()
.map(|a| substitute_var(a, var_name, replacement))
.collect(),
},
Expr::NamespacedCall {
namespace,
name,
args,
} => Expr::NamespacedCall {
namespace: namespace.clone(),
name: name.clone(),
args: args
.iter()
.map(|a| substitute_var(a, var_name, replacement))
.collect(),
},
Expr::Assign { path, value } => Expr::Assign {
path: Box::new(substitute_var(path, var_name, replacement)),
value: Box::new(substitute_var(value, var_name, replacement)),
},
Expr::Update { path, filter } => Expr::Update {
path: Box::new(substitute_var(path, var_name, replacement)),
filter: Box::new(substitute_var(filter, var_name, replacement)),
},
Expr::CompoundAssign { op, path, value } => Expr::CompoundAssign {
op: *op,
path: Box::new(substitute_var(path, var_name, replacement)),
value: Box::new(substitute_var(value, var_name, replacement)),
},
Expr::AlternativeAssign { path, value } => Expr::AlternativeAssign {
path: Box::new(substitute_var(path, var_name, replacement)),
value: Box::new(substitute_var(value, var_name, replacement)),
},
Expr::Label { name, body } => {
if name == var_name {
expr.clone()
} else {
Expr::Label {
name: name.clone(),
body: Box::new(substitute_var(body, var_name, replacement)),
}
}
}
Expr::Break(name) => Expr::Break(name.clone()),
}
}
fn pattern_binds_var(pattern: &Pattern, var_name: &str) -> bool {
match pattern {
Pattern::Var(name) => name == var_name,
Pattern::Object(entries) => entries
.iter()
.any(|e| pattern_binds_var(&e.pattern, var_name)),
Pattern::Array(patterns) => patterns.iter().any(|p| pattern_binds_var(p, var_name)),
}
}
fn substitute_var_in_builtin(
builtin: &Builtin,
var_name: &str,
replacement: &OwnedValue,
) -> Builtin {
match builtin {
Builtin::Type => Builtin::Type,
Builtin::IsNull => Builtin::IsNull,
Builtin::IsBoolean => Builtin::IsBoolean,
Builtin::IsNumber => Builtin::IsNumber,
Builtin::IsString => Builtin::IsString,
Builtin::IsArray => Builtin::IsArray,
Builtin::IsObject => Builtin::IsObject,
Builtin::Values => Builtin::Values,
Builtin::Nulls => Builtin::Nulls,
Builtin::Booleans => Builtin::Booleans,
Builtin::Numbers => Builtin::Numbers,
Builtin::Strings => Builtin::Strings,
Builtin::Arrays => Builtin::Arrays,
Builtin::Objects => Builtin::Objects,
Builtin::Iterables => Builtin::Iterables,
Builtin::Scalars => Builtin::Scalars,
Builtin::Length => Builtin::Length,
Builtin::Utf8ByteLength => Builtin::Utf8ByteLength,
Builtin::Keys => Builtin::Keys,
Builtin::KeysUnsorted => Builtin::KeysUnsorted,
Builtin::Has(e) => Builtin::Has(Box::new(substitute_var(e, var_name, replacement))),
Builtin::In(e) => Builtin::In(Box::new(substitute_var(e, var_name, replacement))),
Builtin::Select(e) => Builtin::Select(Box::new(substitute_var(e, var_name, replacement))),
Builtin::Empty => Builtin::Empty,
Builtin::Map(e) => Builtin::Map(Box::new(substitute_var(e, var_name, replacement))),
Builtin::MapValues(e) => {
Builtin::MapValues(Box::new(substitute_var(e, var_name, replacement)))
}
Builtin::Add => Builtin::Add,
Builtin::Any => Builtin::Any,
Builtin::All => Builtin::All,
Builtin::Min => Builtin::Min,
Builtin::Max => Builtin::Max,
Builtin::MinBy(e) => Builtin::MinBy(Box::new(substitute_var(e, var_name, replacement))),
Builtin::MaxBy(e) => Builtin::MaxBy(Box::new(substitute_var(e, var_name, replacement))),
Builtin::AsciiDowncase => Builtin::AsciiDowncase,
Builtin::AsciiUpcase => Builtin::AsciiUpcase,
Builtin::Ltrimstr(e) => {
Builtin::Ltrimstr(Box::new(substitute_var(e, var_name, replacement)))
}
Builtin::Rtrimstr(e) => {
Builtin::Rtrimstr(Box::new(substitute_var(e, var_name, replacement)))
}
Builtin::Startswith(e) => {
Builtin::Startswith(Box::new(substitute_var(e, var_name, replacement)))
}
Builtin::Endswith(e) => {
Builtin::Endswith(Box::new(substitute_var(e, var_name, replacement)))
}
Builtin::Split(e) => Builtin::Split(Box::new(substitute_var(e, var_name, replacement))),
Builtin::Join(e) => Builtin::Join(Box::new(substitute_var(e, var_name, replacement))),
Builtin::Contains(e) => {
Builtin::Contains(Box::new(substitute_var(e, var_name, replacement)))
}
Builtin::Inside(e) => Builtin::Inside(Box::new(substitute_var(e, var_name, replacement))),
Builtin::First => Builtin::First,
Builtin::Last => Builtin::Last,
Builtin::Nth(e) => Builtin::Nth(Box::new(substitute_var(e, var_name, replacement))),
Builtin::Reverse => Builtin::Reverse,
Builtin::Flatten => Builtin::Flatten,
Builtin::FlattenDepth(e) => {
Builtin::FlattenDepth(Box::new(substitute_var(e, var_name, replacement)))
}
Builtin::GroupBy(e) => Builtin::GroupBy(Box::new(substitute_var(e, var_name, replacement))),
Builtin::Unique => Builtin::Unique,
Builtin::UniqueBy(e) => {
Builtin::UniqueBy(Box::new(substitute_var(e, var_name, replacement)))
}
Builtin::Sort => Builtin::Sort,
Builtin::SortBy(e) => Builtin::SortBy(Box::new(substitute_var(e, var_name, replacement))),
Builtin::ToEntries => Builtin::ToEntries,
Builtin::FromEntries => Builtin::FromEntries,
Builtin::WithEntries(e) => {
Builtin::WithEntries(Box::new(substitute_var(e, var_name, replacement)))
}
Builtin::ToString => Builtin::ToString,
Builtin::ToNumber => Builtin::ToNumber,
Builtin::ToJson => Builtin::ToJson,
Builtin::FromJson => Builtin::FromJson,
Builtin::Explode => Builtin::Explode,
Builtin::Implode => Builtin::Implode,
Builtin::Test(e) => Builtin::Test(Box::new(substitute_var(e, var_name, replacement))),
Builtin::Indices(e) => Builtin::Indices(Box::new(substitute_var(e, var_name, replacement))),
Builtin::Index(e) => Builtin::Index(Box::new(substitute_var(e, var_name, replacement))),
Builtin::Rindex(e) => Builtin::Rindex(Box::new(substitute_var(e, var_name, replacement))),
Builtin::ToJsonStream => Builtin::ToJsonStream,
Builtin::FromJsonStream => Builtin::FromJsonStream,
Builtin::GetPath(e) => Builtin::GetPath(Box::new(substitute_var(e, var_name, replacement))),
Builtin::TestFlags(re, flags) => Builtin::TestFlags(
Box::new(substitute_var(re, var_name, replacement)),
Box::new(substitute_var(flags, var_name, replacement)),
),
Builtin::Match(re) => Builtin::Match(Box::new(substitute_var(re, var_name, replacement))),
Builtin::MatchFlags(re, flags) => Builtin::MatchFlags(
Box::new(substitute_var(re, var_name, replacement)),
Box::new(substitute_var(flags, var_name, replacement)),
),
Builtin::Capture(re) => {
Builtin::Capture(Box::new(substitute_var(re, var_name, replacement)))
}
Builtin::CaptureFlags(re, flags) => Builtin::CaptureFlags(
Box::new(substitute_var(re, var_name, replacement)),
Box::new(substitute_var(flags, var_name, replacement)),
),
Builtin::Sub(re, repl) => Builtin::Sub(
Box::new(substitute_var(re, var_name, replacement)),
Box::new(substitute_var(repl, var_name, replacement)),
),
Builtin::SubFlags(re, repl, flags) => Builtin::SubFlags(
Box::new(substitute_var(re, var_name, replacement)),
Box::new(substitute_var(repl, var_name, replacement)),
Box::new(substitute_var(flags, var_name, replacement)),
),
Builtin::Gsub(re, repl) => Builtin::Gsub(
Box::new(substitute_var(re, var_name, replacement)),
Box::new(substitute_var(repl, var_name, replacement)),
),
Builtin::GsubFlags(re, repl, flags) => Builtin::GsubFlags(
Box::new(substitute_var(re, var_name, replacement)),
Box::new(substitute_var(repl, var_name, replacement)),
Box::new(substitute_var(flags, var_name, replacement)),
),
Builtin::Scan(re) => Builtin::Scan(Box::new(substitute_var(re, var_name, replacement))),
Builtin::ScanFlags(re, flags) => Builtin::ScanFlags(
Box::new(substitute_var(re, var_name, replacement)),
Box::new(substitute_var(flags, var_name, replacement)),
),
Builtin::SplitRegex(re, flags) => Builtin::SplitRegex(
Box::new(substitute_var(re, var_name, replacement)),
Box::new(substitute_var(flags, var_name, replacement)),
),
Builtin::Splits(re) => Builtin::Splits(Box::new(substitute_var(re, var_name, replacement))),
Builtin::SplitsFlags(re, flags) => Builtin::SplitsFlags(
Box::new(substitute_var(re, var_name, replacement)),
Box::new(substitute_var(flags, var_name, replacement)),
),
Builtin::Recurse => Builtin::Recurse,
Builtin::RecurseF(f) => {
Builtin::RecurseF(Box::new(substitute_var(f, var_name, replacement)))
}
Builtin::RecurseCond(f, c) => Builtin::RecurseCond(
Box::new(substitute_var(f, var_name, replacement)),
Box::new(substitute_var(c, var_name, replacement)),
),
Builtin::Walk(f) => Builtin::Walk(Box::new(substitute_var(f, var_name, replacement))),
Builtin::IsValid(e) => Builtin::IsValid(Box::new(substitute_var(e, var_name, replacement))),
Builtin::Path(e) => Builtin::Path(Box::new(substitute_var(e, var_name, replacement))),
Builtin::PathNoArg => Builtin::PathNoArg,
Builtin::Parent => Builtin::Parent,
Builtin::ParentN(e) => Builtin::ParentN(Box::new(substitute_var(e, var_name, replacement))),
Builtin::Paths => Builtin::Paths,
Builtin::PathsFilter(e) => {
Builtin::PathsFilter(Box::new(substitute_var(e, var_name, replacement)))
}
Builtin::LeafPaths => Builtin::LeafPaths,
Builtin::SetPath(p, v) => Builtin::SetPath(
Box::new(substitute_var(p, var_name, replacement)),
Box::new(substitute_var(v, var_name, replacement)),
),
Builtin::DelPaths(e) => {
Builtin::DelPaths(Box::new(substitute_var(e, var_name, replacement)))
}
Builtin::Floor => Builtin::Floor,
Builtin::Ceil => Builtin::Ceil,
Builtin::Round => Builtin::Round,
Builtin::Sqrt => Builtin::Sqrt,
Builtin::Fabs => Builtin::Fabs,
Builtin::Log => Builtin::Log,
Builtin::Log10 => Builtin::Log10,
Builtin::Log2 => Builtin::Log2,
Builtin::Exp => Builtin::Exp,
Builtin::Exp10 => Builtin::Exp10,
Builtin::Exp2 => Builtin::Exp2,
Builtin::Pow(x, y) => Builtin::Pow(
Box::new(substitute_var(x, var_name, replacement)),
Box::new(substitute_var(y, var_name, replacement)),
),
Builtin::Sin => Builtin::Sin,
Builtin::Cos => Builtin::Cos,
Builtin::Tan => Builtin::Tan,
Builtin::Asin => Builtin::Asin,
Builtin::Acos => Builtin::Acos,
Builtin::Atan => Builtin::Atan,
Builtin::Atan2(x, y) => Builtin::Atan2(
Box::new(substitute_var(x, var_name, replacement)),
Box::new(substitute_var(y, var_name, replacement)),
),
Builtin::Sinh => Builtin::Sinh,
Builtin::Cosh => Builtin::Cosh,
Builtin::Tanh => Builtin::Tanh,
Builtin::Asinh => Builtin::Asinh,
Builtin::Acosh => Builtin::Acosh,
Builtin::Atanh => Builtin::Atanh,
Builtin::Infinite => Builtin::Infinite,
Builtin::Nan => Builtin::Nan,
Builtin::IsInfinite => Builtin::IsInfinite,
Builtin::IsNan => Builtin::IsNan,
Builtin::IsNormal => Builtin::IsNormal,
Builtin::IsFinite => Builtin::IsFinite,
Builtin::Debug => Builtin::Debug,
Builtin::DebugMsg(e) => {
Builtin::DebugMsg(Box::new(substitute_var(e, var_name, replacement)))
}
Builtin::Env => Builtin::Env,
Builtin::EnvVar(e) => Builtin::EnvVar(Box::new(substitute_var(e, var_name, replacement))),
Builtin::EnvObject(name) => Builtin::EnvObject(name.clone()),
Builtin::StrEnv(name) => Builtin::StrEnv(name.clone()),
Builtin::NullLit => Builtin::NullLit,
Builtin::Trim => Builtin::Trim,
Builtin::Ltrim => Builtin::Ltrim,
Builtin::Rtrim => Builtin::Rtrim,
Builtin::Transpose => Builtin::Transpose,
Builtin::BSearch(e) => Builtin::BSearch(Box::new(substitute_var(e, var_name, replacement))),
Builtin::ModuleMeta(e) => {
Builtin::ModuleMeta(Box::new(substitute_var(e, var_name, replacement)))
}
Builtin::Pick(e) => Builtin::Pick(Box::new(substitute_var(e, var_name, replacement))),
Builtin::Omit(e) => Builtin::Omit(Box::new(substitute_var(e, var_name, replacement))),
Builtin::Tag => Builtin::Tag,
Builtin::Anchor => Builtin::Anchor,
Builtin::Style => Builtin::Style,
Builtin::Kind => Builtin::Kind,
Builtin::Key => Builtin::Key,
Builtin::Line => Builtin::Line,
Builtin::Column => Builtin::Column,
Builtin::DocumentIndex => Builtin::DocumentIndex,
Builtin::Shuffle => Builtin::Shuffle,
Builtin::Pivot => Builtin::Pivot,
Builtin::SplitDoc => Builtin::SplitDoc,
Builtin::Del(e) => Builtin::Del(Box::new(substitute_var(e, var_name, replacement))),
Builtin::Now => Builtin::Now,
Builtin::Abs => Builtin::Abs,
Builtin::Builtins => Builtin::Builtins,
Builtin::Normals => Builtin::Normals,
Builtin::Finites => Builtin::Finites,
Builtin::Limit(n, e) => Builtin::Limit(
Box::new(substitute_var(n, var_name, replacement)),
Box::new(substitute_var(e, var_name, replacement)),
),
Builtin::FirstStream(e) => {
Builtin::FirstStream(Box::new(substitute_var(e, var_name, replacement)))
}
Builtin::LastStream(e) => {
Builtin::LastStream(Box::new(substitute_var(e, var_name, replacement)))
}
Builtin::NthStream(n, e) => Builtin::NthStream(
Box::new(substitute_var(n, var_name, replacement)),
Box::new(substitute_var(e, var_name, replacement)),
),
Builtin::Range(n) => Builtin::Range(Box::new(substitute_var(n, var_name, replacement))),
Builtin::RangeFromTo(from, to) => Builtin::RangeFromTo(
Box::new(substitute_var(from, var_name, replacement)),
Box::new(substitute_var(to, var_name, replacement)),
),
Builtin::RangeFromToBy(from, to, by) => Builtin::RangeFromToBy(
Box::new(substitute_var(from, var_name, replacement)),
Box::new(substitute_var(to, var_name, replacement)),
Box::new(substitute_var(by, var_name, replacement)),
),
Builtin::IsEmpty(e) => Builtin::IsEmpty(Box::new(substitute_var(e, var_name, replacement))),
Builtin::RecurseDown => Builtin::RecurseDown,
Builtin::Gmtime => Builtin::Gmtime,
Builtin::Localtime => Builtin::Localtime,
Builtin::Mktime => Builtin::Mktime,
Builtin::Strftime(e) => {
Builtin::Strftime(Box::new(substitute_var(e, var_name, replacement)))
}
Builtin::Strptime(e) => {
Builtin::Strptime(Box::new(substitute_var(e, var_name, replacement)))
}
Builtin::Todate => Builtin::Todate,
Builtin::Fromdate => Builtin::Fromdate,
Builtin::Todateiso8601 => Builtin::Todateiso8601,
Builtin::Fromdateiso8601 => Builtin::Fromdateiso8601,
Builtin::Combinations => Builtin::Combinations,
Builtin::CombinationsN(e) => {
Builtin::CombinationsN(Box::new(substitute_var(e, var_name, replacement)))
}
Builtin::Trunc => Builtin::Trunc,
Builtin::ToBoolean => Builtin::ToBoolean,
Builtin::Skip(n, e) => Builtin::Skip(
Box::new(substitute_var(n, var_name, replacement)),
Box::new(substitute_var(e, var_name, replacement)),
),
Builtin::FromUnix => Builtin::FromUnix,
Builtin::ToUnix => Builtin::ToUnix,
Builtin::Tz(e) => Builtin::Tz(Box::new(substitute_var(e, var_name, replacement))),
Builtin::Load(e) => Builtin::Load(Box::new(substitute_var(e, var_name, replacement))),
Builtin::AtOffset(e) => {
Builtin::AtOffset(Box::new(substitute_var(e, var_name, replacement)))
}
Builtin::AtPosition(line, col) => Builtin::AtPosition(
Box::new(substitute_var(line, var_name, replacement)),
Box::new(substitute_var(col, var_name, replacement)),
),
}
}
fn owned_to_expr(value: &OwnedValue) -> Expr {
match value {
OwnedValue::Null => Expr::Literal(Literal::Null),
OwnedValue::Bool(b) => Expr::Literal(Literal::Bool(*b)),
OwnedValue::Int(i) => Expr::Literal(Literal::Int(*i)),
OwnedValue::Float(f) => Expr::Literal(Literal::Float(*f)),
OwnedValue::String(s) => Expr::Literal(Literal::String(s.clone())),
OwnedValue::Array(arr) => {
if arr.is_empty() {
Expr::Array(Box::new(Expr::Builtin(Builtin::Empty)))
} else {
let elements: Vec<Expr> = arr.iter().map(owned_to_expr).collect();
Expr::Array(Box::new(Expr::Comma(elements)))
}
}
OwnedValue::Object(obj) => {
let entries: Vec<ObjectEntry> = obj
.iter()
.map(|(k, v)| ObjectEntry {
key: ObjectKey::Literal(k.clone()),
value: owned_to_expr(v),
})
.collect();
Expr::Object(entries)
}
}
}
fn eval_as<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
expr: &Expr,
var: &str,
body: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let bound_result = eval_single::<W, S>(expr, value.clone(), optional);
let bound_values: Vec<OwnedValue> = match bound_result.materialize_cursor() {
QueryResult::One(v) => vec![to_owned(&v)],
QueryResult::OneCursor(_) => unreachable!(),
QueryResult::Many(vs) => vs.iter().map(to_owned).collect(),
QueryResult::Owned(v) => vec![v],
QueryResult::ManyOwned(vs) => vs,
QueryResult::None => return QueryResult::None,
QueryResult::Error(e) => return QueryResult::Error(e),
QueryResult::Break(label) => return QueryResult::Break(label),
};
let mut all_results: Vec<OwnedValue> = Vec::new();
for bound_val in bound_values {
let substituted_body = substitute_var(body, var, &bound_val);
match eval_single::<W, S>(&substituted_body, value.clone(), optional).materialize_cursor() {
QueryResult::One(v) => all_results.push(to_owned(&v)),
QueryResult::OneCursor(_) => unreachable!(),
QueryResult::Many(vs) => all_results.extend(vs.iter().map(to_owned)),
QueryResult::Owned(v) => all_results.push(v),
QueryResult::ManyOwned(vs) => all_results.extend(vs),
QueryResult::None => {}
QueryResult::Error(e) => return QueryResult::Error(e),
QueryResult::Break(label) => return QueryResult::Break(label),
}
}
if all_results.is_empty() {
QueryResult::None
} else if all_results.len() == 1 {
QueryResult::Owned(all_results.pop().unwrap())
} else {
QueryResult::ManyOwned(all_results)
}
}
fn eval_reduce<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
input: &Expr,
var: &str,
init: &Expr,
update: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let input_result = eval_single::<W, S>(input, value.clone(), optional);
let input_values: Vec<OwnedValue> = match input_result.materialize_cursor() {
QueryResult::One(v) => vec![to_owned(&v)],
QueryResult::OneCursor(_) => unreachable!(),
QueryResult::Many(vs) => vs.iter().map(to_owned).collect(),
QueryResult::Owned(v) => vec![v],
QueryResult::ManyOwned(vs) => vs,
QueryResult::None => Vec::new(),
QueryResult::Error(e) => return QueryResult::Error(e),
QueryResult::Break(label) => return QueryResult::Break(label),
};
let init_result = eval_single::<W, S>(init, value.clone(), optional);
let mut acc = match result_to_owned(init_result) {
Ok(v) => v,
Err(e) => return QueryResult::Error(e),
};
for input_val in input_values {
let substituted = substitute_var(update, var, &input_val);
let acc_result = eval_owned_expr::<S>(&substituted, &acc, optional);
match acc_result {
Ok(new_acc) => acc = new_acc,
Err(e) => return QueryResult::Error(e),
}
}
QueryResult::Owned(acc)
}
fn eval_owned_expr<S: EvalSemantics>(
expr: &Expr,
input: &OwnedValue,
optional: bool,
) -> Result<OwnedValue, EvalError> {
let json_str = owned_value_to_json_string(input);
let json_bytes = json_str.as_bytes();
use crate::json::JsonIndex;
let index = JsonIndex::build(json_bytes);
let cursor = index.root(json_bytes);
match eval_single::<Vec<u64>, S>(expr, cursor.value(), optional).materialize_cursor() {
QueryResult::One(v) => Ok(to_owned(&v)),
QueryResult::OneCursor(_) => unreachable!(),
QueryResult::Owned(v) => Ok(v),
QueryResult::Many(vs) => {
if vs.len() == 1 {
Ok(to_owned(&vs[0]))
} else {
Ok(OwnedValue::Array(vs.iter().map(to_owned).collect()))
}
}
QueryResult::ManyOwned(vs) => {
if vs.len() == 1 {
Ok(vs.into_iter().next().unwrap())
} else {
Ok(OwnedValue::Array(vs))
}
}
QueryResult::None => Ok(OwnedValue::Null),
QueryResult::Error(e) => Err(e),
QueryResult::Break(label) => Err(EvalError::new(format!("break ${} not in label", label))),
}
}
fn owned_value_to_json_string(value: &OwnedValue) -> String {
match value {
OwnedValue::Null => "null".into(),
OwnedValue::Bool(true) => "true".into(),
OwnedValue::Bool(false) => "false".into(),
OwnedValue::Int(i) => format!("{}", i),
OwnedValue::Float(f) => {
if f.is_nan() || f.is_infinite() {
"null".into() } else {
format!("{}", f)
}
}
OwnedValue::String(s) => {
let mut result = String::with_capacity(s.len() + 2);
result.push('"');
for c in s.chars() {
match c {
'"' => result.push_str("\\\""),
'\\' => result.push_str("\\\\"),
'\n' => result.push_str("\\n"),
'\r' => result.push_str("\\r"),
'\t' => result.push_str("\\t"),
c if c.is_control() => {
result.push_str(&format!("\\u{:04x}", c as u32));
}
c => result.push(c),
}
}
result.push('"');
result
}
OwnedValue::Array(arr) => {
let mut result = String::from("[");
for (i, v) in arr.iter().enumerate() {
if i > 0 {
result.push(',');
}
result.push_str(&owned_value_to_json_string(v));
}
result.push(']');
result
}
OwnedValue::Object(obj) => {
let mut result = String::from("{");
for (i, (k, v)) in obj.iter().enumerate() {
if i > 0 {
result.push(',');
}
result.push_str(&owned_value_to_json_string(&OwnedValue::String(k.clone())));
result.push(':');
result.push_str(&owned_value_to_json_string(v));
}
result.push('}');
result
}
}
}
fn eval_foreach<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
input: &Expr,
var: &str,
init: &Expr,
update: &Expr,
extract: Option<&Expr>,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let input_result = eval_single::<W, S>(input, value.clone(), optional);
let input_values: Vec<OwnedValue> = match input_result.materialize_cursor() {
QueryResult::One(v) => vec![to_owned(&v)],
QueryResult::OneCursor(_) => unreachable!(),
QueryResult::Many(vs) => vs.iter().map(to_owned).collect(),
QueryResult::Owned(v) => vec![v],
QueryResult::ManyOwned(vs) => vs,
QueryResult::None => Vec::new(),
QueryResult::Error(e) => return QueryResult::Error(e),
QueryResult::Break(label) => return QueryResult::Break(label),
};
let init_result = eval_single::<W, S>(init, value.clone(), optional);
let mut state = match result_to_owned(init_result) {
Ok(v) => v,
Err(e) => return QueryResult::Error(e),
};
let mut outputs: Vec<OwnedValue> = Vec::new();
for input_val in input_values {
let substituted_update = substitute_var(update, var, &input_val);
match eval_owned_expr::<S>(&substituted_update, &state, optional) {
Ok(new_state) => {
state = new_state;
if let Some(ext) = extract {
let substituted_extract = substitute_var(ext, var, &input_val);
match eval_owned_expr::<S>(&substituted_extract, &state, optional) {
Ok(output) => outputs.push(output),
Err(e) => return QueryResult::Error(e),
}
} else {
outputs.push(state.clone());
}
}
Err(e) => return QueryResult::Error(e),
}
}
if outputs.is_empty() {
QueryResult::None
} else if outputs.len() == 1 {
QueryResult::Owned(outputs.pop().unwrap())
} else {
QueryResult::ManyOwned(outputs)
}
}
fn eval_limit<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
n_expr: &Expr,
expr: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let n_result = eval_single::<W, S>(n_expr, value.clone(), optional);
let n = match result_to_owned(n_result) {
Ok(OwnedValue::Int(i)) if i >= 0 => i as usize,
Ok(_) => {
return QueryResult::Error(EvalError::new("limit requires non-negative integer"));
}
Err(e) => return QueryResult::Error(e),
};
if n == 0 {
return QueryResult::None;
}
let result = eval_single::<W, S>(expr, value, optional);
match result {
QueryResult::One(v) if n >= 1 => QueryResult::One(v),
QueryResult::Many(vs) => {
let taken: Vec<_> = vs.into_iter().take(n).collect();
if taken.is_empty() {
QueryResult::None
} else if taken.len() == 1 {
QueryResult::One(taken.into_iter().next().unwrap())
} else {
QueryResult::Many(taken)
}
}
QueryResult::Owned(v) if n >= 1 => QueryResult::Owned(v),
QueryResult::ManyOwned(vs) => {
let taken: Vec<_> = vs.into_iter().take(n).collect();
if taken.is_empty() {
QueryResult::None
} else if taken.len() == 1 {
QueryResult::Owned(taken.into_iter().next().unwrap())
} else {
QueryResult::ManyOwned(taken)
}
}
QueryResult::None => QueryResult::None,
QueryResult::Error(e) => QueryResult::Error(e),
_ => QueryResult::None,
}
}
fn eval_first_expr<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
expr: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let result = eval_single::<W, S>(expr, value, optional);
match result.materialize_cursor() {
QueryResult::One(v) => QueryResult::One(v),
QueryResult::OneCursor(_) => unreachable!(),
QueryResult::Many(vs) => {
if let Some(first) = vs.into_iter().next() {
QueryResult::One(first)
} else {
QueryResult::None
}
}
QueryResult::Owned(v) => QueryResult::Owned(v),
QueryResult::ManyOwned(vs) => {
if let Some(first) = vs.into_iter().next() {
QueryResult::Owned(first)
} else {
QueryResult::None
}
}
QueryResult::None => QueryResult::None,
QueryResult::Error(e) => QueryResult::Error(e),
QueryResult::Break(label) => QueryResult::Break(label),
}
}
fn eval_last_expr<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
expr: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let result = eval_single::<W, S>(expr, value, optional);
match result.materialize_cursor() {
QueryResult::One(v) => QueryResult::One(v),
QueryResult::OneCursor(_) => unreachable!(),
QueryResult::Many(vs) => {
if let Some(last) = vs.into_iter().last() {
QueryResult::One(last)
} else {
QueryResult::None
}
}
QueryResult::Owned(v) => QueryResult::Owned(v),
QueryResult::ManyOwned(vs) => {
if let Some(last) = vs.into_iter().last() {
QueryResult::Owned(last)
} else {
QueryResult::None
}
}
QueryResult::None => QueryResult::None,
QueryResult::Error(e) => QueryResult::Error(e),
QueryResult::Break(label) => QueryResult::Break(label),
}
}
fn eval_nth_expr<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
n_expr: &Expr,
expr: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let n_result = eval_single::<W, S>(n_expr, value.clone(), optional);
let n = match result_to_owned(n_result) {
Ok(OwnedValue::Int(i)) if i >= 0 => i as usize,
Ok(_) => {
return QueryResult::Error(EvalError::new("nth requires non-negative integer"));
}
Err(e) => return QueryResult::Error(e),
};
let result = eval_single::<W, S>(expr, value, optional);
match result.materialize_cursor() {
QueryResult::One(v) if n == 0 => QueryResult::One(v),
QueryResult::OneCursor(_) => unreachable!(),
QueryResult::One(_) => QueryResult::None,
QueryResult::Many(vs) => {
if let Some(item) = vs.into_iter().nth(n) {
QueryResult::One(item)
} else {
QueryResult::None
}
}
QueryResult::Owned(v) if n == 0 => QueryResult::Owned(v),
QueryResult::Owned(_) => QueryResult::None,
QueryResult::ManyOwned(vs) => {
if let Some(item) = vs.into_iter().nth(n) {
QueryResult::Owned(item)
} else {
QueryResult::None
}
}
QueryResult::None => QueryResult::None,
QueryResult::Error(e) => QueryResult::Error(e),
QueryResult::Break(label) => QueryResult::Break(label),
}
}
fn eval_until<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
cond: &Expr,
update: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let mut current = to_owned(&value);
const MAX_ITERATIONS: usize = 10000;
for _ in 0..MAX_ITERATIONS {
match eval_owned_expr::<S>(cond, ¤t, optional) {
Ok(cond_val) => {
if cond_val.is_truthy() {
return QueryResult::Owned(current);
}
}
Err(e) => return QueryResult::Error(e),
}
match eval_owned_expr::<S>(update, ¤t, optional) {
Ok(new_val) => current = new_val,
Err(e) => return QueryResult::Error(e),
}
}
QueryResult::Error(EvalError::new("until: maximum iterations exceeded"))
}
fn eval_while<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
cond: &Expr,
update: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let mut current = to_owned(&value);
let mut outputs: Vec<OwnedValue> = Vec::new();
const MAX_ITERATIONS: usize = 10000;
for _ in 0..MAX_ITERATIONS {
match eval_owned_expr::<S>(cond, ¤t, optional) {
Ok(cond_val) => {
if !cond_val.is_truthy() {
break;
}
}
Err(e) => return QueryResult::Error(e),
}
outputs.push(current.clone());
match eval_owned_expr::<S>(update, ¤t, optional) {
Ok(new_val) => current = new_val,
Err(e) => return QueryResult::Error(e),
}
}
if outputs.is_empty() {
QueryResult::None
} else if outputs.len() == 1 {
QueryResult::Owned(outputs.pop().unwrap())
} else {
QueryResult::ManyOwned(outputs)
}
}
fn eval_repeat<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
expr: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let owned = to_owned(&value);
let mut outputs: Vec<OwnedValue> = Vec::new();
const MAX_ITERATIONS: usize = 1000;
for _ in 0..MAX_ITERATIONS {
match eval_owned_expr::<S>(expr, &owned, optional) {
Ok(new_val) => outputs.push(new_val),
Err(_) => break, }
}
if outputs.is_empty() {
QueryResult::None
} else if outputs.len() == 1 {
QueryResult::Owned(outputs.pop().unwrap())
} else {
QueryResult::ManyOwned(outputs)
}
}
fn eval_range<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
from: &Expr,
to: Option<&Expr>,
step: Option<&Expr>,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let from_val = match eval_single::<W, S>(from, value.clone(), optional) {
QueryResult::Owned(OwnedValue::Int(i)) => i,
QueryResult::Owned(OwnedValue::Float(f)) => f as i64,
QueryResult::One(v) => match to_owned(&v) {
OwnedValue::Int(i) => i,
OwnedValue::Float(f) => f as i64,
_ => return QueryResult::Error(EvalError::new("range requires numbers")),
},
QueryResult::Error(e) => return QueryResult::Error(e),
_ => return QueryResult::Error(EvalError::new("range requires numbers")),
};
let to_val = if let Some(to_expr) = to {
match eval_single::<W, S>(to_expr, value.clone(), optional) {
QueryResult::Owned(OwnedValue::Int(i)) => i,
QueryResult::Owned(OwnedValue::Float(f)) => f as i64,
QueryResult::One(v) => match to_owned(&v) {
OwnedValue::Int(i) => i,
OwnedValue::Float(f) => f as i64,
_ => return QueryResult::Error(EvalError::new("range requires numbers")),
},
QueryResult::Error(e) => return QueryResult::Error(e),
_ => return QueryResult::Error(EvalError::new("range requires numbers")),
}
} else {
let to = from_val;
return eval_range_values::<W>(0, to, 1);
};
let step_val = if let Some(step_expr) = step {
match eval_single::<W, S>(step_expr, value, optional) {
QueryResult::Owned(OwnedValue::Int(i)) if i != 0 => i,
QueryResult::Owned(OwnedValue::Float(f)) if f != 0.0 => f as i64,
QueryResult::One(v) => match to_owned(&v) {
OwnedValue::Int(i) if i != 0 => i,
OwnedValue::Float(f) if f != 0.0 => f as i64,
_ => return QueryResult::Error(EvalError::new("range step cannot be zero")),
},
QueryResult::Error(e) => return QueryResult::Error(e),
_ => return QueryResult::Error(EvalError::new("range step cannot be zero")),
}
} else {
1
};
eval_range_values::<W>(from_val, to_val, step_val)
}
fn eval_range_values<'a, W: Clone + AsRef<[u64]>>(
from: i64,
to: i64,
step: i64,
) -> QueryResult<'a, W> {
let mut values: Vec<OwnedValue> = Vec::new();
const MAX_RANGE: usize = 100000;
if step > 0 {
let mut i = from;
while i < to && values.len() < MAX_RANGE {
values.push(OwnedValue::Int(i));
i += step;
}
} else if step < 0 {
let mut i = from;
while i > to && values.len() < MAX_RANGE {
values.push(OwnedValue::Int(i));
i += step;
}
}
if values.is_empty() {
QueryResult::None
} else if values.len() == 1 {
QueryResult::Owned(values.pop().unwrap())
} else {
QueryResult::ManyOwned(values)
}
}
fn builtin_recurse<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let f = Expr::Optional(Box::new(Expr::Iterate));
builtin_recurse_f::<W, S>(&f, value, optional)
}
fn builtin_recurse_f<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
f: &Expr,
value: StandardJson<'a, W>,
_optional: bool,
) -> QueryResult<'a, W> {
let mut outputs: Vec<OwnedValue> = Vec::new();
let mut queue: Vec<OwnedValue> = vec![to_owned(&value)];
const MAX_ITEMS: usize = 10000;
while !queue.is_empty() && outputs.len() < MAX_ITEMS {
let current = queue.remove(0);
outputs.push(current.clone());
match eval_owned_expr::<S>(f, ¤t, true) {
Ok(OwnedValue::Array(arr)) => {
queue.extend(arr);
}
Ok(v) if !matches!(v, OwnedValue::Null) => {
queue.push(v);
}
_ => {}
}
}
if outputs.is_empty() {
QueryResult::None
} else if outputs.len() == 1 {
QueryResult::Owned(outputs.pop().unwrap())
} else {
QueryResult::ManyOwned(outputs)
}
}
fn builtin_recurse_cond<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
f: &Expr,
cond: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let mut outputs: Vec<OwnedValue> = Vec::new();
let mut queue: Vec<OwnedValue> = vec![to_owned(&value)];
const MAX_ITEMS: usize = 10000;
while !queue.is_empty() && outputs.len() < MAX_ITEMS {
let current = queue.remove(0);
let should_continue = match eval_owned_expr::<S>(cond, ¤t, optional) {
Ok(v) => v.is_truthy(),
Err(_) => false,
};
if !should_continue {
continue;
}
outputs.push(current.clone());
match eval_owned_expr::<S>(f, ¤t, true) {
Ok(OwnedValue::Array(arr)) => {
queue.extend(arr);
}
Ok(v) if !matches!(v, OwnedValue::Null) => {
queue.push(v);
}
_ => {}
}
}
if outputs.is_empty() {
QueryResult::None
} else if outputs.len() == 1 {
QueryResult::Owned(outputs.pop().unwrap())
} else {
QueryResult::ManyOwned(outputs)
}
}
fn builtin_walk<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
f: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let owned = to_owned(&value);
match walk_impl::<S>(f, owned, optional) {
Ok(result) => QueryResult::Owned(result),
Err(e) => QueryResult::Error(e),
}
}
fn walk_impl<S: EvalSemantics>(
f: &Expr,
value: OwnedValue,
optional: bool,
) -> Result<OwnedValue, EvalError> {
let processed = match value {
OwnedValue::Array(arr) => {
let new_arr: Result<Vec<_>, _> = arr
.into_iter()
.map(|v| walk_impl::<S>(f, v, optional))
.collect();
OwnedValue::Array(new_arr?)
}
OwnedValue::Object(obj) => {
let new_obj: Result<IndexMap<_, _>, _> = obj
.into_iter()
.map(|(k, v)| walk_impl::<S>(f, v, optional).map(|nv| (k, nv)))
.collect();
OwnedValue::Object(new_obj?)
}
other => other,
};
eval_owned_expr::<S>(f, &processed, optional)
}
fn builtin_isvalid<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
expr: &Expr,
value: StandardJson<'a, W>,
_optional: bool,
) -> QueryResult<'a, W> {
match eval_single::<W, S>(expr, value, true) {
QueryResult::Error(_) => QueryResult::Owned(OwnedValue::Bool(false)),
QueryResult::None => QueryResult::Owned(OwnedValue::Bool(false)),
_ => QueryResult::Owned(OwnedValue::Bool(true)),
}
}
fn eval_pipe_with_path_context<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
exprs: &[Expr],
value: &OwnedValue,
current_path: &[OwnedValue],
optional: bool,
) -> QueryResult<'a, W> {
eval_pipe_with_path_context_internal::<W, S>(exprs, value, value, current_path, optional)
}
fn eval_pipe_with_path_context_internal<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
exprs: &[Expr],
value: &OwnedValue,
root: &OwnedValue,
current_path: &[OwnedValue],
optional: bool,
) -> QueryResult<'a, W> {
if exprs.is_empty() {
return QueryResult::Owned(value.clone());
}
let (first, rest) = exprs.split_first().unwrap();
if matches!(first, Expr::Builtin(Builtin::PathNoArg)) {
let path_result = QueryResult::Owned(OwnedValue::Array(current_path.to_vec()));
if rest.is_empty() {
return path_result;
}
if let QueryResult::Owned(v) = path_result {
return eval_pipe_with_path_context_internal::<W, S>(
rest,
&v,
root,
current_path,
optional,
);
}
}
if matches!(first, Expr::Builtin(Builtin::Key)) {
let key_result = if current_path.is_empty() {
QueryResult::Owned(OwnedValue::Null)
} else {
QueryResult::Owned(current_path.last().unwrap().clone())
};
if rest.is_empty() {
return key_result;
}
if let QueryResult::Owned(v) = key_result {
return eval_pipe_with_path_context_internal::<W, S>(
rest,
&v,
root,
current_path,
optional,
);
}
}
if matches!(first, Expr::Builtin(Builtin::Parent)) {
let parent_result = if current_path.is_empty() {
QueryResult::Owned(OwnedValue::Object(IndexMap::new()))
} else {
let parent_path = ¤t_path[..current_path.len() - 1];
match get_value_at_owned_path(root, parent_path) {
Some(parent_value) => QueryResult::Owned(parent_value),
None => QueryResult::Owned(OwnedValue::Object(IndexMap::new())),
}
};
if rest.is_empty() {
return parent_result;
}
if let QueryResult::Owned(v) = parent_result {
let parent_path = if current_path.is_empty() {
vec![]
} else {
current_path[..current_path.len() - 1].to_vec()
};
return eval_pipe_with_path_context_internal::<W, S>(
rest,
&v,
root,
&parent_path,
optional,
);
}
}
if let Expr::Builtin(Builtin::ParentN(n_expr)) = first {
let n = match eval_owned_expr::<S>(n_expr, value, optional) {
Ok(OwnedValue::Int(i)) => i as usize,
Ok(OwnedValue::Float(f)) => f as usize,
Ok(_) if optional => return QueryResult::None,
Ok(_) => return QueryResult::Error(EvalError::type_error("number", "other")),
Err(_) if optional => return QueryResult::None,
Err(e) => return QueryResult::Error(e),
};
let parent_path = if n >= current_path.len() {
vec![]
} else {
current_path[..current_path.len() - n].to_vec()
};
let parent_result = if parent_path.is_empty() && n > 0 {
QueryResult::Owned(OwnedValue::Object(IndexMap::new()))
} else {
match get_value_at_owned_path(root, &parent_path) {
Some(parent_value) => QueryResult::Owned(parent_value),
None => QueryResult::Owned(OwnedValue::Object(IndexMap::new())),
}
};
if rest.is_empty() {
return parent_result;
}
if let QueryResult::Owned(v) = parent_result {
return eval_pipe_with_path_context_internal::<W, S>(
rest,
&v,
root,
&parent_path,
optional,
);
}
}
match first {
Expr::Identity => {
eval_pipe_with_path_context_internal::<W, S>(rest, value, root, current_path, optional)
}
Expr::Field(name) => {
let mut new_path = current_path.to_vec();
new_path.push(OwnedValue::String(name.clone()));
if let OwnedValue::Object(entries) = value {
if let Some(v) = entries.get(name) {
if rest.is_empty() {
return QueryResult::Owned(v.clone());
}
return eval_pipe_with_path_context_internal::<W, S>(
rest, v, root, &new_path, optional,
);
}
return QueryResult::Owned(OwnedValue::Null);
}
if matches!(value, OwnedValue::Null) {
return QueryResult::Owned(OwnedValue::Null);
}
if optional {
QueryResult::None
} else {
QueryResult::Error(EvalError::type_error("object", owned_type_name(value)))
}
}
Expr::Index(idx) => {
let mut new_path = current_path.to_vec();
new_path.push(OwnedValue::Int(*idx));
if let OwnedValue::Array(arr) = value {
let len = arr.len() as i64;
let actual_idx = if *idx < 0 { len + *idx } else { *idx };
if actual_idx >= 0 && (actual_idx as usize) < arr.len() {
let v = &arr[actual_idx as usize];
if rest.is_empty() {
return QueryResult::Owned(v.clone());
}
return eval_pipe_with_path_context_internal::<W, S>(
rest, v, root, &new_path, optional,
);
}
}
if optional {
QueryResult::None
} else {
QueryResult::Error(EvalError::index_out_of_bounds(
*idx,
if let OwnedValue::Array(arr) = value {
arr.len()
} else {
0
},
))
}
}
Expr::Iterate => {
let mut results = Vec::new();
match value {
OwnedValue::Array(arr) => {
for (i, v) in arr.iter().enumerate() {
let mut new_path = current_path.to_vec();
new_path.push(OwnedValue::Int(i as i64));
if rest.is_empty() {
results.push(v.clone());
} else {
match eval_pipe_with_path_context_internal::<W, S>(
rest, v, root, &new_path, optional,
) {
QueryResult::Owned(r) => results.push(r),
QueryResult::ManyOwned(rs) => results.extend(rs),
QueryResult::None => {}
QueryResult::Error(e) => return QueryResult::Error(e),
_ => {}
}
}
}
}
OwnedValue::Object(entries) => {
for (key, v) in entries {
let mut new_path = current_path.to_vec();
new_path.push(OwnedValue::String(key.clone()));
if rest.is_empty() {
results.push(v.clone());
} else {
match eval_pipe_with_path_context_internal::<W, S>(
rest, v, root, &new_path, optional,
) {
QueryResult::Owned(r) => results.push(r),
QueryResult::ManyOwned(rs) => results.extend(rs),
QueryResult::None => {}
QueryResult::Error(e) => return QueryResult::Error(e),
_ => {}
}
}
}
}
_ if optional => return QueryResult::None,
_ => {
return QueryResult::Error(EvalError::type_error(
"array or object",
owned_type_name(value),
))
}
}
if results.is_empty() {
QueryResult::None
} else if results.len() == 1 {
QueryResult::Owned(results.pop().unwrap())
} else {
QueryResult::ManyOwned(results)
}
}
Expr::Paren(inner) => {
if rest.is_empty() {
eval_pipe_with_path_context_internal::<W, S>(
&[(**inner).clone()],
value,
root,
current_path,
optional,
)
} else {
let mut combined = vec![(**inner).clone()];
combined.extend(rest.iter().cloned());
eval_pipe_with_path_context_internal::<W, S>(
&combined,
value,
root,
current_path,
optional,
)
}
}
Expr::Optional(inner) => {
if rest.is_empty() {
eval_pipe_with_path_context_internal::<W, S>(
&[(**inner).clone()],
value,
root,
current_path,
true,
)
} else {
let mut combined = vec![(**inner).clone()];
combined.extend(rest.iter().cloned());
eval_pipe_with_path_context_internal::<W, S>(
&combined,
value,
root,
current_path,
true,
)
}
}
Expr::Pipe(inner_exprs) => {
let mut combined = inner_exprs.clone();
combined.extend(rest.iter().cloned());
eval_pipe_with_path_context_internal::<W, S>(
&combined,
value,
root,
current_path,
optional,
)
}
Expr::Builtin(builtin) => {
match eval_builtin_owned::<S>(builtin, value, optional) {
Ok(result) => {
if rest.is_empty() {
QueryResult::Owned(result)
} else {
eval_pipe_with_path_context_internal::<W, S>(
rest,
&result,
root,
current_path,
optional,
)
}
}
Err(_) if optional => QueryResult::None,
Err(e) => QueryResult::Error(e),
}
}
Expr::Object(_) | Expr::Array(_) | Expr::Literal(_) => {
match eval_owned_expr::<S>(first, value, optional) {
Ok(result) => {
if rest.is_empty() {
QueryResult::Owned(result)
} else {
eval_pipe_with_path_context_internal::<W, S>(
rest,
&result,
&result,
&[],
optional,
)
}
}
Err(_) if optional => QueryResult::None,
Err(e) => QueryResult::Error(e),
}
}
_ => {
match eval_owned_expr::<S>(first, value, optional) {
Ok(result) => {
if rest.is_empty() {
QueryResult::Owned(result)
} else {
eval_pipe_with_path_context_internal::<W, S>(
rest,
&result,
root,
current_path,
optional,
)
}
}
Err(_) if optional => QueryResult::None,
Err(e) => QueryResult::Error(e),
}
}
}
}
fn eval_builtin_owned<S: EvalSemantics>(
builtin: &Builtin,
value: &OwnedValue,
optional: bool,
) -> Result<OwnedValue, EvalError> {
eval_owned_expr::<S>(&Expr::Builtin(builtin.clone()), value, optional)
}
fn builtin_path<'a, W: Clone + AsRef<[u64]>>(
expr: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let owned = to_owned(&value);
let mut paths = Vec::new();
eval_with_path_tracking(expr, &owned, &[], &mut paths, optional);
if paths.is_empty() {
if optional {
QueryResult::None
} else {
QueryResult::Owned(OwnedValue::Array(vec![]))
}
} else if paths.len() == 1 {
QueryResult::Owned(paths.into_iter().next().unwrap())
} else {
let owned_paths: Vec<OwnedValue> = paths;
QueryResult::ManyOwned(owned_paths)
}
}
fn eval_with_path_tracking(
expr: &Expr,
value: &OwnedValue,
current_path: &[OwnedValue],
paths: &mut Vec<OwnedValue>,
optional: bool,
) {
match expr {
Expr::Identity => {
paths.push(OwnedValue::Array(current_path.to_vec()));
}
Expr::Field(name) => {
let mut new_path = current_path.to_vec();
new_path.push(OwnedValue::String(name.clone()));
paths.push(OwnedValue::Array(new_path));
}
Expr::Index(idx) => {
let mut new_path = current_path.to_vec();
new_path.push(OwnedValue::Int(*idx));
paths.push(OwnedValue::Array(new_path));
}
Expr::Iterate => match value {
OwnedValue::Array(arr) => {
for (i, _) in arr.iter().enumerate() {
let mut new_path = current_path.to_vec();
new_path.push(OwnedValue::Int(i as i64));
paths.push(OwnedValue::Array(new_path));
}
}
OwnedValue::Object(entries) => {
for (key, _) in entries {
let mut new_path = current_path.to_vec();
new_path.push(OwnedValue::String(key.clone()));
paths.push(OwnedValue::Array(new_path));
}
}
_ => {}
},
Expr::Pipe(exprs) => {
if exprs.is_empty() {
paths.push(OwnedValue::Array(current_path.to_vec()));
return;
}
eval_pipe_with_path_tracking(exprs, value, current_path, paths, optional);
}
Expr::Optional(inner) => {
eval_with_path_tracking(inner, value, current_path, paths, true);
}
Expr::Paren(inner) => {
eval_with_path_tracking(inner, value, current_path, paths, optional);
}
Expr::Slice { start, end } => {
if let OwnedValue::Array(arr) = value {
let len = arr.len() as i64;
let s = start.unwrap_or(0);
let e = end.unwrap_or(len);
let actual_start = if s < 0 { (len + s).max(0) } else { s.min(len) } as usize;
let actual_end = if e < 0 { (len + e).max(0) } else { e.min(len) } as usize;
for i in actual_start..actual_end {
let mut new_path = current_path.to_vec();
new_path.push(OwnedValue::Int(i as i64));
paths.push(OwnedValue::Array(new_path));
}
}
}
_ => {
}
}
}
fn eval_pipe_with_path_tracking(
exprs: &[Expr],
value: &OwnedValue,
current_path: &[OwnedValue],
paths: &mut Vec<OwnedValue>,
optional: bool,
) {
if exprs.is_empty() {
paths.push(OwnedValue::Array(current_path.to_vec()));
return;
}
let first = &exprs[0];
let rest = &exprs[1..];
if rest.is_empty() {
eval_with_path_tracking(first, value, current_path, paths, optional);
return;
}
let mut intermediate: Vec<(Vec<OwnedValue>, OwnedValue)> = Vec::new();
collect_intermediate_with_paths(first, value, current_path, &mut intermediate, optional);
for (path, val) in intermediate {
eval_pipe_with_path_tracking(rest, &val, &path, paths, optional);
}
}
#[allow(clippy::only_used_in_recursion)]
fn collect_intermediate_with_paths(
expr: &Expr,
value: &OwnedValue,
current_path: &[OwnedValue],
results: &mut Vec<(Vec<OwnedValue>, OwnedValue)>,
optional: bool,
) {
match expr {
Expr::Identity => {
results.push((current_path.to_vec(), value.clone()));
}
Expr::Field(name) => {
if let OwnedValue::Object(entries) = value {
for (key, val) in entries {
if key == name {
let mut new_path = current_path.to_vec();
new_path.push(OwnedValue::String(name.clone()));
results.push((new_path, val.clone()));
return;
}
}
}
}
Expr::Index(idx) => {
if let OwnedValue::Array(arr) = value {
let len = arr.len() as i64;
let actual_idx = if *idx < 0 { len + *idx } else { *idx };
if actual_idx >= 0 && (actual_idx as usize) < arr.len() {
let mut new_path = current_path.to_vec();
new_path.push(OwnedValue::Int(*idx));
results.push((new_path, arr[actual_idx as usize].clone()));
}
}
}
Expr::Iterate => match value {
OwnedValue::Array(arr) => {
for (i, val) in arr.iter().enumerate() {
let mut new_path = current_path.to_vec();
new_path.push(OwnedValue::Int(i as i64));
results.push((new_path, val.clone()));
}
}
OwnedValue::Object(entries) => {
for (key, val) in entries {
let mut new_path = current_path.to_vec();
new_path.push(OwnedValue::String(key.clone()));
results.push((new_path, val.clone()));
}
}
_ => {}
},
Expr::Optional(inner) => {
collect_intermediate_with_paths(inner, value, current_path, results, true);
}
Expr::Paren(inner) => {
collect_intermediate_with_paths(inner, value, current_path, results, optional);
}
Expr::Pipe(inner_exprs) => {
if inner_exprs.is_empty() {
results.push((current_path.to_vec(), value.clone()));
return;
}
let mut intermediate: Vec<(Vec<OwnedValue>, OwnedValue)> = Vec::new();
collect_intermediate_with_paths(
&inner_exprs[0],
value,
current_path,
&mut intermediate,
optional,
);
for (path, val) in intermediate {
if inner_exprs.len() == 1 {
results.push((path, val));
} else {
let rest_pipe = Expr::Pipe(inner_exprs[1..].to_vec());
collect_intermediate_with_paths(&rest_pipe, &val, &path, results, optional);
}
}
}
Expr::Slice { start, end } => {
if let OwnedValue::Array(arr) = value {
let len = arr.len() as i64;
let s = start.unwrap_or(0);
let e = end.unwrap_or(len);
let actual_start = if s < 0 { (len + s).max(0) } else { s.min(len) } as usize;
let actual_end = if e < 0 { (len + e).max(0) } else { e.min(len) } as usize;
for (i, item) in arr
.iter()
.enumerate()
.skip(actual_start)
.take(actual_end - actual_start)
{
let mut new_path = current_path.to_vec();
new_path.push(OwnedValue::Int(i as i64));
results.push((new_path, item.clone()));
}
}
}
_ => {
}
}
}
fn collect_paths(value: &OwnedValue, current_path: &[OwnedValue], paths: &mut Vec<OwnedValue>) {
match value {
OwnedValue::Object(entries) => {
for (key, val) in entries {
let mut new_path = current_path.to_vec();
new_path.push(OwnedValue::String(key.clone()));
paths.push(OwnedValue::Array(new_path.clone()));
collect_paths(val, &new_path, paths);
}
}
OwnedValue::Array(arr) => {
for (i, val) in arr.iter().enumerate() {
let mut new_path = current_path.to_vec();
new_path.push(OwnedValue::Int(i as i64));
paths.push(OwnedValue::Array(new_path.clone()));
collect_paths(val, &new_path, paths);
}
}
_ => {}
}
}
fn builtin_paths<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
_optional: bool,
) -> QueryResult<'a, W> {
let owned = to_owned(&value);
let mut paths = Vec::new();
collect_paths(&owned, &[], &mut paths);
if paths.is_empty() {
QueryResult::None
} else if paths.len() == 1 {
QueryResult::Owned(paths.pop().unwrap())
} else {
QueryResult::ManyOwned(paths)
}
}
fn builtin_paths_filter<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
filter: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let owned = to_owned(&value);
let mut all_paths = Vec::new();
collect_paths(&owned, &[], &mut all_paths);
let mut filtered_paths = Vec::new();
for path in all_paths {
if let OwnedValue::Array(path_arr) = &path {
if let Some(val_at_path) = get_value_at_path(&owned, path_arr) {
if let Ok(OwnedValue::Bool(true)) =
eval_owned_expr::<S>(filter, &val_at_path, optional)
{
filtered_paths.push(path);
}
}
}
}
if filtered_paths.is_empty() {
QueryResult::None
} else if filtered_paths.len() == 1 {
QueryResult::Owned(filtered_paths.pop().unwrap())
} else {
QueryResult::ManyOwned(filtered_paths)
}
}
#[inline]
fn get_value_at_owned_path(value: &OwnedValue, path: &[OwnedValue]) -> Option<OwnedValue> {
get_value_at_path(value, path)
}
fn get_value_at_path(value: &OwnedValue, path: &[OwnedValue]) -> Option<OwnedValue> {
if path.is_empty() {
return Some(value.clone());
}
match (&path[0], value) {
(OwnedValue::String(key), OwnedValue::Object(entries)) => {
for (k, v) in entries {
if k == key {
return get_value_at_path(v, &path[1..]);
}
}
None
}
(OwnedValue::Int(idx), OwnedValue::Array(arr)) => {
let index = if *idx < 0 {
(arr.len() as i64 + *idx) as usize
} else {
*idx as usize
};
arr.get(index)
.and_then(|v| get_value_at_path(v, &path[1..]))
}
_ => None,
}
}
fn collect_leaf_paths(
value: &OwnedValue,
current_path: &[OwnedValue],
paths: &mut Vec<OwnedValue>,
) {
match value {
OwnedValue::Object(entries) => {
if entries.is_empty() {
paths.push(OwnedValue::Array(current_path.to_vec()));
} else {
for (key, val) in entries {
let mut new_path = current_path.to_vec();
new_path.push(OwnedValue::String(key.clone()));
collect_leaf_paths(val, &new_path, paths);
}
}
}
OwnedValue::Array(arr) => {
if arr.is_empty() {
paths.push(OwnedValue::Array(current_path.to_vec()));
} else {
for (i, val) in arr.iter().enumerate() {
let mut new_path = current_path.to_vec();
new_path.push(OwnedValue::Int(i as i64));
collect_leaf_paths(val, &new_path, paths);
}
}
}
_ => {
paths.push(OwnedValue::Array(current_path.to_vec()));
}
}
}
fn builtin_leaf_paths<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
_optional: bool,
) -> QueryResult<'a, W> {
let owned = to_owned(&value);
let mut paths = Vec::new();
collect_leaf_paths(&owned, &[], &mut paths);
if paths.is_empty() {
QueryResult::None
} else if paths.len() == 1 {
QueryResult::Owned(paths.pop().unwrap())
} else {
QueryResult::ManyOwned(paths)
}
}
fn set_value_at_path(value: OwnedValue, path: &[OwnedValue], new_val: OwnedValue) -> OwnedValue {
if path.is_empty() {
return new_val;
}
match &path[0] {
OwnedValue::String(key) => match value {
OwnedValue::Object(mut entries) => {
if entries.contains_key(key) {
let old = entries.shift_remove(key).unwrap_or(OwnedValue::Null);
let new = set_value_at_path(old, &path[1..], new_val);
entries.insert(key.clone(), new);
} else {
let val = set_value_at_path(OwnedValue::Null, &path[1..], new_val);
entries.insert(key.clone(), val);
}
OwnedValue::Object(entries)
}
_ => {
let val = set_value_at_path(OwnedValue::Null, &path[1..], new_val);
let mut entries = IndexMap::new();
entries.insert(key.clone(), val);
OwnedValue::Object(entries)
}
},
OwnedValue::Int(idx) => match value {
OwnedValue::Array(mut arr) => {
let index = if *idx < 0 {
(arr.len() as i64 + *idx) as usize
} else {
*idx as usize
};
while arr.len() <= index {
arr.push(OwnedValue::Null);
}
let old = arr[index].clone();
arr[index] = set_value_at_path(old, &path[1..], new_val);
OwnedValue::Array(arr)
}
_ => {
let index = if *idx < 0 { 0 } else { *idx as usize };
let mut arr = vec![OwnedValue::Null; index + 1];
arr[index] = set_value_at_path(OwnedValue::Null, &path[1..], new_val);
OwnedValue::Array(arr)
}
},
_ => value,
}
}
fn builtin_setpath<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
path_expr: &Expr,
val_expr: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let path_result = eval_single::<W, S>(path_expr, value.clone(), optional);
let path_owned = match path_result {
QueryResult::One(v) => to_owned(&v),
QueryResult::Owned(v) => v,
QueryResult::Error(e) => return QueryResult::Error(e),
_ => return QueryResult::Error(EvalError::new("setpath requires array path")),
};
let path = match path_owned {
OwnedValue::Array(p) => p,
_ => return QueryResult::Error(EvalError::new("setpath requires array path")),
};
let new_val = match eval_single::<W, S>(val_expr, value.clone(), optional) {
QueryResult::One(v) => to_owned(&v),
QueryResult::Owned(v) => v,
QueryResult::Error(e) => return QueryResult::Error(e),
_ => OwnedValue::Null,
};
let owned = to_owned(&value);
let result = set_value_at_path(owned, &path, new_val);
QueryResult::Owned(result)
}
fn delete_path(value: OwnedValue, path: &[OwnedValue]) -> OwnedValue {
if path.is_empty() {
return OwnedValue::Null;
}
if path.len() == 1 {
match &path[0] {
OwnedValue::String(key) => match value {
OwnedValue::Object(mut entries) => {
entries.shift_remove(key);
return OwnedValue::Object(entries);
}
other => return other,
},
OwnedValue::Int(idx) => match value {
OwnedValue::Array(mut arr) => {
let index = if *idx < 0 {
(arr.len() as i64 + *idx) as usize
} else {
*idx as usize
};
if index < arr.len() {
arr.remove(index);
}
return OwnedValue::Array(arr);
}
other => return other,
},
_ => return value,
}
}
match &path[0] {
OwnedValue::String(key) => match value {
OwnedValue::Object(mut entries) => {
if let Some(v) = entries.shift_remove(key) {
let updated = delete_path(v, &path[1..]);
entries.insert(key.clone(), updated);
}
OwnedValue::Object(entries)
}
other => other,
},
OwnedValue::Int(idx) => match value {
OwnedValue::Array(mut arr) => {
let index = if *idx < 0 {
(arr.len() as i64 + *idx) as usize
} else {
*idx as usize
};
if index < arr.len() {
let old = arr[index].clone();
arr[index] = delete_path(old, &path[1..]);
}
OwnedValue::Array(arr)
}
other => other,
},
_ => value,
}
}
fn builtin_del<'a, W: Clone + AsRef<[u64]>>(
path_expr: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let mut result = to_owned(&value);
if let Err(e) = delete_at_path(&mut result, path_expr, optional) {
return QueryResult::Error(e);
}
QueryResult::Owned(result)
}
fn delete_at_path(
root: &mut OwnedValue,
path_expr: &Expr,
optional: bool,
) -> Result<(), EvalError> {
match path_expr {
Expr::Identity => {
*root = OwnedValue::Null;
Ok(())
}
Expr::Field(name) => {
if let OwnedValue::Object(map) = root {
map.shift_remove(name);
Ok(())
} else if optional {
Ok(())
} else {
Err(EvalError::type_error("object", owned_type_name(root)))
}
}
Expr::Index(idx) => {
if let OwnedValue::Array(arr) = root {
let len = arr.len() as i64;
let actual_idx = if *idx < 0 { len + idx } else { *idx };
if actual_idx >= 0 && (actual_idx as usize) < arr.len() {
arr.remove(actual_idx as usize);
Ok(())
} else if optional {
Ok(())
} else {
Err(EvalError::index_out_of_bounds(*idx, arr.len()))
}
} else if optional {
Ok(())
} else {
Err(EvalError::type_error("array", owned_type_name(root)))
}
}
Expr::Iterate => {
match root {
OwnedValue::Array(arr) => {
arr.clear();
Ok(())
}
OwnedValue::Object(map) => {
map.clear();
Ok(())
}
_ if optional => Ok(()),
_ => Err(EvalError::type_error(
"array or object",
owned_type_name(root),
)),
}
}
Expr::Pipe(exprs) if !exprs.is_empty() => {
if exprs.len() == 1 {
delete_at_path(root, &exprs[0], optional)
} else {
let first = &exprs[0];
let rest = Expr::Pipe(exprs[1..].to_vec());
match first {
Expr::Field(name) => {
if let OwnedValue::Object(map) = root {
if let Some(current) = map.get_mut(name) {
delete_at_path(current, &rest, optional)
} else if optional {
Ok(())
} else {
Err(EvalError::field_not_found(name))
}
} else if optional {
Ok(())
} else {
Err(EvalError::type_error("object", owned_type_name(root)))
}
}
Expr::Index(idx) => {
if let OwnedValue::Array(arr) = root {
let len = arr.len() as i64;
let actual_idx = if *idx < 0 { len + idx } else { *idx };
if actual_idx >= 0 && (actual_idx as usize) < arr.len() {
delete_at_path(&mut arr[actual_idx as usize], &rest, optional)
} else if optional {
Ok(())
} else {
Err(EvalError::index_out_of_bounds(*idx, arr.len()))
}
} else if optional {
Ok(())
} else {
Err(EvalError::type_error("array", owned_type_name(root)))
}
}
Expr::Iterate => match root {
OwnedValue::Array(arr) => {
for elem in arr.iter_mut() {
delete_at_path(elem, &rest, optional)?;
}
Ok(())
}
OwnedValue::Object(map) => {
for value in map.values_mut() {
delete_at_path(value, &rest, optional)?;
}
Ok(())
}
_ if optional => Ok(()),
_ => Err(EvalError::type_error(
"array or object",
owned_type_name(root),
)),
},
_ => delete_at_path(root, first, optional),
}
}
}
Expr::Optional(inner) => delete_at_path(root, inner, true),
_ => Err(EvalError::new("cannot use expression as delete target")),
}
}
fn builtin_now<'a, W: Clone + AsRef<[u64]>>() -> QueryResult<'a, W> {
#[cfg(feature = "std")]
{
use std::time::{SystemTime, UNIX_EPOCH};
match SystemTime::now().duration_since(UNIX_EPOCH) {
Ok(duration) => QueryResult::Owned(OwnedValue::Float(duration.as_secs_f64())),
Err(_) => QueryResult::Error(EvalError::new("failed to get current time")),
}
}
#[cfg(not(feature = "std"))]
{
QueryResult::Owned(OwnedValue::Float(0.0))
}
}
fn builtin_gmtime<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let timestamp = match get_float_value::<W>(&value, optional) {
Ok(f) => f,
Err(r) => return r,
};
let secs = timestamp.trunc() as i64;
let days = if secs >= 0 {
secs / 86400
} else {
(secs - 86399) / 86400
};
let time_of_day = ((secs % 86400) + 86400) % 86400;
let hour = (time_of_day / 3600) as i64;
let minute = ((time_of_day % 3600) / 60) as i64;
let second = (time_of_day % 60) as i64;
let z = days + 719468; let era = if z >= 0 { z } else { z - 146096 } / 146097;
let doe = (z - era * 146097) as u32; let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365; let y = yoe as i64 + era * 400;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100); let mp = (5 * doy + 2) / 153; let day = (doy - (153 * mp + 2) / 5 + 1) as i64;
let month = if mp < 10 { mp + 3 } else { mp - 9 };
let year = y + if month <= 2 { 1 } else { 0 };
let weekday = ((days % 7 + 4 + 7) % 7) as i64;
let is_leap = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
let month_days: [i64; 12] = if is_leap {
[0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335]
} else {
[0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]
};
let yearday = month_days[(month - 1) as usize] + day - 1;
let result = vec![
OwnedValue::Int(year),
OwnedValue::Int((month - 1) as i64), OwnedValue::Int(day),
OwnedValue::Int(hour),
OwnedValue::Int(minute),
OwnedValue::Int(second),
OwnedValue::Int(weekday),
OwnedValue::Int(yearday),
];
QueryResult::Owned(OwnedValue::Array(result))
}
fn builtin_localtime<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
#[cfg(feature = "std")]
{
let timestamp = match get_float_value::<W>(&value, optional) {
Ok(f) => f,
Err(r) => return r,
};
use std::time::{SystemTime, UNIX_EPOCH};
let now_utc = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs() as i64)
.unwrap_or(0);
let secs = timestamp.trunc() as i64;
let local_offset = estimate_local_offset(now_utc);
let local_secs = secs + local_offset;
let days = if local_secs >= 0 {
local_secs / 86400
} else {
(local_secs - 86399) / 86400
};
let time_of_day = ((local_secs % 86400) + 86400) % 86400;
let hour = (time_of_day / 3600) as i64;
let minute = ((time_of_day % 3600) / 60) as i64;
let second = (time_of_day % 60) as i64;
let z = days + 719468;
let era = if z >= 0 { z } else { z - 146096 } / 146097;
let doe = (z - era * 146097) as u32;
let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
let y = yoe as i64 + era * 400;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let day = (doy - (153 * mp + 2) / 5 + 1) as i64;
let month = if mp < 10 { mp + 3 } else { mp - 9 };
let year = y + if month <= 2 { 1 } else { 0 };
let weekday = ((days % 7 + 4 + 7) % 7) as i64;
let is_leap = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
let month_days: [i64; 12] = if is_leap {
[0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335]
} else {
[0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]
};
let yearday = month_days[(month - 1) as usize] + day - 1;
let result = vec![
OwnedValue::Int(year),
OwnedValue::Int((month - 1) as i64),
OwnedValue::Int(day),
OwnedValue::Int(hour),
OwnedValue::Int(minute),
OwnedValue::Int(second),
OwnedValue::Int(weekday),
OwnedValue::Int(yearday),
];
QueryResult::Owned(OwnedValue::Array(result))
}
#[cfg(not(feature = "std"))]
{
builtin_gmtime::<W>(value, optional)
}
}
#[cfg(feature = "std")]
fn estimate_local_offset(utc_secs: i64) -> i64 {
if let Ok(tz) = std::env::var("TZ") {
if let Some(offset) = parse_simple_tz_offset(&tz) {
return offset;
}
}
let _ = utc_secs; 0
}
#[cfg(feature = "std")]
fn parse_simple_tz_offset(tz: &str) -> Option<i64> {
let offset_start = tz.find(|c: char| c.is_ascii_digit() || c == '-' || c == '+')?;
let offset_part = &tz[offset_start..];
let offset_end = offset_part
.find(|c: char| c.is_ascii_alphabetic())
.unwrap_or(offset_part.len());
let offset_str = &offset_part[..offset_end];
let negative = offset_str.starts_with('-');
let offset_str = offset_str.trim_start_matches(['+', '-']);
let parts: Vec<&str> = offset_str.split(':').collect();
let hours: i64 = parts.first()?.parse().ok()?;
let minutes: i64 = parts.get(1).and_then(|s| s.parse().ok()).unwrap_or(0);
let offset_secs = (hours * 3600 + minutes * 60) * if negative { 1 } else { -1 };
Some(offset_secs)
}
fn builtin_mktime<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let arr = match to_owned(&value) {
OwnedValue::Array(a) => a,
_ if optional => return QueryResult::None,
_ => return QueryResult::Error(EvalError::type_error("array", "mktime")),
};
if arr.len() < 6 {
if optional {
return QueryResult::None;
}
return QueryResult::Error(EvalError::new(
"mktime requires array with at least 6 elements",
));
}
let get_int = |idx: usize| -> Result<i64, EvalError> {
match arr.get(idx) {
Some(OwnedValue::Int(n)) => Ok(*n),
Some(OwnedValue::Float(f)) => Ok(*f as i64),
_ => Err(EvalError::new(format!(
"mktime: element {} must be a number",
idx
))),
}
};
let year = match get_int(0) {
Ok(y) => y,
Err(_) if optional => return QueryResult::None,
Err(e) => return QueryResult::Error(e),
};
let month = match get_int(1) {
Ok(m) => m + 1, Err(_) if optional => return QueryResult::None,
Err(e) => return QueryResult::Error(e),
};
let day = match get_int(2) {
Ok(d) => d,
Err(_) if optional => return QueryResult::None,
Err(e) => return QueryResult::Error(e),
};
let hour = match get_int(3) {
Ok(h) => h,
Err(_) if optional => return QueryResult::None,
Err(e) => return QueryResult::Error(e),
};
let minute = match get_int(4) {
Ok(m) => m,
Err(_) if optional => return QueryResult::None,
Err(e) => return QueryResult::Error(e),
};
let second = match get_int(5) {
Ok(s) => s,
Err(_) if optional => return QueryResult::None,
Err(e) => return QueryResult::Error(e),
};
let y = year - if month <= 2 { 1 } else { 0 };
let era = if y >= 0 { y } else { y - 399 } / 400;
let yoe = (y - era * 400) as u32; let m = month as u32;
let d = day as u32;
let doy = (153 * (if m > 2 { m - 3 } else { m + 9 }) + 2) / 5 + d - 1; let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; let days = era * 146097 + doe as i64 - 719468;
let timestamp = days * 86400 + hour * 3600 + minute * 60 + second;
QueryResult::Owned(OwnedValue::Float(timestamp as f64))
}
fn builtin_strftime<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
fmt_expr: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let fmt = match result_to_owned(eval_single::<W, S>(fmt_expr, value.clone(), optional)) {
Ok(OwnedValue::String(s)) => s,
Ok(_) if optional => return QueryResult::None,
Ok(_) => return QueryResult::Error(EvalError::type_error("string", "strftime format")),
Err(e) => return QueryResult::Error(e),
};
let arr = match to_owned(&value) {
OwnedValue::Array(a) => a,
_ if optional => return QueryResult::None,
_ => return QueryResult::Error(EvalError::type_error("array", "strftime")),
};
if arr.len() < 6 {
if optional {
return QueryResult::None;
}
return QueryResult::Error(EvalError::new(
"strftime requires array with at least 6 elements",
));
}
let get_int = |idx: usize| -> i64 {
match arr.get(idx) {
Some(OwnedValue::Int(n)) => *n,
Some(OwnedValue::Float(f)) => *f as i64,
_ => 0,
}
};
let year = get_int(0);
let month = get_int(1) + 1; let day = get_int(2);
let hour = get_int(3);
let minute = get_int(4);
let second = get_int(5);
let weekday = if arr.len() > 6 { get_int(6) } else { 0 };
let yearday = if arr.len() > 7 { get_int(7) } else { 0 };
let result = format_strftime(
&fmt, year, month, day, hour, minute, second, weekday, yearday,
);
QueryResult::Owned(OwnedValue::String(result))
}
#[allow(clippy::too_many_arguments)]
fn format_strftime(
fmt: &str,
year: i64,
month: i64,
day: i64,
hour: i64,
minute: i64,
second: i64,
weekday: i64,
yearday: i64,
) -> String {
let mut result = String::new();
let mut chars = fmt.chars().peekable();
while let Some(c) = chars.next() {
if c == '%' {
match chars.next() {
Some('%') => result.push('%'),
Some('Y') => result.push_str(&format!("{:04}", year)),
Some('y') => result.push_str(&format!("{:02}", year % 100)),
Some('m') => result.push_str(&format!("{:02}", month)),
Some('d') => result.push_str(&format!("{:02}", day)),
Some('e') => result.push_str(&format!("{:2}", day)),
Some('H') => result.push_str(&format!("{:02}", hour)),
Some('I') => result.push_str(&format!(
"{:02}",
if hour == 0 {
12
} else if hour > 12 {
hour - 12
} else {
hour
}
)),
Some('M') => result.push_str(&format!("{:02}", minute)),
Some('S') => result.push_str(&format!("{:02}", second)),
Some('p') => result.push_str(if hour < 12 { "AM" } else { "PM" }),
Some('P') => result.push_str(if hour < 12 { "am" } else { "pm" }),
Some('j') => result.push_str(&format!("{:03}", yearday + 1)), Some('w') => result.push_str(&format!("{}", weekday)),
Some('u') => {
result.push_str(&format!("{}", if weekday == 0 { 7 } else { weekday }))
} Some('a') => {
let names = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
result.push_str(names[weekday as usize % 7]);
}
Some('A') => {
let names = [
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
];
result.push_str(names[weekday as usize % 7]);
}
Some('b') | Some('h') => {
let names = [
"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct",
"Nov", "Dec",
];
result.push_str(names[(month - 1) as usize % 12]);
}
Some('B') => {
let names = [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December",
];
result.push_str(names[(month - 1) as usize % 12]);
}
Some('C') => result.push_str(&format!("{:02}", year / 100)),
Some('D') => result.push_str(&format!("{:02}/{:02}/{:02}", month, day, year % 100)),
Some('F') => result.push_str(&format!("{:04}-{:02}-{:02}", year, month, day)),
Some('R') => result.push_str(&format!("{:02}:{:02}", hour, minute)),
Some('T') => result.push_str(&format!("{:02}:{:02}:{:02}", hour, minute, second)),
Some('n') => result.push('\n'),
Some('t') => result.push('\t'),
Some('z') => result.push_str("+0000"), Some('Z') => result.push_str("UTC"),
Some(other) => {
result.push('%');
result.push(other);
}
None => result.push('%'),
}
} else {
result.push(c);
}
}
result
}
fn builtin_strptime<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
fmt_expr: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let fmt = match result_to_owned(eval_single::<W, S>(fmt_expr, value.clone(), optional)) {
Ok(OwnedValue::String(s)) => s,
Ok(_) if optional => return QueryResult::None,
Ok(_) => return QueryResult::Error(EvalError::type_error("string", "strptime format")),
Err(e) => return QueryResult::Error(e),
};
let input = match &value {
StandardJson::String(s) => match s.as_str() {
Ok(cow) => cow.into_owned(),
Err(_) if optional => return QueryResult::None,
Err(_) => return QueryResult::Error(EvalError::new("invalid string")),
},
_ if optional => return QueryResult::None,
_ => return QueryResult::Error(EvalError::type_error("string", "strptime")),
};
match parse_strptime(&input, &fmt) {
Ok(t) => {
let result = vec![
OwnedValue::Int(t.year),
OwnedValue::Int(t.month - 1), OwnedValue::Int(t.day),
OwnedValue::Int(t.hour),
OwnedValue::Int(t.minute),
OwnedValue::Int(t.second),
OwnedValue::Int(t.weekday),
OwnedValue::Int(t.yearday),
];
QueryResult::Owned(OwnedValue::Array(result))
}
Err(_) if optional => QueryResult::None,
Err(e) => QueryResult::Error(EvalError::new(e)),
}
}
struct BrokenDownTime {
year: i64,
month: i64, day: i64,
hour: i64,
minute: i64,
second: i64,
weekday: i64, yearday: i64, }
#[allow(clippy::type_complexity)]
fn parse_strptime(input: &str, fmt: &str) -> Result<BrokenDownTime, String> {
let mut year: i64 = 1970;
let mut month: i64 = 1;
let mut day: i64 = 1;
let mut hour: i64 = 0;
let mut minute: i64 = 0;
let mut second: i64 = 0;
#[allow(unused_variables, unused_assignments)]
let mut weekday: i64 = 4; #[allow(unused_variables, unused_assignments)]
let mut yearday: i64 = 0;
let mut input_iter = input.chars().peekable();
let mut fmt_iter = fmt.chars().peekable();
while let Some(fc) = fmt_iter.next() {
if fc == '%' {
match fmt_iter.next() {
Some('%') => {
if input_iter.next() != Some('%') {
return Err("expected '%'".to_string());
}
}
Some('Y') => {
year = parse_digits(&mut input_iter, 4)?;
}
Some('y') => {
let y = parse_digits(&mut input_iter, 2)?;
year = if y >= 69 { 1900 + y } else { 2000 + y };
}
Some('m') => {
month = parse_digits(&mut input_iter, 2)?;
}
Some('d') => {
day = parse_digits(&mut input_iter, 2)?;
}
Some('e') => {
if input_iter.peek() == Some(&' ') {
input_iter.next();
}
day = parse_digits(&mut input_iter, 2)?;
}
Some('H') => {
hour = parse_digits(&mut input_iter, 2)?;
}
Some('I') => {
hour = parse_digits(&mut input_iter, 2)?;
}
Some('M') => {
minute = parse_digits(&mut input_iter, 2)?;
}
Some('S') => {
second = parse_digits(&mut input_iter, 2)?;
}
Some('p') | Some('P') => {
let mut ampm = String::new();
while let Some(&c) = input_iter.peek() {
if c.is_ascii_alphabetic() {
ampm.push(c);
input_iter.next();
} else {
break;
}
}
let ampm_lower = ampm.to_lowercase();
if ampm_lower == "pm" && hour < 12 {
hour += 12;
} else if ampm_lower == "am" && hour == 12 {
hour = 0;
}
}
#[allow(unused_assignments)]
Some('j') => {
yearday = parse_digits(&mut input_iter, 3)? - 1; }
#[allow(unused_assignments)]
Some('w') => {
weekday = parse_digits(&mut input_iter, 1)?;
}
#[allow(unused_assignments)]
Some('u') => {
let w = parse_digits(&mut input_iter, 1)?;
weekday = if w == 7 { 0 } else { w };
}
Some('a') | Some('A') => {
while let Some(&c) = input_iter.peek() {
if c.is_ascii_alphabetic() {
input_iter.next();
} else {
break;
}
}
}
Some('b') | Some('B') | Some('h') => {
let mut name = String::new();
while let Some(&c) = input_iter.peek() {
if c.is_ascii_alphabetic() {
name.push(c);
input_iter.next();
} else {
break;
}
}
let name_lower = name.to_lowercase();
let months = [
"jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct",
"nov", "dec",
];
for (i, m) in months.iter().enumerate() {
if name_lower.starts_with(m) {
month = (i + 1) as i64;
break;
}
}
}
Some('C') => {
let century = parse_digits(&mut input_iter, 2)?;
year = century * 100 + (year % 100);
}
Some('D') => {
month = parse_digits(&mut input_iter, 2)?;
if input_iter.next() != Some('/') {
return Err("expected '/'".to_string());
}
day = parse_digits(&mut input_iter, 2)?;
if input_iter.next() != Some('/') {
return Err("expected '/'".to_string());
}
let y = parse_digits(&mut input_iter, 2)?;
year = if y >= 69 { 1900 + y } else { 2000 + y };
}
Some('F') => {
year = parse_digits(&mut input_iter, 4)?;
if input_iter.next() != Some('-') {
return Err("expected '-'".to_string());
}
month = parse_digits(&mut input_iter, 2)?;
if input_iter.next() != Some('-') {
return Err("expected '-'".to_string());
}
day = parse_digits(&mut input_iter, 2)?;
}
Some('R') => {
hour = parse_digits(&mut input_iter, 2)?;
if input_iter.next() != Some(':') {
return Err("expected ':'".to_string());
}
minute = parse_digits(&mut input_iter, 2)?;
}
Some('T') => {
hour = parse_digits(&mut input_iter, 2)?;
if input_iter.next() != Some(':') {
return Err("expected ':'".to_string());
}
minute = parse_digits(&mut input_iter, 2)?;
if input_iter.next() != Some(':') {
return Err("expected ':'".to_string());
}
second = parse_digits(&mut input_iter, 2)?;
}
Some('n') | Some('t') => {
while let Some(&c) = input_iter.peek() {
if c.is_whitespace() {
input_iter.next();
} else {
break;
}
}
}
Some('z') => {
if let Some(&c) = input_iter.peek() {
if c == '+' || c == '-' {
input_iter.next();
for _ in 0..4 {
if input_iter
.peek()
.map(|c| c.is_ascii_digit())
.unwrap_or(false)
{
input_iter.next();
}
}
}
}
}
Some('Z') => {
while let Some(&c) = input_iter.peek() {
if c.is_ascii_alphabetic() {
input_iter.next();
} else {
break;
}
}
}
Some(other) => {
if input_iter.next() != Some(other) {
return Err(format!("expected '{}'", other));
}
}
None => {
if input_iter.next() != Some('%') {
return Err("expected '%'".to_string());
}
}
}
} else if fc.is_whitespace() {
while let Some(&c) = input_iter.peek() {
if c.is_whitespace() {
input_iter.next();
} else {
break;
}
}
} else {
match input_iter.next() {
Some(c) if c == fc => {}
Some(c) => return Err(format!("expected '{}', got '{}'", fc, c)),
None => return Err(format!("expected '{}', got end of input", fc)),
}
}
}
let y = year - if month <= 2 { 1 } else { 0 };
let era = if y >= 0 { y } else { y - 399 } / 400;
let yoe = (y - era * 400) as u32;
let m = month as u32;
let d = day as u32;
let doy = (153 * (if m > 2 { m - 3 } else { m + 9 }) + 2) / 5 + d - 1;
let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
let days = era * 146097 + doe as i64 - 719468;
weekday = (days % 7 + 4 + 7) % 7;
let is_leap = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
let month_days: [i64; 12] = if is_leap {
[0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335]
} else {
[0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]
};
let yearday = month_days[(month - 1) as usize] + day - 1;
Ok(BrokenDownTime {
year,
month,
day,
hour,
minute,
second,
weekday,
yearday,
})
}
fn parse_digits(
input: &mut core::iter::Peekable<core::str::Chars>,
max_digits: usize,
) -> Result<i64, String> {
let mut s = String::new();
for _ in 0..max_digits {
if let Some(&c) = input.peek() {
if c.is_ascii_digit() {
s.push(c);
input.next();
} else {
break;
}
} else {
break;
}
}
if s.is_empty() {
return Err("expected digits".to_string());
}
s.parse().map_err(|_| "invalid number".to_string())
}
fn builtin_todate<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let timestamp = match get_float_value::<W>(&value, optional) {
Ok(f) => f,
Err(r) => return r,
};
let secs = timestamp.trunc() as i64;
let days = if secs >= 0 {
secs / 86400
} else {
(secs - 86399) / 86400
};
let time_of_day = ((secs % 86400) + 86400) % 86400;
let hour = time_of_day / 3600;
let minute = (time_of_day % 3600) / 60;
let second = time_of_day % 60;
let z = days + 719468;
let era = if z >= 0 { z } else { z - 146096 } / 146097;
let doe = (z - era * 146097) as u32;
let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
let y = yoe as i64 + era * 400;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let day = doy - (153 * mp + 2) / 5 + 1;
let month = if mp < 10 { mp + 3 } else { mp - 9 };
let year = y + if month <= 2 { 1 } else { 0 };
let result = format!(
"{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z",
year, month, day, hour, minute, second
);
QueryResult::Owned(OwnedValue::String(result))
}
fn builtin_fromdate<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let input = match &value {
StandardJson::String(s) => match s.as_str() {
Ok(cow) => cow.into_owned(),
Err(_) if optional => return QueryResult::None,
Err(_) => return QueryResult::Error(EvalError::new("invalid string")),
},
_ if optional => return QueryResult::None,
_ => return QueryResult::Error(EvalError::type_error("string", "fromdate")),
};
match parse_iso8601(&input) {
Ok(timestamp) => QueryResult::Owned(OwnedValue::Float(timestamp)),
Err(_) if optional => QueryResult::None,
Err(e) => QueryResult::Error(EvalError::new(e)),
}
}
fn parse_iso8601(input: &str) -> Result<f64, String> {
let input = input.trim();
let mut chars = input.chars().peekable();
let year: i64 = parse_digits(&mut chars, 4)?;
if chars.next() != Some('-') {
return Err("expected '-' after year".to_string());
}
let month: i64 = parse_digits(&mut chars, 2)?;
if chars.next() != Some('-') {
return Err("expected '-' after month".to_string());
}
let day: i64 = parse_digits(&mut chars, 2)?;
let (hour, minute, second, tz_offset) =
if chars.peek() == Some(&'T') || chars.peek() == Some(&'t') || chars.peek() == Some(&' ') {
chars.next();
let hour: i64 = parse_digits(&mut chars, 2)?;
if chars.next() != Some(':') {
return Err("expected ':' after hour".to_string());
}
let minute: i64 = parse_digits(&mut chars, 2)?;
let second = if chars.peek() == Some(&':') {
chars.next();
let s = parse_digits(&mut chars, 2)?;
if chars.peek() == Some(&'.') {
chars.next();
while chars.peek().map(|c| c.is_ascii_digit()).unwrap_or(false) {
chars.next();
}
}
s
} else {
0
};
let tz_offset = match chars.peek() {
Some('Z') | Some('z') => {
chars.next();
0
}
Some('+') => {
chars.next();
let h = parse_digits(&mut chars, 2)?;
let m = if chars.peek() == Some(&':') {
chars.next();
parse_digits(&mut chars, 2)?
} else if chars.peek().map(|c| c.is_ascii_digit()).unwrap_or(false) {
parse_digits(&mut chars, 2)?
} else {
0
};
-(h * 3600 + m * 60) }
Some('-') => {
chars.next();
let h = parse_digits(&mut chars, 2)?;
let m = if chars.peek() == Some(&':') {
chars.next();
parse_digits(&mut chars, 2)?
} else if chars.peek().map(|c| c.is_ascii_digit()).unwrap_or(false) {
parse_digits(&mut chars, 2)?
} else {
0
};
h * 3600 + m * 60 }
_ => 0, };
(hour, minute, second, tz_offset)
} else {
(0, 0, 0, 0)
};
let y = year - if month <= 2 { 1 } else { 0 };
let era = if y >= 0 { y } else { y - 399 } / 400;
let yoe = (y - era * 400) as u32;
let m = month as u32;
let d = day as u32;
let doy = (153 * (if m > 2 { m - 3 } else { m + 9 }) + 2) / 5 + d - 1;
let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
let days = era * 146097 + doe as i64 - 719468;
let timestamp = days * 86400 + hour * 3600 + minute * 60 + second + tz_offset;
Ok(timestamp as f64)
}
fn builtin_from_unix<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
builtin_todate::<W>(value, optional)
}
fn builtin_to_unix<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
builtin_fromdate::<W>(value, optional)
}
fn format_datetime_with_offset(timestamp: f64, offset_seconds: i64) -> String {
let adjusted_secs = timestamp.trunc() as i64 + offset_seconds;
let days = if adjusted_secs >= 0 {
adjusted_secs / 86400
} else {
(adjusted_secs - 86399) / 86400
};
let time_of_day = ((adjusted_secs % 86400) + 86400) % 86400;
let hour = time_of_day / 3600;
let minute = (time_of_day % 3600) / 60;
let second = time_of_day % 60;
let z = days + 719468;
let era = if z >= 0 { z } else { z - 146096 } / 146097;
let doe = (z - era * 146097) as u32;
let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
let y = yoe as i64 + era * 400;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let day = doy - (153 * mp + 2) / 5 + 1;
let month = if mp < 10 { mp + 3 } else { mp - 9 };
let year = y + if month <= 2 { 1 } else { 0 };
if offset_seconds == 0 {
format!(
"{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z",
year, month, day, hour, minute, second
)
} else {
let offset_hours = offset_seconds.abs() / 3600;
let offset_mins = (offset_seconds.abs() % 3600) / 60;
let sign = if offset_seconds >= 0 { '+' } else { '-' };
format!(
"{:04}-{:02}-{:02}T{:02}:{:02}:{:02}{}{:02}:{:02}",
year, month, day, hour, minute, second, sign, offset_hours, offset_mins
)
}
}
fn get_timezone_offset(zone: &str, timestamp: f64) -> Result<i64, String> {
match zone.to_lowercase().as_str() {
"utc" | "z" | "gmt" => Ok(0),
"local" => {
let _ = timestamp; Ok(0)
}
_ => {
let offset = match zone.to_uppercase().as_str() {
"EST" => -5 * 3600,
"EDT" => -4 * 3600,
"CST" => -6 * 3600,
"CDT" => -5 * 3600,
"MST" => -7 * 3600,
"MDT" => -6 * 3600,
"PST" => -8 * 3600,
"PDT" => -7 * 3600,
"AKST" => -9 * 3600,
"AKDT" => -8 * 3600,
"HST" => -10 * 3600,
"WET" => 0,
"WEST" => 3600,
"CET" => 3600,
"CEST" => 2 * 3600,
"EET" => 2 * 3600,
"EEST" => 3 * 3600,
"JST" => 9 * 3600,
"KST" => 9 * 3600,
"CST_CHINA" => 8 * 3600,
"IST" => 5 * 3600 + 30 * 60, "AEST" => 10 * 3600,
"AEDT" => 13600,
"ACST" => 9 * 3600 + 30 * 60,
"ACDT" => 10 * 3600 + 30 * 60,
"AWST" => 8 * 3600,
_ => {
let offset = match zone {
"America/New_York" | "US/Eastern" => {
if is_dst_us_eastern(timestamp) {
-4 * 3600
} else {
-5 * 3600
}
}
"America/Chicago" | "US/Central" => {
if is_dst_us_eastern(timestamp) {
-5 * 3600
} else {
-6 * 3600
}
}
"America/Denver" | "US/Mountain" => {
if is_dst_us_eastern(timestamp) {
-6 * 3600
} else {
-7 * 3600
}
}
"America/Los_Angeles" | "US/Pacific" => {
if is_dst_us_eastern(timestamp) {
-7 * 3600
} else {
-8 * 3600
}
}
"America/Anchorage" | "US/Alaska" => {
if is_dst_us_eastern(timestamp) {
-8 * 3600
} else {
-9 * 3600
}
}
"Pacific/Honolulu" | "US/Hawaii" => -10 * 3600,
"Europe/London" | "GB" => {
if is_dst_europe(timestamp) {
3600
} else {
0
}
}
"Europe/Paris" | "Europe/Berlin" | "Europe/Rome" => {
if is_dst_europe(timestamp) {
2 * 3600
} else {
3600
}
}
"Europe/Moscow" => 3 * 3600,
"Asia/Tokyo" | "Japan" => 9 * 3600,
"Asia/Seoul" | "ROK" => 9 * 3600,
"Asia/Shanghai" | "Asia/Hong_Kong" | "PRC" => 8 * 3600,
"Asia/Kolkata" | "Asia/Calcutta" => 5 * 3600 + 30 * 60,
"Asia/Dubai" => 4 * 3600,
"Asia/Singapore" => 8 * 3600,
"Australia/Sydney" => {
if is_dst_australia(timestamp) {
13600
} else {
10 * 3600
}
}
"Australia/Melbourne" => {
if is_dst_australia(timestamp) {
13600
} else {
10 * 3600
}
}
"Australia/Perth" => 8 * 3600,
"Etc/UTC" | "Etc/GMT" | "UTC" | "GMT" => 0,
_ => {
if let Some(offset) = parse_numeric_offset(zone) {
return Ok(offset);
}
return Err(format!("unknown timezone: {}", zone));
}
};
return Ok(offset);
}
};
Ok(offset)
}
}
}
fn parse_numeric_offset(s: &str) -> Option<i64> {
let s = s.trim();
if s.is_empty() {
return None;
}
let (sign, rest) = match s.chars().next() {
Some('+') => (1i64, &s[1..]),
Some('-') => (-1i64, &s[1..]),
_ => return None,
};
if rest.len() == 5 && rest.chars().nth(2) == Some(':') {
let hours: i64 = rest[..2].parse().ok()?;
let mins: i64 = rest[3..].parse().ok()?;
return Some(sign * (hours * 3600 + mins * 60));
}
if rest.len() == 4 && rest.chars().all(|c| c.is_ascii_digit()) {
let hours: i64 = rest[..2].parse().ok()?;
let mins: i64 = rest[2..].parse().ok()?;
return Some(sign * (hours * 3600 + mins * 60));
}
if rest.len() == 2 && rest.chars().all(|c| c.is_ascii_digit()) {
let hours: i64 = rest.parse().ok()?;
return Some(sign * hours * 3600);
}
None
}
fn is_dst_us_eastern(timestamp: f64) -> bool {
let secs = timestamp.trunc() as i64;
let days = secs / 86400;
let z = days + 719468;
let era = if z >= 0 { z } else { z - 146096 } / 146097;
let doe = (z - era * 146097) as u32;
let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let day = (doy - (153 * mp + 2) / 5 + 1) as i32;
let month = if mp < 10 { mp + 3 } else { mp - 9 } as i32;
match month {
3 => day >= 8, 4..=10 => true, 11 => day < 7, _ => false, }
}
fn is_dst_europe(timestamp: f64) -> bool {
let secs = timestamp.trunc() as i64;
let days = secs / 86400;
let z = days + 719468;
let era = if z >= 0 { z } else { z - 146096 } / 146097;
let doe = (z - era * 146097) as u32;
let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let day = (doy - (153 * mp + 2) / 5 + 1) as i32;
let month = if mp < 10 { mp + 3 } else { mp - 9 } as i32;
match month {
3 => day >= 25, 4..=9 => true, 10 => day < 25, _ => false,
}
}
fn is_dst_australia(timestamp: f64) -> bool {
let secs = timestamp.trunc() as i64;
let days = secs / 86400;
let z = days + 719468;
let era = if z >= 0 { z } else { z - 146096 } / 146097;
let doe = (z - era * 146097) as u32;
let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let day = (doy - (153 * mp + 2) / 5 + 1) as i32;
let month = if mp < 10 { mp + 3 } else { mp - 9 } as i32;
match month {
10 => day >= 7, 11 | 12 | 1 | 2 | 3 => true, 4 => day < 7, _ => false,
}
}
fn builtin_tz<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
zone_expr: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let timestamp = match get_float_value::<W>(&value, optional) {
Ok(f) => f,
Err(r) => return r,
};
let zone_str = match result_to_owned(eval_single::<W, S>(zone_expr, value.clone(), optional)) {
Ok(OwnedValue::String(s)) => s,
Ok(_) if optional => return QueryResult::None,
Ok(_) => return QueryResult::Error(EvalError::type_error("string", "tz zone argument")),
Err(e) => return QueryResult::Error(e),
};
let offset = match get_timezone_offset(&zone_str, timestamp) {
Ok(o) => o,
Err(_) if optional => return QueryResult::None,
Err(e) => return QueryResult::Error(EvalError::new(e)),
};
let result = format_datetime_with_offset(timestamp, offset);
QueryResult::Owned(OwnedValue::String(result))
}
#[cfg(feature = "std")]
fn builtin_load<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
file_expr: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
use std::path::Path;
let filename = match result_to_owned(eval_single::<W, S>(file_expr, value, optional)) {
Ok(OwnedValue::String(s)) => s,
Ok(_) if optional => return QueryResult::None,
Ok(_) => return QueryResult::Error(EvalError::type_error("string", "load filename")),
Err(e) => return QueryResult::Error(e),
};
let file_bytes = match std::fs::read(&filename) {
Ok(bytes) => bytes,
Err(_) if optional => {
return QueryResult::None;
}
Err(e) => {
return QueryResult::Error(EvalError::new(format!(
"load: failed to read file '{}': {}",
filename, e
)));
}
};
let path = Path::new(&filename);
let is_json = path
.extension()
.and_then(|e| e.to_str())
.map(|ext| ext.eq_ignore_ascii_case("json"))
.unwrap_or(false);
if is_json {
let index = crate::json::JsonIndex::build(&file_bytes);
let cursor = index.root(&file_bytes);
QueryResult::Owned(to_owned(&cursor.value()))
} else {
match crate::yaml::YamlIndex::build(&file_bytes) {
Ok(index) => {
let root = index.root(&file_bytes);
match root.value() {
crate::yaml::YamlValue::Sequence(docs) => {
let doc_values: Vec<OwnedValue> =
docs.into_iter().map(|v| yaml_value_to_owned(v)).collect();
if doc_values.len() == 1 {
QueryResult::Owned(doc_values.into_iter().next().unwrap())
} else {
QueryResult::Owned(OwnedValue::Array(doc_values))
}
}
other => {
QueryResult::Owned(yaml_value_to_owned(other))
}
}
}
Err(_) if optional => QueryResult::None,
Err(e) => QueryResult::Error(EvalError::new(format!(
"load: failed to parse YAML file '{}': {}",
filename, e
))),
}
}
}
#[cfg(feature = "std")]
fn yaml_value_to_owned<W: Clone + AsRef<[u64]>>(
value: crate::yaml::YamlValue<'_, W>,
) -> OwnedValue {
use crate::yaml::YamlValue;
match value {
YamlValue::Null => OwnedValue::Null,
YamlValue::String(s) => {
let str_value = match s.as_str() {
Ok(cow) => cow.into_owned(),
Err(_) => return OwnedValue::Null,
};
if !s.is_unquoted() {
return OwnedValue::String(str_value);
}
match str_value.as_str() {
"null" | "~" | "" => return OwnedValue::Null,
"true" | "True" | "TRUE" => return OwnedValue::Bool(true),
"false" | "False" | "FALSE" => return OwnedValue::Bool(false),
_ => {}
}
if let Ok(n) = str_value.parse::<i64>() {
return OwnedValue::Int(n);
}
if let Ok(f) = str_value.parse::<f64>() {
if !f.is_nan() {
return OwnedValue::Float(f);
}
}
match str_value.as_str() {
".inf" | ".Inf" | ".INF" => return OwnedValue::Float(f64::INFINITY),
"-.inf" | "-.Inf" | "-.INF" => return OwnedValue::Float(f64::NEG_INFINITY),
".nan" | ".NaN" | ".NAN" => return OwnedValue::Float(f64::NAN),
_ => {}
}
OwnedValue::String(str_value)
}
YamlValue::Sequence(elements) => {
let items: Vec<OwnedValue> = elements.into_iter().map(yaml_value_to_owned).collect();
OwnedValue::Array(items)
}
YamlValue::Mapping(fields) => {
let mut map = indexmap::IndexMap::new();
for field in fields {
let key = match field.key() {
YamlValue::String(s) => match s.as_str() {
Ok(cow) => cow.into_owned(),
Err(_) => continue,
},
other => {
let v = yaml_value_to_owned(other);
v.to_json()
}
};
let value = yaml_value_to_owned(field.value());
map.insert(key, value);
}
OwnedValue::Object(map)
}
YamlValue::Alias { target, .. } => {
if let Some(target_cursor) = target {
yaml_value_to_owned(target_cursor.value())
} else {
OwnedValue::Null
}
}
YamlValue::Error(_) => OwnedValue::Null,
}
}
#[cfg(not(feature = "std"))]
fn builtin_load<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
_file_expr: &Expr,
_value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
if optional {
QueryResult::None
} else {
QueryResult::Error(EvalError::new(
"load() requires the 'std' feature to be enabled".to_string(),
))
}
}
fn builtin_combinations<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let arrays = match &value {
StandardJson::Array(elements) => {
let mut arrays: Vec<Vec<OwnedValue>> = Vec::new();
for elem in *elements {
match elem {
StandardJson::Array(inner) => {
let inner_values: Vec<OwnedValue> = inner.map(|v| to_owned(&v)).collect();
arrays.push(inner_values);
}
_ if optional => return QueryResult::None,
_ => {
return QueryResult::Error(EvalError::type_error("array", type_name(&elem)))
}
}
}
arrays
}
_ if optional => return QueryResult::None,
_ => return QueryResult::Error(EvalError::type_error("array", type_name(&value))),
};
if arrays.iter().any(|a| a.is_empty()) {
return QueryResult::None;
}
if arrays.is_empty() {
return QueryResult::Owned(OwnedValue::Array(Vec::new()));
}
let results = cartesian_product(&arrays);
QueryResult::ManyOwned(results)
}
fn builtin_combinations_n<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
n_expr: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let n = match result_to_owned(eval_single::<W, S>(n_expr, value.clone(), optional)) {
Ok(OwnedValue::Int(i)) if i >= 0 => i as usize,
Ok(OwnedValue::Int(_)) if optional => return QueryResult::None,
Ok(OwnedValue::Int(_)) => {
return QueryResult::Error(EvalError::new("combinations(n): n must be non-negative"))
}
Ok(_) if optional => return QueryResult::None,
Ok(_) => return QueryResult::Error(EvalError::type_error("number", "n")),
Err(e) => return QueryResult::Error(e),
};
let base_array = match &value {
StandardJson::Array(elements) => (*elements).map(|v| to_owned(&v)).collect::<Vec<_>>(),
_ if optional => return QueryResult::None,
_ => return QueryResult::Error(EvalError::type_error("array", type_name(&value))),
};
if n == 0 {
return QueryResult::Owned(OwnedValue::Array(Vec::new()));
}
if base_array.is_empty() {
return QueryResult::None;
}
let arrays: Vec<Vec<OwnedValue>> = (0..n).map(|_| base_array.clone()).collect();
let results = cartesian_product(&arrays);
QueryResult::ManyOwned(results)
}
fn cartesian_product(arrays: &[Vec<OwnedValue>]) -> Vec<OwnedValue> {
if arrays.is_empty() {
return vec![OwnedValue::Array(Vec::new())];
}
let mut results = Vec::new();
let mut indices = vec![0usize; arrays.len()];
loop {
let combination: Vec<OwnedValue> = indices
.iter()
.enumerate()
.map(|(i, &idx)| arrays[i][idx].clone())
.collect();
results.push(OwnedValue::Array(combination));
let mut carry = true;
for i in (0..arrays.len()).rev() {
if carry {
indices[i] += 1;
if indices[i] >= arrays[i].len() {
indices[i] = 0;
} else {
carry = false;
}
}
}
if carry {
break;
}
}
results
}
fn builtin_builtins<'a, W: Clone + AsRef<[u64]>>() -> QueryResult<'a, W> {
let builtins = vec![
"type/0",
"isnull/0",
"isboolean/0",
"isnumber/0",
"isstring/0",
"isarray/0",
"isobject/0",
"values/0",
"nulls/0",
"booleans/0",
"numbers/0",
"strings/0",
"arrays/0",
"objects/0",
"iterables/0",
"scalars/0",
"normals/0",
"finites/0",
"length/0",
"utf8bytelength/0",
"keys/0",
"keys_unsorted/0",
"has/1",
"in/1",
"select/1",
"empty/0",
"map/1",
"map_values/1",
"add/0",
"any/0",
"all/0",
"min/0",
"max/0",
"min_by/1",
"max_by/1",
"ascii_downcase/0",
"ascii_upcase/0",
"ltrimstr/1",
"rtrimstr/1",
"startswith/1",
"endswith/1",
"split/1",
"join/1",
"contains/1",
"inside/1",
"trim/0",
"ltrim/0",
"rtrim/0",
"first/0",
"last/0",
"nth/1",
"reverse/0",
"flatten/0",
"flatten/1",
"group_by/1",
"unique/0",
"unique_by/1",
"sort/0",
"sort_by/1",
"transpose/0",
"bsearch/1",
"to_entries/0",
"from_entries/0",
"with_entries/1",
"pick/1",
"tostring/0",
"tonumber/0",
"tojson/0",
"fromjson/0",
"explode/0",
"implode/0",
"test/1",
"indices/1",
"index/1",
"rindex/1",
"tojsonstream/0",
"fromjsonstream/0",
"path/1",
"paths/0",
"paths/1",
"leaf_paths/0",
"getpath/1",
"setpath/2",
"delpaths/1",
"del/1",
"floor/0",
"ceil/0",
"round/0",
"sqrt/0",
"fabs/0",
"abs/0",
"log/0",
"log10/0",
"log2/0",
"exp/0",
"exp10/0",
"exp2/0",
"pow/2",
"sin/0",
"cos/0",
"tan/0",
"asin/0",
"acos/0",
"atan/0",
"atan2/2",
"sinh/0",
"cosh/0",
"tanh/0",
"asinh/0",
"acosh/0",
"atanh/0",
"infinite/0",
"nan/0",
"isinfinite/0",
"isnan/0",
"isnormal/0",
"isfinite/0",
"recurse/0",
"recurse/1",
"recurse/2",
"walk/1",
"isvalid/1",
"limit/2",
"skip/2",
"first/1",
"last/1",
"nth/2",
"until/2",
"while/2",
"repeat/1",
"range/1",
"range/2",
"range/3",
"reduce/3",
"foreach/3",
"foreach/4",
"debug/0",
"debug/1",
"env/0",
"env/1",
"strenv/1",
"now/0",
"gmtime/0",
"localtime/0",
"mktime/0",
"strftime/1",
"strptime/1",
"todate/0",
"fromdate/0",
"todateiso8601/0",
"fromdateiso8601/0",
"combinations/0",
"combinations/1",
"trunc/0",
"toboolean/0",
"builtins/0",
"modulemeta/1",
"error/0",
"error/1",
"tag/0",
"anchor/0",
"style/0",
"kind/0",
"key/0",
"line/0",
"column/0",
"parent/0",
"parent/1",
];
let arr: Vec<OwnedValue> = builtins
.iter()
.map(|s| OwnedValue::String((*s).to_string()))
.collect();
QueryResult::Owned(OwnedValue::Array(arr))
}
fn builtin_normals<'a, W: Clone + AsRef<[u64]>>(value: StandardJson<'a, W>) -> QueryResult<'a, W> {
if let StandardJson::Number(n) = &value {
if let Ok(f) = n.as_f64() {
if f.is_normal() {
return QueryResult::One(value);
}
}
}
QueryResult::None
}
fn builtin_finites<'a, W: Clone + AsRef<[u64]>>(value: StandardJson<'a, W>) -> QueryResult<'a, W> {
if let StandardJson::Number(n) = &value {
if let Ok(f) = n.as_f64() {
if f.is_finite() {
return QueryResult::One(value);
}
}
}
QueryResult::None
}
fn builtin_limit<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
n_expr: &Expr,
expr: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let n_result = eval_single::<W, S>(n_expr, value.clone(), optional);
let n = match n_result {
QueryResult::One(v) => {
if let StandardJson::Number(num) = v {
num.as_i64().unwrap_or(0) as usize
} else {
return QueryResult::Error(EvalError::type_error("number", type_name(&v)));
}
}
QueryResult::Owned(OwnedValue::Int(i)) => i as usize,
QueryResult::Owned(OwnedValue::Float(f)) => f as usize,
QueryResult::Error(e) => return QueryResult::Error(e),
_ => return QueryResult::Error(EvalError::type_error("number", "null")),
};
if n == 0 {
return QueryResult::None;
}
let result = eval_single::<W, S>(expr, value, optional);
match result {
QueryResult::One(v) => QueryResult::Owned(to_owned(&v)),
QueryResult::OneCursor(c) => QueryResult::Owned(to_owned(&c.value())),
QueryResult::Owned(v) => QueryResult::Owned(v),
QueryResult::Many(results) => {
let limited: Vec<OwnedValue> =
results.into_iter().take(n).map(|v| to_owned(&v)).collect();
if limited.is_empty() {
QueryResult::None
} else if limited.len() == 1 {
QueryResult::Owned(limited.into_iter().next().unwrap())
} else {
QueryResult::ManyOwned(limited)
}
}
QueryResult::ManyOwned(results) => {
let limited: Vec<OwnedValue> = results.into_iter().take(n).collect();
if limited.is_empty() {
QueryResult::None
} else if limited.len() == 1 {
QueryResult::Owned(limited.into_iter().next().unwrap())
} else {
QueryResult::ManyOwned(limited)
}
}
QueryResult::None => QueryResult::None,
QueryResult::Error(e) => QueryResult::Error(e),
QueryResult::Break(label) => QueryResult::Break(label),
}
}
fn builtin_first_stream<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
expr: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let result = eval_single::<W, S>(expr, value, optional);
match result {
QueryResult::One(v) => QueryResult::Owned(to_owned(&v)),
QueryResult::OneCursor(c) => QueryResult::Owned(to_owned(&c.value())),
QueryResult::Owned(v) => QueryResult::Owned(v),
QueryResult::Many(results) => {
if let Some(first) = results.into_iter().next() {
QueryResult::Owned(to_owned(&first))
} else {
QueryResult::None
}
}
QueryResult::ManyOwned(results) => {
if let Some(first) = results.into_iter().next() {
QueryResult::Owned(first)
} else {
QueryResult::None
}
}
QueryResult::None => QueryResult::None,
QueryResult::Error(e) => QueryResult::Error(e),
QueryResult::Break(label) => QueryResult::Break(label),
}
}
fn builtin_last_stream<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
expr: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let result = eval_single::<W, S>(expr, value, optional);
match result {
QueryResult::One(v) => QueryResult::Owned(to_owned(&v)),
QueryResult::OneCursor(c) => QueryResult::Owned(to_owned(&c.value())),
QueryResult::Owned(v) => QueryResult::Owned(v),
QueryResult::Many(results) => {
if let Some(last) = results.into_iter().last() {
QueryResult::Owned(to_owned(&last))
} else {
QueryResult::None
}
}
QueryResult::ManyOwned(results) => {
if let Some(last) = results.into_iter().last() {
QueryResult::Owned(last)
} else {
QueryResult::None
}
}
QueryResult::None => QueryResult::None,
QueryResult::Error(e) => QueryResult::Error(e),
QueryResult::Break(label) => QueryResult::Break(label),
}
}
fn builtin_nth_stream<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
n_expr: &Expr,
expr: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let n_result = eval_single::<W, S>(n_expr, value.clone(), optional);
let n = match n_result {
QueryResult::One(v) => {
if let StandardJson::Number(num) = v {
num.as_i64().unwrap_or(0) as usize
} else {
return QueryResult::Error(EvalError::type_error("number", type_name(&v)));
}
}
QueryResult::Owned(OwnedValue::Int(i)) => i as usize,
QueryResult::Owned(OwnedValue::Float(f)) => f as usize,
QueryResult::Error(e) => return QueryResult::Error(e),
_ => return QueryResult::Error(EvalError::type_error("number", "null")),
};
let result = eval_single::<W, S>(expr, value, optional);
match result {
QueryResult::One(v) if n == 0 => QueryResult::Owned(to_owned(&v)),
QueryResult::OneCursor(c) if n == 0 => QueryResult::Owned(to_owned(&c.value())),
QueryResult::Owned(v) if n == 0 => QueryResult::Owned(v),
QueryResult::Many(results) => {
if let Some(nth) = results.into_iter().nth(n) {
QueryResult::Owned(to_owned(&nth))
} else {
QueryResult::None
}
}
QueryResult::ManyOwned(results) => {
if let Some(nth) = results.into_iter().nth(n) {
QueryResult::Owned(nth)
} else {
QueryResult::None
}
}
_ => QueryResult::None,
}
}
fn builtin_range<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
n_expr: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let n_result = eval_single::<W, S>(n_expr, value, optional);
let n = match n_result {
QueryResult::One(v) => {
if let StandardJson::Number(num) = v {
num.as_i64().unwrap_or(0)
} else {
return QueryResult::Error(EvalError::type_error("number", type_name(&v)));
}
}
QueryResult::Owned(OwnedValue::Int(i)) => i,
QueryResult::Owned(OwnedValue::Float(f)) => f as i64,
QueryResult::Error(e) => return QueryResult::Error(e),
_ => return QueryResult::Error(EvalError::type_error("number", "null")),
};
if n <= 0 {
return QueryResult::None;
}
let results: Vec<OwnedValue> = (0..n).map(OwnedValue::Int).collect();
if results.len() == 1 {
QueryResult::Owned(results.into_iter().next().unwrap())
} else {
QueryResult::ManyOwned(results)
}
}
fn builtin_range_from_to<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
from_expr: &Expr,
to_expr: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let from_result = eval_single::<W, S>(from_expr, value.clone(), optional);
let from = match from_result {
QueryResult::One(v) => {
if let StandardJson::Number(num) = v {
num.as_i64().unwrap_or(0)
} else {
return QueryResult::Error(EvalError::type_error("number", type_name(&v)));
}
}
QueryResult::Owned(OwnedValue::Int(i)) => i,
QueryResult::Owned(OwnedValue::Float(f)) => f as i64,
QueryResult::Error(e) => return QueryResult::Error(e),
_ => return QueryResult::Error(EvalError::type_error("number", "null")),
};
let to_result = eval_single::<W, S>(to_expr, value, optional);
let to = match to_result {
QueryResult::One(v) => {
if let StandardJson::Number(num) = v {
num.as_i64().unwrap_or(0)
} else {
return QueryResult::Error(EvalError::type_error("number", type_name(&v)));
}
}
QueryResult::Owned(OwnedValue::Int(i)) => i,
QueryResult::Owned(OwnedValue::Float(f)) => f as i64,
QueryResult::Error(e) => return QueryResult::Error(e),
_ => return QueryResult::Error(EvalError::type_error("number", "null")),
};
if from >= to {
return QueryResult::None;
}
let results: Vec<OwnedValue> = (from..to).map(OwnedValue::Int).collect();
if results.len() == 1 {
QueryResult::Owned(results.into_iter().next().unwrap())
} else {
QueryResult::ManyOwned(results)
}
}
fn builtin_range_from_to_by<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
from_expr: &Expr,
to_expr: &Expr,
by_expr: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let from_result = eval_single::<W, S>(from_expr, value.clone(), optional);
let from = match from_result {
QueryResult::One(v) => {
if let StandardJson::Number(num) = v {
num.as_i64().unwrap_or(0)
} else {
return QueryResult::Error(EvalError::type_error("number", type_name(&v)));
}
}
QueryResult::Owned(OwnedValue::Int(i)) => i,
QueryResult::Owned(OwnedValue::Float(f)) => f as i64,
QueryResult::Error(e) => return QueryResult::Error(e),
_ => return QueryResult::Error(EvalError::type_error("number", "null")),
};
let to_result = eval_single::<W, S>(to_expr, value.clone(), optional);
let to = match to_result {
QueryResult::One(v) => {
if let StandardJson::Number(num) = v {
num.as_i64().unwrap_or(0)
} else {
return QueryResult::Error(EvalError::type_error("number", type_name(&v)));
}
}
QueryResult::Owned(OwnedValue::Int(i)) => i,
QueryResult::Owned(OwnedValue::Float(f)) => f as i64,
QueryResult::Error(e) => return QueryResult::Error(e),
_ => return QueryResult::Error(EvalError::type_error("number", "null")),
};
let by_result = eval_single::<W, S>(by_expr, value, optional);
let by = match by_result {
QueryResult::One(v) => {
if let StandardJson::Number(num) = v {
num.as_i64().unwrap_or(1)
} else {
return QueryResult::Error(EvalError::type_error("number", type_name(&v)));
}
}
QueryResult::Owned(OwnedValue::Int(i)) => i,
QueryResult::Owned(OwnedValue::Float(f)) => f as i64,
QueryResult::Error(e) => return QueryResult::Error(e),
_ => return QueryResult::Error(EvalError::type_error("number", "null")),
};
if by == 0 {
return QueryResult::Error(EvalError::new("range step cannot be zero"));
}
let mut results = Vec::new();
let mut i = from;
if by > 0 {
while i < to {
results.push(OwnedValue::Int(i));
i += by;
}
} else {
while i > to {
results.push(OwnedValue::Int(i));
i += by;
}
}
if results.is_empty() {
QueryResult::None
} else if results.len() == 1 {
QueryResult::Owned(results.into_iter().next().unwrap())
} else {
QueryResult::ManyOwned(results)
}
}
fn builtin_isempty<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
expr: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let result = eval_single::<W, S>(expr, value, optional);
let is_empty = match result {
QueryResult::None => true,
QueryResult::Many(ref v) if v.is_empty() => true,
QueryResult::ManyOwned(ref v) if v.is_empty() => true,
QueryResult::Error(_) => true, _ => false,
};
QueryResult::Owned(OwnedValue::Bool(is_empty))
}
fn builtin_delpaths<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
paths_expr: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let paths_result = eval_single::<W, S>(paths_expr, value.clone(), optional);
let paths_owned = match paths_result {
QueryResult::One(v) => to_owned(&v),
QueryResult::Owned(v) => v,
QueryResult::Error(e) => return QueryResult::Error(e),
_ => return QueryResult::Error(EvalError::new("delpaths requires array of paths")),
};
let paths = match paths_owned {
OwnedValue::Array(p) => p,
_ => return QueryResult::Error(EvalError::new("delpaths requires array of paths")),
};
let mut result = to_owned(&value);
let mut sorted_paths: Vec<Vec<OwnedValue>> = paths
.into_iter()
.filter_map(|p| match p {
OwnedValue::Array(arr) => Some(arr),
_ => None,
})
.collect();
sorted_paths.sort_by_key(|b| core::cmp::Reverse(b.len()));
for path in sorted_paths {
result = delete_path(result, &path);
}
QueryResult::Owned(result)
}
fn get_float_value<'a, W: Clone + AsRef<[u64]>>(
value: &StandardJson<'a, W>,
optional: bool,
) -> Result<f64, QueryResult<'a, W>> {
match value {
StandardJson::Number(n) => {
if let Ok(f) = n.as_f64() {
Ok(f)
} else if optional {
Err(QueryResult::None)
} else {
Err(QueryResult::Error(EvalError::new("invalid number")))
}
}
_ if optional => Err(QueryResult::None),
_ => Err(QueryResult::Error(EvalError::new(
"math function requires number",
))),
}
}
fn floor_f64(x: f64) -> f64 {
let t = x as i64 as f64;
if x < t {
t - 1.0
} else {
t
}
}
fn ceil_f64(x: f64) -> f64 {
let t = x as i64 as f64;
if x > t {
t + 1.0
} else {
t
}
}
fn round_f64(x: f64) -> f64 {
if x >= 0.0 {
floor_f64(x + 0.5)
} else {
ceil_f64(x - 0.5)
}
}
fn sqrt_f64(x: f64) -> f64 {
if x < 0.0 {
return f64::NAN;
}
if x == 0.0 {
return 0.0;
}
let mut guess = x / 2.0;
for _ in 0..50 {
let next = (guess + x / guess) / 2.0;
if (next - guess).abs() < 1e-15 * guess.abs() {
break;
}
guess = next;
}
guess
}
fn builtin_floor<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
match get_float_value::<W>(&value, optional) {
Ok(n) => QueryResult::Owned(OwnedValue::Int(floor_f64(n) as i64)),
Err(r) => r,
}
}
fn builtin_ceil<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
match get_float_value::<W>(&value, optional) {
Ok(n) => QueryResult::Owned(OwnedValue::Int(ceil_f64(n) as i64)),
Err(r) => r,
}
}
fn builtin_round<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
match get_float_value::<W>(&value, optional) {
Ok(n) => QueryResult::Owned(OwnedValue::Int(round_f64(n) as i64)),
Err(r) => r,
}
}
fn builtin_trunc<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
match get_float_value::<W>(&value, optional) {
Ok(n) => QueryResult::Owned(OwnedValue::Int(libm::trunc(n) as i64)),
Err(r) => r,
}
}
fn builtin_sqrt<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
match get_float_value::<W>(&value, optional) {
Ok(n) => QueryResult::Owned(OwnedValue::Float(sqrt_f64(n))),
Err(r) => r,
}
}
fn builtin_fabs<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
match get_float_value::<W>(&value, optional) {
Ok(n) => QueryResult::Owned(OwnedValue::Float(libm::fabs(n))),
Err(r) => r,
}
}
fn builtin_log<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
match get_float_value::<W>(&value, optional) {
Ok(n) => QueryResult::Owned(OwnedValue::Float(libm::log(n))),
Err(r) => r,
}
}
fn builtin_log10<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
match get_float_value::<W>(&value, optional) {
Ok(n) => QueryResult::Owned(OwnedValue::Float(libm::log10(n))),
Err(r) => r,
}
}
fn builtin_log2<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
match get_float_value::<W>(&value, optional) {
Ok(n) => QueryResult::Owned(OwnedValue::Float(libm::log2(n))),
Err(r) => r,
}
}
fn builtin_exp<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
match get_float_value::<W>(&value, optional) {
Ok(n) => QueryResult::Owned(OwnedValue::Float(libm::exp(n))),
Err(r) => r,
}
}
fn builtin_exp10<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
match get_float_value::<W>(&value, optional) {
Ok(n) => QueryResult::Owned(OwnedValue::Float(libm::pow(10.0, n))),
Err(r) => r,
}
}
fn builtin_exp2<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
match get_float_value::<W>(&value, optional) {
Ok(n) => QueryResult::Owned(OwnedValue::Float(libm::exp2(n))),
Err(r) => r,
}
}
enum NumberError {
None,
Error(EvalError),
}
fn get_number_from_result<W: Clone + AsRef<[u64]>>(
result: QueryResult<'_, W>,
optional: bool,
) -> Result<f64, NumberError> {
match result {
QueryResult::Owned(OwnedValue::Int(n)) => Ok(n as f64),
QueryResult::Owned(OwnedValue::Float(n)) => Ok(n),
QueryResult::One(StandardJson::Number(n)) => n
.as_f64()
.map_err(|_| NumberError::Error(EvalError::new("invalid number"))),
QueryResult::Error(e) => Err(NumberError::Error(e)),
_ if optional => Err(NumberError::None),
_ => Err(NumberError::Error(EvalError::new("expected number"))),
}
}
fn builtin_pow<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
base_expr: &Expr,
exp_expr: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let base = match get_number_from_result(
eval_single::<W, S>(base_expr, value.clone(), optional),
optional,
) {
Ok(n) => n,
Err(NumberError::None) => return QueryResult::None,
Err(NumberError::Error(e)) => return QueryResult::Error(e),
};
let exp = match get_number_from_result(eval_single::<W, S>(exp_expr, value, optional), optional)
{
Ok(n) => n,
Err(NumberError::None) => return QueryResult::None,
Err(NumberError::Error(e)) => return QueryResult::Error(e),
};
QueryResult::Owned(OwnedValue::Float(libm::pow(base, exp)))
}
fn builtin_sin<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
match get_float_value::<W>(&value, optional) {
Ok(n) => QueryResult::Owned(OwnedValue::Float(libm::sin(n))),
Err(r) => r,
}
}
fn builtin_cos<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
match get_float_value::<W>(&value, optional) {
Ok(n) => QueryResult::Owned(OwnedValue::Float(libm::cos(n))),
Err(r) => r,
}
}
fn builtin_tan<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
match get_float_value::<W>(&value, optional) {
Ok(n) => QueryResult::Owned(OwnedValue::Float(libm::tan(n))),
Err(r) => r,
}
}
fn builtin_asin<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
match get_float_value::<W>(&value, optional) {
Ok(n) => QueryResult::Owned(OwnedValue::Float(libm::asin(n))),
Err(r) => r,
}
}
fn builtin_acos<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
match get_float_value::<W>(&value, optional) {
Ok(n) => QueryResult::Owned(OwnedValue::Float(libm::acos(n))),
Err(r) => r,
}
}
fn builtin_atan<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
match get_float_value::<W>(&value, optional) {
Ok(n) => QueryResult::Owned(OwnedValue::Float(libm::atan(n))),
Err(r) => r,
}
}
fn builtin_atan2<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
y_expr: &Expr,
x_expr: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let y = match get_number_from_result(
eval_single::<W, S>(y_expr, value.clone(), optional),
optional,
) {
Ok(n) => n,
Err(NumberError::None) => return QueryResult::None,
Err(NumberError::Error(e)) => return QueryResult::Error(e),
};
let x = match get_number_from_result(eval_single::<W, S>(x_expr, value, optional), optional) {
Ok(n) => n,
Err(NumberError::None) => return QueryResult::None,
Err(NumberError::Error(e)) => return QueryResult::Error(e),
};
QueryResult::Owned(OwnedValue::Float(libm::atan2(y, x)))
}
fn builtin_sinh<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
match get_float_value::<W>(&value, optional) {
Ok(n) => QueryResult::Owned(OwnedValue::Float(libm::sinh(n))),
Err(r) => r,
}
}
fn builtin_cosh<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
match get_float_value::<W>(&value, optional) {
Ok(n) => QueryResult::Owned(OwnedValue::Float(libm::cosh(n))),
Err(r) => r,
}
}
fn builtin_tanh<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
match get_float_value::<W>(&value, optional) {
Ok(n) => QueryResult::Owned(OwnedValue::Float(libm::tanh(n))),
Err(r) => r,
}
}
fn builtin_asinh<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
match get_float_value::<W>(&value, optional) {
Ok(n) => QueryResult::Owned(OwnedValue::Float(libm::asinh(n))),
Err(r) => r,
}
}
fn builtin_acosh<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
match get_float_value::<W>(&value, optional) {
Ok(n) => QueryResult::Owned(OwnedValue::Float(libm::acosh(n))),
Err(r) => r,
}
}
fn builtin_atanh<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
match get_float_value::<W>(&value, optional) {
Ok(n) => QueryResult::Owned(OwnedValue::Float(libm::atanh(n))),
Err(r) => r,
}
}
fn builtin_isinfinite<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
_optional: bool,
) -> QueryResult<'a, W> {
match &value {
StandardJson::Number(n) => {
if let Ok(f) = n.as_f64() {
QueryResult::Owned(OwnedValue::Bool(f.is_infinite()))
} else {
QueryResult::Owned(OwnedValue::Bool(false))
}
}
_ => QueryResult::Owned(OwnedValue::Bool(false)),
}
}
fn builtin_isnan<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
_optional: bool,
) -> QueryResult<'a, W> {
match &value {
StandardJson::Number(n) => {
if let Ok(f) = n.as_f64() {
QueryResult::Owned(OwnedValue::Bool(f.is_nan()))
} else {
QueryResult::Owned(OwnedValue::Bool(false))
}
}
_ => QueryResult::Owned(OwnedValue::Bool(false)),
}
}
fn builtin_isnormal<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
_optional: bool,
) -> QueryResult<'a, W> {
match &value {
StandardJson::Number(n) => {
if let Ok(f) = n.as_f64() {
QueryResult::Owned(OwnedValue::Bool(f.is_normal()))
} else {
QueryResult::Owned(OwnedValue::Bool(false))
}
}
_ => QueryResult::Owned(OwnedValue::Bool(false)),
}
}
fn builtin_isfinite<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
match get_float_value::<W>(&value, optional) {
Ok(n) => QueryResult::Owned(OwnedValue::Bool(n.is_finite())),
Err(_) => QueryResult::Owned(OwnedValue::Bool(false)),
}
}
fn builtin_debug<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
_optional: bool,
) -> QueryResult<'a, W> {
QueryResult::Owned(to_owned(&value))
}
fn builtin_debug_msg<'a, W: Clone + AsRef<[u64]>>(
_msg: &Expr,
value: StandardJson<'a, W>,
_optional: bool,
) -> QueryResult<'a, W> {
QueryResult::Owned(to_owned(&value))
}
#[cfg(feature = "std")]
fn eval_env<'a, W: Clone + AsRef<[u64]>>(_optional: bool) -> QueryResult<'a, W> {
let mut env_obj = IndexMap::new();
for (key, value) in std::env::vars() {
env_obj.insert(key, OwnedValue::String(value));
}
QueryResult::Owned(OwnedValue::Object(env_obj))
}
#[cfg(not(feature = "std"))]
fn eval_env<'a, W: Clone + AsRef<[u64]>>(_optional: bool) -> QueryResult<'a, W> {
QueryResult::Owned(OwnedValue::Object(IndexMap::new()))
}
#[cfg(feature = "std")]
fn builtin_env<'a, W: Clone + AsRef<[u64]>>(
_value: StandardJson<'a, W>,
_optional: bool,
) -> QueryResult<'a, W> {
let mut env_obj = IndexMap::new();
for (key, value) in std::env::vars() {
env_obj.insert(key, OwnedValue::String(value));
}
QueryResult::Owned(OwnedValue::Object(env_obj))
}
#[cfg(not(feature = "std"))]
fn builtin_env<'a, W: Clone + AsRef<[u64]>>(
_value: StandardJson<'a, W>,
_optional: bool,
) -> QueryResult<'a, W> {
QueryResult::Owned(OwnedValue::Object(IndexMap::new()))
}
#[cfg(feature = "std")]
fn builtin_envvar<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
var: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let owned_value = to_owned(&value);
let var_result = eval_owned_expr::<S>(var, &owned_value, optional);
let var_name = match var_result {
Ok(OwnedValue::String(s)) => s,
_ if optional => return QueryResult::None,
_ => return QueryResult::Error(EvalError::new("env variable name must be a string")),
};
match std::env::var(&var_name) {
Ok(val) => QueryResult::Owned(OwnedValue::String(val)),
Err(_) if optional => QueryResult::None,
Err(_) => QueryResult::Owned(OwnedValue::Null),
}
}
#[cfg(not(feature = "std"))]
fn builtin_envvar<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
_var: &Expr,
_value: StandardJson<'a, W>,
_optional: bool,
) -> QueryResult<'a, W> {
QueryResult::Owned(OwnedValue::Null)
}
#[cfg(feature = "std")]
fn builtin_env_object<'a, W: Clone + AsRef<[u64]>>(
name: &str,
optional: bool,
) -> QueryResult<'a, W> {
match std::env::var(name) {
Ok(val) => QueryResult::Owned(OwnedValue::String(val)),
Err(_) if optional => QueryResult::None,
Err(_) => QueryResult::Error(EvalError::new(format!(
"value for env variable '{}' not provided in env()",
name
))),
}
}
#[cfg(not(feature = "std"))]
fn builtin_env_object<'a, W: Clone + AsRef<[u64]>>(
name: &str,
optional: bool,
) -> QueryResult<'a, W> {
if optional {
QueryResult::None
} else {
QueryResult::Error(EvalError::new(format!(
"value for env variable '{}' not provided in env() (no_std)",
name
)))
}
}
#[cfg(feature = "std")]
fn builtin_strenv<'a, W: Clone + AsRef<[u64]>>(name: &str, optional: bool) -> QueryResult<'a, W> {
match std::env::var(name) {
Ok(val) => QueryResult::Owned(OwnedValue::String(val)),
Err(_) if optional => QueryResult::None,
Err(_) => QueryResult::Error(EvalError::new(format!(
"value for env variable '{}' not provided in strenv()",
name
))),
}
}
#[cfg(not(feature = "std"))]
fn builtin_strenv<'a, W: Clone + AsRef<[u64]>>(name: &str, optional: bool) -> QueryResult<'a, W> {
if optional {
QueryResult::None
} else {
QueryResult::Error(EvalError::new(format!(
"value for env variable '{}' not provided in strenv() (no_std)",
name
)))
}
}
fn builtin_trim<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
match &value {
StandardJson::String(s) => {
if let Ok(cow) = s.as_str() {
QueryResult::Owned(OwnedValue::String(cow.trim().into()))
} else {
QueryResult::Owned(OwnedValue::String(String::new()))
}
}
_ if optional => QueryResult::None,
_ => QueryResult::Error(EvalError::new("trim requires string")),
}
}
fn builtin_ltrim<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
match &value {
StandardJson::String(s) => {
if let Ok(cow) = s.as_str() {
QueryResult::Owned(OwnedValue::String(cow.trim_start().into()))
} else {
QueryResult::Owned(OwnedValue::String(String::new()))
}
}
_ if optional => QueryResult::None,
_ => QueryResult::Error(EvalError::new("ltrim requires string")),
}
}
fn builtin_rtrim<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
match &value {
StandardJson::String(s) => {
if let Ok(cow) = s.as_str() {
QueryResult::Owned(OwnedValue::String(cow.trim_end().into()))
} else {
QueryResult::Owned(OwnedValue::String(String::new()))
}
}
_ if optional => QueryResult::None,
_ => QueryResult::Error(EvalError::new("rtrim requires string")),
}
}
fn builtin_transpose<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let elements = match value {
StandardJson::Array(a) => a,
_ if optional => return QueryResult::None,
_ => return QueryResult::Error(EvalError::new("transpose requires array")),
};
let mut inner_arrays: Vec<Vec<OwnedValue>> = Vec::new();
for item in elements {
match item {
StandardJson::Array(inner) => {
inner_arrays.push(inner.map(|v| to_owned(&v)).collect());
}
_ => {
inner_arrays.push(vec![to_owned(&item)]);
}
}
}
if inner_arrays.is_empty() {
return QueryResult::Owned(OwnedValue::Array(vec![]));
}
let max_len = inner_arrays.iter().map(|a| a.len()).max().unwrap_or(0);
let mut result = Vec::with_capacity(max_len);
for i in 0..max_len {
let mut row = Vec::new();
for inner in &inner_arrays {
if let Some(val) = inner.get(i) {
row.push(val.clone());
}
}
result.push(OwnedValue::Array(row));
}
QueryResult::Owned(OwnedValue::Array(result))
}
fn compare_owned_values(a: &OwnedValue, b: &OwnedValue) -> core::cmp::Ordering {
use core::cmp::Ordering;
match (a, b) {
(OwnedValue::Null, OwnedValue::Null) => Ordering::Equal,
(OwnedValue::Bool(x), OwnedValue::Bool(y)) => x.cmp(y),
(OwnedValue::Int(x), OwnedValue::Int(y)) => x.cmp(y),
(OwnedValue::Float(x), OwnedValue::Float(y)) => x.partial_cmp(y).unwrap_or(Ordering::Equal),
(OwnedValue::Int(x), OwnedValue::Float(y)) => {
(*x as f64).partial_cmp(y).unwrap_or(Ordering::Equal)
}
(OwnedValue::Float(x), OwnedValue::Int(y)) => {
x.partial_cmp(&(*y as f64)).unwrap_or(Ordering::Equal)
}
(OwnedValue::String(x), OwnedValue::String(y)) => x.cmp(y),
_ => {
let type_order = |v: &OwnedValue| match v {
OwnedValue::Null => 0,
OwnedValue::Bool(_) => 1,
OwnedValue::Int(_) | OwnedValue::Float(_) => 2,
OwnedValue::String(_) => 3,
OwnedValue::Array(_) => 4,
OwnedValue::Object(_) => 5,
};
type_order(a).cmp(&type_order(b))
}
}
}
fn builtin_bsearch<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
x_expr: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let elements_iter = match value.clone() {
StandardJson::Array(a) => a,
_ if optional => return QueryResult::None,
_ => return QueryResult::Error(EvalError::new("bsearch requires array")),
};
let x = match eval_single::<W, S>(x_expr, value, optional) {
QueryResult::One(v) => to_owned(&v),
QueryResult::Owned(v) => v,
QueryResult::Error(e) => return QueryResult::Error(e),
_ => return QueryResult::None,
};
let elements: Vec<OwnedValue> = elements_iter.map(|v| to_owned(&v)).collect();
match elements.binary_search_by(|probe| compare_owned_values(probe, &x)) {
Ok(idx) => QueryResult::Owned(OwnedValue::Int(idx as i64)),
Err(idx) => {
let mut obj = IndexMap::new();
obj.insert("index".to_string(), OwnedValue::Int(idx as i64));
QueryResult::Owned(OwnedValue::Object(obj))
}
}
}
fn builtin_modulemeta<'a, W: Clone + AsRef<[u64]>>(
_name: &Expr,
_value: StandardJson<'a, W>,
_optional: bool,
) -> QueryResult<'a, W> {
QueryResult::Owned(OwnedValue::Null)
}
fn builtin_pick<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
keys_expr: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let keys_owned = match eval_single::<W, S>(keys_expr, value.clone(), optional) {
QueryResult::One(v) => to_owned(&v),
QueryResult::OneCursor(c) => to_owned(&c.value()),
QueryResult::Owned(v) => v,
QueryResult::ManyOwned(v) if !v.is_empty() => v.into_iter().next().unwrap(),
QueryResult::ManyOwned(_) => {
return QueryResult::Error(EvalError::new("pick: keys expression produced no output"))
}
QueryResult::Error(e) => return QueryResult::Error(e),
QueryResult::Break(label) => return QueryResult::Break(label),
QueryResult::None if optional => return QueryResult::None,
QueryResult::None => {
return QueryResult::Error(EvalError::new("pick: keys expression produced no output"))
}
QueryResult::Many(v) if !v.is_empty() => to_owned(&v.into_iter().next().unwrap()),
QueryResult::Many(_) => {
return QueryResult::Error(EvalError::new("pick: keys expression produced no output"))
}
};
let keys = match &keys_owned {
OwnedValue::Array(arr) => arr,
_ if optional => return QueryResult::None,
_ => return QueryResult::Error(EvalError::new("pick: argument must be an array of keys")),
};
match &value {
StandardJson::Object(fields) => {
let mut result = IndexMap::new();
for key in keys {
if let OwnedValue::String(k) = key {
for field in *fields {
if let StandardJson::String(key_str) = field.key() {
if let Ok(cow) = key_str.as_str() {
if cow.as_ref() == k.as_str() {
result.insert(k.clone(), to_owned(&field.value()));
break;
}
}
}
}
}
}
QueryResult::Owned(OwnedValue::Object(result))
}
StandardJson::Array(elements) => {
let arr: Vec<_> = (*elements).collect();
let len = arr.len() as i64;
let mut result = Vec::new();
for key in keys {
let idx = match key {
OwnedValue::Int(i) => *i,
OwnedValue::Float(f) => *f as i64,
_ => continue, };
let actual_idx = if idx < 0 { len + idx } else { idx };
if actual_idx >= 0 && actual_idx < len {
result.push(to_owned(&arr[actual_idx as usize]));
}
}
QueryResult::Owned(OwnedValue::Array(result))
}
_ if optional => QueryResult::None,
_ => QueryResult::Error(EvalError::new("pick: input must be an object or array")),
}
}
fn builtin_omit<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
keys_expr: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let keys_owned = match eval_single::<W, S>(keys_expr, value.clone(), optional) {
QueryResult::One(v) => to_owned(&v),
QueryResult::OneCursor(c) => to_owned(&c.value()),
QueryResult::Owned(v) => v,
QueryResult::ManyOwned(v) if !v.is_empty() => v.into_iter().next().unwrap(),
QueryResult::ManyOwned(_) => {
return QueryResult::Error(EvalError::new("omit: keys expression produced no output"))
}
QueryResult::Error(e) => return QueryResult::Error(e),
QueryResult::Break(label) => return QueryResult::Break(label),
QueryResult::None if optional => return QueryResult::None,
QueryResult::None => {
return QueryResult::Error(EvalError::new("omit: keys expression produced no output"))
}
QueryResult::Many(v) if !v.is_empty() => to_owned(&v.into_iter().next().unwrap()),
QueryResult::Many(_) => {
return QueryResult::Error(EvalError::new("omit: keys expression produced no output"))
}
};
let keys = match &keys_owned {
OwnedValue::Array(arr) => arr,
_ if optional => return QueryResult::None,
_ => return QueryResult::Error(EvalError::new("omit: argument must be an array of keys")),
};
match &value {
StandardJson::Object(fields) => {
let omit_keys: Vec<&str> = keys
.iter()
.filter_map(|k| {
if let OwnedValue::String(s) = k {
Some(s.as_str())
} else {
None
}
})
.collect();
let mut result = IndexMap::new();
for field in *fields {
if let StandardJson::String(key_str) = field.key() {
if let Ok(cow) = key_str.as_str() {
let key = cow.as_ref();
if !omit_keys.contains(&key) {
result.insert(key.to_string(), to_owned(&field.value()));
}
}
}
}
QueryResult::Owned(OwnedValue::Object(result))
}
StandardJson::Array(elements) => {
let arr: Vec<_> = (*elements).collect();
let len = arr.len() as i64;
let omit_indices: Vec<usize> = keys
.iter()
.filter_map(|k| {
let idx = match k {
OwnedValue::Int(i) => *i,
OwnedValue::Float(f) => *f as i64,
_ => return None,
};
let actual_idx = if idx < 0 { len + idx } else { idx };
if actual_idx >= 0 && actual_idx < len {
Some(actual_idx as usize)
} else {
None }
})
.collect();
let result: Vec<OwnedValue> = arr
.iter()
.enumerate()
.filter_map(|(i, v)| {
if omit_indices.contains(&i) {
None
} else {
Some(to_owned(v))
}
})
.collect();
QueryResult::Owned(OwnedValue::Array(result))
}
_ if optional => QueryResult::None,
_ => QueryResult::Error(EvalError::new("omit: input must be an object or array")),
}
}
fn builtin_tag<'a, W: Clone + AsRef<[u64]>>(value: StandardJson<'a, W>) -> QueryResult<'a, W> {
let tag = match &value {
StandardJson::Null => "!!null",
StandardJson::Bool(_) => "!!bool",
StandardJson::Number(n) => {
if n.as_i64().is_ok() {
"!!int"
} else {
"!!float"
}
}
StandardJson::String(_) => "!!str",
StandardJson::Array(_) => "!!seq",
StandardJson::Object(_) => "!!map",
StandardJson::Error(_) => "!!null",
};
QueryResult::Owned(OwnedValue::String(tag.to_string()))
}
fn builtin_anchor<'a, W: Clone + AsRef<[u64]>>() -> QueryResult<'a, W> {
QueryResult::Owned(OwnedValue::String(String::new()))
}
fn builtin_style<'a, W: Clone + AsRef<[u64]>>(value: StandardJson<'a, W>) -> QueryResult<'a, W> {
let style = match &value {
StandardJson::Array(_) | StandardJson::Object(_) => "",
_ => "",
};
QueryResult::Owned(OwnedValue::String(style.to_string()))
}
fn builtin_kind<'a, W: Clone + AsRef<[u64]>>(value: StandardJson<'a, W>) -> QueryResult<'a, W> {
let kind = match &value {
StandardJson::Array(_) => "seq",
StandardJson::Object(_) => "map",
_ => "scalar",
};
QueryResult::Owned(OwnedValue::String(kind.to_string()))
}
fn builtin_line<'a, W: Clone + AsRef<[u64]>>() -> QueryResult<'a, W> {
QueryResult::Owned(OwnedValue::Int(0))
}
fn builtin_column<'a, W: Clone + AsRef<[u64]>>() -> QueryResult<'a, W> {
QueryResult::Owned(OwnedValue::Int(0))
}
fn builtin_document_index<'a, W: Clone + AsRef<[u64]>>() -> QueryResult<'a, W> {
QueryResult::Owned(OwnedValue::Int(0))
}
#[cfg(feature = "cli")]
fn builtin_shuffle<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
use rand::seq::SliceRandom;
use rand::SeedableRng;
use rand_chacha::ChaCha8Rng;
match value {
StandardJson::Array(elements) => {
let mut items: Vec<OwnedValue> = elements.map(|e| to_owned(&e)).collect();
let mut rng = ChaCha8Rng::from_entropy();
items.shuffle(&mut rng);
QueryResult::Owned(OwnedValue::Array(items))
}
_ if optional => QueryResult::None,
_ => QueryResult::Error(EvalError::type_error("array", type_name(&value))),
}
}
#[cfg(not(feature = "cli"))]
fn builtin_shuffle<'a, W: Clone + AsRef<[u64]>>(
_value: StandardJson<'a, W>,
_optional: bool,
) -> QueryResult<'a, W> {
QueryResult::Error(EvalError::new(
"shuffle requires the 'cli' feature to be enabled",
))
}
fn builtin_pivot<'a, W: Clone + AsRef<[u64]>>(
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
match value {
StandardJson::Array(elements) => {
let items: Vec<OwnedValue> = elements.map(|e| to_owned(&e)).collect();
if items.is_empty() {
return QueryResult::Owned(OwnedValue::Array(vec![]));
}
let all_arrays = items.iter().all(|v| matches!(v, OwnedValue::Array(_)));
let all_objects = items.iter().all(|v| matches!(v, OwnedValue::Object(_)));
if all_arrays {
pivot_arrays::<W>(&items)
} else if all_objects {
pivot_objects::<W>(&items)
} else {
if optional {
QueryResult::None
} else {
QueryResult::Error(EvalError::new(
"pivot requires array of arrays or array of objects",
))
}
}
}
_ if optional => QueryResult::None,
_ => QueryResult::Error(EvalError::type_error("array", type_name(&value))),
}
}
fn pivot_arrays<'a, W: Clone + AsRef<[u64]>>(items: &[OwnedValue]) -> QueryResult<'a, W> {
let max_len = items
.iter()
.filter_map(|v| {
if let OwnedValue::Array(arr) = v {
Some(arr.len())
} else {
None
}
})
.max()
.unwrap_or(0);
if max_len == 0 {
return QueryResult::Owned(OwnedValue::Array(vec![]));
}
let mut result = Vec::with_capacity(max_len);
for col_idx in 0..max_len {
let mut column = Vec::with_capacity(items.len());
for item in items {
if let OwnedValue::Array(arr) = item {
column.push(arr.get(col_idx).cloned().unwrap_or(OwnedValue::Null));
} else {
column.push(OwnedValue::Null);
}
}
result.push(OwnedValue::Array(column));
}
QueryResult::Owned(OwnedValue::Array(result))
}
fn pivot_objects<'a, W: Clone + AsRef<[u64]>>(items: &[OwnedValue]) -> QueryResult<'a, W> {
let mut all_keys: Vec<String> = Vec::new();
for item in items {
if let OwnedValue::Object(obj) = item {
for key in obj.keys() {
if !all_keys.contains(key) {
all_keys.push(key.clone());
}
}
}
}
let mut result = IndexMap::new();
for key in &all_keys {
let mut values = Vec::with_capacity(items.len());
for item in items {
if let OwnedValue::Object(obj) = item {
values.push(obj.get(key).cloned().unwrap_or(OwnedValue::Null));
} else {
values.push(OwnedValue::Null);
}
}
result.insert(key.clone(), OwnedValue::Array(values));
}
QueryResult::Owned(OwnedValue::Object(result))
}
fn eval_as_pattern<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
expr: &Expr,
pattern: &Pattern,
body: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let bound_result = eval_single::<W, S>(expr, value.clone(), optional);
let bound_values: Vec<OwnedValue> = match bound_result.materialize_cursor() {
QueryResult::One(v) => vec![to_owned(&v)],
QueryResult::OneCursor(_) => unreachable!(),
QueryResult::Many(vs) => vs.iter().map(to_owned).collect(),
QueryResult::Owned(v) => vec![v],
QueryResult::ManyOwned(vs) => vs,
QueryResult::None => return QueryResult::None,
QueryResult::Error(e) => return QueryResult::Error(e),
QueryResult::Break(label) => return QueryResult::Break(label),
};
let mut all_results: Vec<OwnedValue> = Vec::new();
for bound_val in bound_values {
let bindings = match extract_pattern_bindings(pattern, &bound_val) {
Ok(b) => b,
Err(e) => return QueryResult::Error(e),
};
let mut substituted_body = body.clone();
for (var_name, var_value) in &bindings {
substituted_body = substitute_var(&substituted_body, var_name, var_value);
}
match eval_single::<W, S>(&substituted_body, value.clone(), optional).materialize_cursor() {
QueryResult::One(v) => all_results.push(to_owned(&v)),
QueryResult::OneCursor(_) => unreachable!(),
QueryResult::Many(vs) => all_results.extend(vs.iter().map(to_owned)),
QueryResult::Owned(v) => all_results.push(v),
QueryResult::ManyOwned(vs) => all_results.extend(vs),
QueryResult::None => {}
QueryResult::Error(e) => return QueryResult::Error(e),
QueryResult::Break(label) => return QueryResult::Break(label),
}
}
if all_results.is_empty() {
QueryResult::None
} else if all_results.len() == 1 {
QueryResult::Owned(all_results.pop().unwrap())
} else {
QueryResult::ManyOwned(all_results)
}
}
fn extract_pattern_bindings(
pattern: &Pattern,
value: &OwnedValue,
) -> Result<Vec<(String, OwnedValue)>, EvalError> {
match pattern {
Pattern::Var(name) => Ok(vec![(name.clone(), value.clone())]),
Pattern::Object(entries) => {
let obj = match value {
OwnedValue::Object(o) => o,
_ => return Err(EvalError::type_error("object", owned_type_name(value))),
};
let mut bindings = Vec::new();
for entry in entries {
let field_value = obj.get(&entry.key).cloned().unwrap_or(OwnedValue::Null);
let sub_bindings = extract_pattern_bindings(&entry.pattern, &field_value)?;
bindings.extend(sub_bindings);
}
Ok(bindings)
}
Pattern::Array(patterns) => {
let arr = match value {
OwnedValue::Array(a) => a,
_ => return Err(EvalError::type_error("array", owned_type_name(value))),
};
let mut bindings = Vec::new();
for (i, pat) in patterns.iter().enumerate() {
let elem_value = arr.get(i).cloned().unwrap_or(OwnedValue::Null);
let sub_bindings = extract_pattern_bindings(pat, &elem_value)?;
bindings.extend(sub_bindings);
}
Ok(bindings)
}
}
}
fn eval_func_def<'a, W: Clone + AsRef<[u64]>, S: EvalSemantics>(
name: &str,
params: &[String],
body: &Expr,
then: &Expr,
value: StandardJson<'a, W>,
optional: bool,
) -> QueryResult<'a, W> {
let expanded_then = expand_func_calls(then, name, params, body);
eval_single::<W, S>(&expanded_then, value, optional)
}
fn expand_func_calls(expr: &Expr, func_name: &str, params: &[String], body: &Expr) -> Expr {
match expr {
Expr::FuncCall { name, args } if name == func_name => {
if args.len() != params.len() {
return Expr::Error(Some(Box::new(Expr::Literal(Literal::String(format!(
"function {} takes {} arguments, got {}",
func_name,
params.len(),
args.len()
))))));
}
let mut result = body.clone();
let expanded_args: Vec<Expr> = args
.iter()
.map(|a| expand_func_calls(a, func_name, params, body))
.collect();
for (param, arg) in params.iter().zip(expanded_args.iter()) {
result = substitute_func_param(&result, param, arg);
}
expand_func_calls(&result, func_name, params, body)
}
Expr::Identity => Expr::Identity,
Expr::Field(s) => Expr::Field(s.clone()),
Expr::Index(i) => Expr::Index(*i),
Expr::Slice { start, end } => Expr::Slice {
start: *start,
end: *end,
},
Expr::Iterate => Expr::Iterate,
Expr::RecursiveDescent => Expr::RecursiveDescent,
Expr::Optional(e) => {
Expr::Optional(Box::new(expand_func_calls(e, func_name, params, body)))
}
Expr::Pipe(exprs) => Expr::Pipe(
exprs
.iter()
.map(|e| expand_func_calls(e, func_name, params, body))
.collect(),
),
Expr::Comma(exprs) => Expr::Comma(
exprs
.iter()
.map(|e| expand_func_calls(e, func_name, params, body))
.collect(),
),
Expr::Array(e) => Expr::Array(Box::new(expand_func_calls(e, func_name, params, body))),
Expr::Object(entries) => Expr::Object(
entries
.iter()
.map(|entry| {
let new_key = match &entry.key {
ObjectKey::Literal(s) => ObjectKey::Literal(s.clone()),
ObjectKey::Expr(e) => {
ObjectKey::Expr(Box::new(expand_func_calls(e, func_name, params, body)))
}
};
ObjectEntry {
key: new_key,
value: expand_func_calls(&entry.value, func_name, params, body),
}
})
.collect(),
),
Expr::Literal(lit) => Expr::Literal(lit.clone()),
Expr::Paren(e) => Expr::Paren(Box::new(expand_func_calls(e, func_name, params, body))),
Expr::Arithmetic { op, left, right } => Expr::Arithmetic {
op: *op,
left: Box::new(expand_func_calls(left, func_name, params, body)),
right: Box::new(expand_func_calls(right, func_name, params, body)),
},
Expr::Compare { op, left, right } => Expr::Compare {
op: *op,
left: Box::new(expand_func_calls(left, func_name, params, body)),
right: Box::new(expand_func_calls(right, func_name, params, body)),
},
Expr::And(l, r) => Expr::And(
Box::new(expand_func_calls(l, func_name, params, body)),
Box::new(expand_func_calls(r, func_name, params, body)),
),
Expr::Or(l, r) => Expr::Or(
Box::new(expand_func_calls(l, func_name, params, body)),
Box::new(expand_func_calls(r, func_name, params, body)),
),
Expr::Not => Expr::Not,
Expr::Alternative(l, r) => Expr::Alternative(
Box::new(expand_func_calls(l, func_name, params, body)),
Box::new(expand_func_calls(r, func_name, params, body)),
),
Expr::If {
cond,
then_branch,
else_branch,
} => Expr::If {
cond: Box::new(expand_func_calls(cond, func_name, params, body)),
then_branch: Box::new(expand_func_calls(then_branch, func_name, params, body)),
else_branch: Box::new(expand_func_calls(else_branch, func_name, params, body)),
},
Expr::Try { expr, catch } => Expr::Try {
expr: Box::new(expand_func_calls(expr, func_name, params, body)),
catch: catch
.as_ref()
.map(|c| Box::new(expand_func_calls(c, func_name, params, body))),
},
Expr::Error(msg) => Expr::Error(msg.clone()),
Expr::Builtin(b) => Expr::Builtin(expand_func_calls_in_builtin(b, func_name, params, body)),
Expr::StringInterpolation(parts) => Expr::StringInterpolation(
parts
.iter()
.map(|p| match p {
StringPart::Literal(s) => StringPart::Literal(s.clone()),
StringPart::Expr(e) => {
StringPart::Expr(Box::new(expand_func_calls(e, func_name, params, body)))
}
})
.collect(),
),
Expr::Format(f) => Expr::Format(f.clone()),
Expr::Var(v) => Expr::Var(v.clone()),
Expr::Loc { line } => Expr::Loc { line: *line },
Expr::Env => Expr::Env,
Expr::As {
expr,
var,
body: as_body,
} => Expr::As {
expr: Box::new(expand_func_calls(expr, func_name, params, body)),
var: var.clone(),
body: Box::new(expand_func_calls(as_body, func_name, params, body)),
},
Expr::Reduce {
input,
var,
init,
update,
} => Expr::Reduce {
input: Box::new(expand_func_calls(input, func_name, params, body)),
var: var.clone(),
init: Box::new(expand_func_calls(init, func_name, params, body)),
update: Box::new(expand_func_calls(update, func_name, params, body)),
},
Expr::Foreach {
input,
var,
init,
update,
extract,
} => Expr::Foreach {
input: Box::new(expand_func_calls(input, func_name, params, body)),
var: var.clone(),
init: Box::new(expand_func_calls(init, func_name, params, body)),
update: Box::new(expand_func_calls(update, func_name, params, body)),
extract: extract
.as_ref()
.map(|e| Box::new(expand_func_calls(e, func_name, params, body))),
},
Expr::Limit { n, expr } => Expr::Limit {
n: Box::new(expand_func_calls(n, func_name, params, body)),
expr: Box::new(expand_func_calls(expr, func_name, params, body)),
},
Expr::FirstExpr(e) => {
Expr::FirstExpr(Box::new(expand_func_calls(e, func_name, params, body)))
}
Expr::LastExpr(e) => {
Expr::LastExpr(Box::new(expand_func_calls(e, func_name, params, body)))
}
Expr::NthExpr { n, expr } => Expr::NthExpr {
n: Box::new(expand_func_calls(n, func_name, params, body)),
expr: Box::new(expand_func_calls(expr, func_name, params, body)),
},
Expr::Until { cond, update } => Expr::Until {
cond: Box::new(expand_func_calls(cond, func_name, params, body)),
update: Box::new(expand_func_calls(update, func_name, params, body)),
},
Expr::While { cond, update } => Expr::While {
cond: Box::new(expand_func_calls(cond, func_name, params, body)),
update: Box::new(expand_func_calls(update, func_name, params, body)),
},
Expr::Repeat(e) => Expr::Repeat(Box::new(expand_func_calls(e, func_name, params, body))),
Expr::Range { from, to, step } => Expr::Range {
from: Box::new(expand_func_calls(from, func_name, params, body)),
to: to
.as_ref()
.map(|e| Box::new(expand_func_calls(e, func_name, params, body))),
step: step
.as_ref()
.map(|e| Box::new(expand_func_calls(e, func_name, params, body))),
},
Expr::AsPattern {
expr,
pattern,
body: pattern_body,
} => Expr::AsPattern {
expr: Box::new(expand_func_calls(expr, func_name, params, body)),
pattern: pattern.clone(),
body: Box::new(expand_func_calls(pattern_body, func_name, params, body)),
},
Expr::FuncDef {
name: inner_name,
params: inner_params,
body: inner_body,
then,
} => {
if inner_name == func_name {
expr.clone()
} else {
Expr::FuncDef {
name: inner_name.clone(),
params: inner_params.clone(),
body: Box::new(expand_func_calls(inner_body, func_name, params, body)),
then: Box::new(expand_func_calls(then, func_name, params, body)),
}
}
}
Expr::FuncCall { name, args } => {
Expr::FuncCall {
name: name.clone(),
args: args
.iter()
.map(|a| expand_func_calls(a, func_name, params, body))
.collect(),
}
}
Expr::NamespacedCall {
namespace,
name,
args,
} => Expr::NamespacedCall {
namespace: namespace.clone(),
name: name.clone(),
args: args
.iter()
.map(|a| expand_func_calls(a, func_name, params, body))
.collect(),
},
Expr::Assign { path, value } => Expr::Assign {
path: Box::new(expand_func_calls(path, func_name, params, body)),
value: Box::new(expand_func_calls(value, func_name, params, body)),
},
Expr::Update { path, filter } => Expr::Update {
path: Box::new(expand_func_calls(path, func_name, params, body)),
filter: Box::new(expand_func_calls(filter, func_name, params, body)),
},
Expr::CompoundAssign { op, path, value } => Expr::CompoundAssign {
op: *op,
path: Box::new(expand_func_calls(path, func_name, params, body)),
value: Box::new(expand_func_calls(value, func_name, params, body)),
},
Expr::AlternativeAssign { path, value } => Expr::AlternativeAssign {
path: Box::new(expand_func_calls(path, func_name, params, body)),
value: Box::new(expand_func_calls(value, func_name, params, body)),
},
Expr::Label { name, body: lbody } => Expr::Label {
name: name.clone(),
body: Box::new(expand_func_calls(lbody, func_name, params, body)),
},
Expr::Break(name) => Expr::Break(name.clone()),
}
}
fn substitute_func_param(expr: &Expr, param: &str, arg: &Expr) -> Expr {
match expr {
Expr::Var(name) if name == param => arg.clone(),
Expr::Var(_) => expr.clone(),
Expr::Loc { line } => Expr::Loc { line: *line },
Expr::Env => Expr::Env,
Expr::Identity => Expr::Identity,
Expr::Field(name) => Expr::Field(name.clone()),
Expr::Index(i) => Expr::Index(*i),
Expr::Slice { start, end } => Expr::Slice {
start: *start,
end: *end,
},
Expr::Iterate => Expr::Iterate,
Expr::RecursiveDescent => Expr::RecursiveDescent,
Expr::Optional(e) => Expr::Optional(Box::new(substitute_func_param(e, param, arg))),
Expr::Pipe(exprs) => Expr::Pipe(
exprs
.iter()
.map(|e| substitute_func_param(e, param, arg))
.collect(),
),
Expr::Comma(exprs) => Expr::Comma(
exprs
.iter()
.map(|e| substitute_func_param(e, param, arg))
.collect(),
),
Expr::Array(e) => Expr::Array(Box::new(substitute_func_param(e, param, arg))),
Expr::Object(entries) => Expr::Object(
entries
.iter()
.map(|entry| {
let new_key = match &entry.key {
ObjectKey::Literal(s) => ObjectKey::Literal(s.clone()),
ObjectKey::Expr(e) => {
ObjectKey::Expr(Box::new(substitute_func_param(e, param, arg)))
}
};
ObjectEntry {
key: new_key,
value: substitute_func_param(&entry.value, param, arg),
}
})
.collect(),
),
Expr::Literal(lit) => Expr::Literal(lit.clone()),
Expr::Paren(e) => Expr::Paren(Box::new(substitute_func_param(e, param, arg))),
Expr::Arithmetic { op, left, right } => Expr::Arithmetic {
op: *op,
left: Box::new(substitute_func_param(left, param, arg)),
right: Box::new(substitute_func_param(right, param, arg)),
},
Expr::Compare { op, left, right } => Expr::Compare {
op: *op,
left: Box::new(substitute_func_param(left, param, arg)),
right: Box::new(substitute_func_param(right, param, arg)),
},
Expr::And(l, r) => Expr::And(
Box::new(substitute_func_param(l, param, arg)),
Box::new(substitute_func_param(r, param, arg)),
),
Expr::Or(l, r) => Expr::Or(
Box::new(substitute_func_param(l, param, arg)),
Box::new(substitute_func_param(r, param, arg)),
),
Expr::Not => Expr::Not,
Expr::Alternative(l, r) => Expr::Alternative(
Box::new(substitute_func_param(l, param, arg)),
Box::new(substitute_func_param(r, param, arg)),
),
Expr::If {
cond,
then_branch,
else_branch,
} => Expr::If {
cond: Box::new(substitute_func_param(cond, param, arg)),
then_branch: Box::new(substitute_func_param(then_branch, param, arg)),
else_branch: Box::new(substitute_func_param(else_branch, param, arg)),
},
Expr::Try { expr, catch } => Expr::Try {
expr: Box::new(substitute_func_param(expr, param, arg)),
catch: catch
.as_ref()
.map(|c| Box::new(substitute_func_param(c, param, arg))),
},
Expr::Error(msg) => Expr::Error(msg.clone()),
Expr::Builtin(b) => Expr::Builtin(substitute_func_param_in_builtin(b, param, arg)),
Expr::StringInterpolation(parts) => Expr::StringInterpolation(
parts
.iter()
.map(|p| match p {
StringPart::Literal(s) => StringPart::Literal(s.clone()),
StringPart::Expr(e) => {
StringPart::Expr(Box::new(substitute_func_param(e, param, arg)))
}
})
.collect(),
),
Expr::Format(f) => Expr::Format(f.clone()),
Expr::As { expr, var, body } => {
if var == param {
Expr::As {
expr: Box::new(substitute_func_param(expr, param, arg)),
var: var.clone(),
body: body.clone(),
}
} else {
Expr::As {
expr: Box::new(substitute_func_param(expr, param, arg)),
var: var.clone(),
body: Box::new(substitute_func_param(body, param, arg)),
}
}
}
Expr::Reduce {
input,
var,
init,
update,
} => {
if var == param {
Expr::Reduce {
input: Box::new(substitute_func_param(input, param, arg)),
var: var.clone(),
init: Box::new(substitute_func_param(init, param, arg)),
update: update.clone(),
}
} else {
Expr::Reduce {
input: Box::new(substitute_func_param(input, param, arg)),
var: var.clone(),
init: Box::new(substitute_func_param(init, param, arg)),
update: Box::new(substitute_func_param(update, param, arg)),
}
}
}
Expr::Foreach {
input,
var,
init,
update,
extract,
} => {
if var == param {
Expr::Foreach {
input: Box::new(substitute_func_param(input, param, arg)),
var: var.clone(),
init: Box::new(substitute_func_param(init, param, arg)),
update: update.clone(),
extract: extract.clone(),
}
} else {
Expr::Foreach {
input: Box::new(substitute_func_param(input, param, arg)),
var: var.clone(),
init: Box::new(substitute_func_param(init, param, arg)),
update: Box::new(substitute_func_param(update, param, arg)),
extract: extract
.as_ref()
.map(|e| Box::new(substitute_func_param(e, param, arg))),
}
}
}
Expr::Limit { n, expr } => Expr::Limit {
n: Box::new(substitute_func_param(n, param, arg)),
expr: Box::new(substitute_func_param(expr, param, arg)),
},
Expr::FirstExpr(e) => Expr::FirstExpr(Box::new(substitute_func_param(e, param, arg))),
Expr::LastExpr(e) => Expr::LastExpr(Box::new(substitute_func_param(e, param, arg))),
Expr::NthExpr { n, expr } => Expr::NthExpr {
n: Box::new(substitute_func_param(n, param, arg)),
expr: Box::new(substitute_func_param(expr, param, arg)),
},
Expr::Until { cond, update } => Expr::Until {
cond: Box::new(substitute_func_param(cond, param, arg)),
update: Box::new(substitute_func_param(update, param, arg)),
},
Expr::While { cond, update } => Expr::While {
cond: Box::new(substitute_func_param(cond, param, arg)),
update: Box::new(substitute_func_param(update, param, arg)),
},
Expr::Repeat(e) => Expr::Repeat(Box::new(substitute_func_param(e, param, arg))),
Expr::Range { from, to, step } => Expr::Range {
from: Box::new(substitute_func_param(from, param, arg)),
to: to
.as_ref()
.map(|e| Box::new(substitute_func_param(e, param, arg))),
step: step
.as_ref()
.map(|e| Box::new(substitute_func_param(e, param, arg))),
},
Expr::AsPattern {
expr,
pattern,
body,
} => {
let shadowed = pattern_binds_var(pattern, param);
Expr::AsPattern {
expr: Box::new(substitute_func_param(expr, param, arg)),
pattern: pattern.clone(),
body: if shadowed {
body.clone()
} else {
Box::new(substitute_func_param(body, param, arg))
},
}
}
Expr::FuncDef {
name,
params,
body,
then,
} => {
let shadowed = params.contains(¶m.to_string());
Expr::FuncDef {
name: name.clone(),
params: params.clone(),
body: if shadowed {
body.clone()
} else {
Box::new(substitute_func_param(body, param, arg))
},
then: Box::new(substitute_func_param(then, param, arg)),
}
}
Expr::FuncCall { name, args } => {
if name == param && args.is_empty() {
arg.clone()
} else {
Expr::FuncCall {
name: name.clone(),
args: args
.iter()
.map(|a| substitute_func_param(a, param, arg))
.collect(),
}
}
}
Expr::NamespacedCall {
namespace,
name,
args,
} => Expr::NamespacedCall {
namespace: namespace.clone(),
name: name.clone(),
args: args
.iter()
.map(|a| substitute_func_param(a, param, arg))
.collect(),
},
Expr::Assign { path, value } => Expr::Assign {
path: Box::new(substitute_func_param(path, param, arg)),
value: Box::new(substitute_func_param(value, param, arg)),
},
Expr::Update { path, filter } => Expr::Update {
path: Box::new(substitute_func_param(path, param, arg)),
filter: Box::new(substitute_func_param(filter, param, arg)),
},
Expr::CompoundAssign { op, path, value } => Expr::CompoundAssign {
op: *op,
path: Box::new(substitute_func_param(path, param, arg)),
value: Box::new(substitute_func_param(value, param, arg)),
},
Expr::AlternativeAssign { path, value } => Expr::AlternativeAssign {
path: Box::new(substitute_func_param(path, param, arg)),
value: Box::new(substitute_func_param(value, param, arg)),
},
Expr::Label { name, body } => Expr::Label {
name: name.clone(),
body: Box::new(substitute_func_param(body, param, arg)),
},
Expr::Break(name) => Expr::Break(name.clone()),
}
}
fn expand_func_calls_in_builtin(
builtin: &Builtin,
func_name: &str,
params: &[String],
body: &Expr,
) -> Builtin {
match builtin {
Builtin::Type => Builtin::Type,
Builtin::IsNull => Builtin::IsNull,
Builtin::IsBoolean => Builtin::IsBoolean,
Builtin::IsNumber => Builtin::IsNumber,
Builtin::IsString => Builtin::IsString,
Builtin::IsArray => Builtin::IsArray,
Builtin::IsObject => Builtin::IsObject,
Builtin::Values => Builtin::Values,
Builtin::Nulls => Builtin::Nulls,
Builtin::Booleans => Builtin::Booleans,
Builtin::Numbers => Builtin::Numbers,
Builtin::Strings => Builtin::Strings,
Builtin::Arrays => Builtin::Arrays,
Builtin::Objects => Builtin::Objects,
Builtin::Iterables => Builtin::Iterables,
Builtin::Scalars => Builtin::Scalars,
Builtin::Length => Builtin::Length,
Builtin::Utf8ByteLength => Builtin::Utf8ByteLength,
Builtin::Keys => Builtin::Keys,
Builtin::KeysUnsorted => Builtin::KeysUnsorted,
Builtin::Has(e) => Builtin::Has(Box::new(expand_func_calls(e, func_name, params, body))),
Builtin::In(e) => Builtin::In(Box::new(expand_func_calls(e, func_name, params, body))),
Builtin::Select(e) => {
Builtin::Select(Box::new(expand_func_calls(e, func_name, params, body)))
}
Builtin::Empty => Builtin::Empty,
Builtin::Map(e) => Builtin::Map(Box::new(expand_func_calls(e, func_name, params, body))),
Builtin::MapValues(e) => {
Builtin::MapValues(Box::new(expand_func_calls(e, func_name, params, body)))
}
Builtin::Add => Builtin::Add,
Builtin::Any => Builtin::Any,
Builtin::All => Builtin::All,
Builtin::Min => Builtin::Min,
Builtin::Max => Builtin::Max,
Builtin::MinBy(e) => {
Builtin::MinBy(Box::new(expand_func_calls(e, func_name, params, body)))
}
Builtin::MaxBy(e) => {
Builtin::MaxBy(Box::new(expand_func_calls(e, func_name, params, body)))
}
Builtin::AsciiDowncase => Builtin::AsciiDowncase,
Builtin::AsciiUpcase => Builtin::AsciiUpcase,
Builtin::Ltrimstr(e) => {
Builtin::Ltrimstr(Box::new(expand_func_calls(e, func_name, params, body)))
}
Builtin::Rtrimstr(e) => {
Builtin::Rtrimstr(Box::new(expand_func_calls(e, func_name, params, body)))
}
Builtin::Startswith(e) => {
Builtin::Startswith(Box::new(expand_func_calls(e, func_name, params, body)))
}
Builtin::Endswith(e) => {
Builtin::Endswith(Box::new(expand_func_calls(e, func_name, params, body)))
}
Builtin::Split(e) => {
Builtin::Split(Box::new(expand_func_calls(e, func_name, params, body)))
}
Builtin::Join(e) => Builtin::Join(Box::new(expand_func_calls(e, func_name, params, body))),
Builtin::Contains(e) => {
Builtin::Contains(Box::new(expand_func_calls(e, func_name, params, body)))
}
Builtin::Inside(e) => {
Builtin::Inside(Box::new(expand_func_calls(e, func_name, params, body)))
}
Builtin::First => Builtin::First,
Builtin::Last => Builtin::Last,
Builtin::Nth(e) => Builtin::Nth(Box::new(expand_func_calls(e, func_name, params, body))),
Builtin::Reverse => Builtin::Reverse,
Builtin::Flatten => Builtin::Flatten,
Builtin::FlattenDepth(e) => {
Builtin::FlattenDepth(Box::new(expand_func_calls(e, func_name, params, body)))
}
Builtin::GroupBy(e) => {
Builtin::GroupBy(Box::new(expand_func_calls(e, func_name, params, body)))
}
Builtin::Unique => Builtin::Unique,
Builtin::UniqueBy(e) => {
Builtin::UniqueBy(Box::new(expand_func_calls(e, func_name, params, body)))
}
Builtin::Sort => Builtin::Sort,
Builtin::SortBy(e) => {
Builtin::SortBy(Box::new(expand_func_calls(e, func_name, params, body)))
}
Builtin::ToEntries => Builtin::ToEntries,
Builtin::FromEntries => Builtin::FromEntries,
Builtin::WithEntries(e) => {
Builtin::WithEntries(Box::new(expand_func_calls(e, func_name, params, body)))
}
Builtin::ToString => Builtin::ToString,
Builtin::ToNumber => Builtin::ToNumber,
Builtin::ToJson => Builtin::ToJson,
Builtin::FromJson => Builtin::FromJson,
Builtin::Explode => Builtin::Explode,
Builtin::Implode => Builtin::Implode,
Builtin::Test(e) => Builtin::Test(Box::new(expand_func_calls(e, func_name, params, body))),
Builtin::Indices(e) => {
Builtin::Indices(Box::new(expand_func_calls(e, func_name, params, body)))
}
Builtin::Index(e) => {
Builtin::Index(Box::new(expand_func_calls(e, func_name, params, body)))
}
Builtin::Rindex(e) => {
Builtin::Rindex(Box::new(expand_func_calls(e, func_name, params, body)))
}
Builtin::ToJsonStream => Builtin::ToJsonStream,
Builtin::FromJsonStream => Builtin::FromJsonStream,
Builtin::GetPath(e) => {
Builtin::GetPath(Box::new(expand_func_calls(e, func_name, params, body)))
}
Builtin::TestFlags(re, flags) => Builtin::TestFlags(
Box::new(expand_func_calls(re, func_name, params, body)),
Box::new(expand_func_calls(flags, func_name, params, body)),
),
Builtin::Match(re) => {
Builtin::Match(Box::new(expand_func_calls(re, func_name, params, body)))
}
Builtin::MatchFlags(re, flags) => Builtin::MatchFlags(
Box::new(expand_func_calls(re, func_name, params, body)),
Box::new(expand_func_calls(flags, func_name, params, body)),
),
Builtin::Capture(e) => {
Builtin::Capture(Box::new(expand_func_calls(e, func_name, params, body)))
}
Builtin::CaptureFlags(re, flags) => Builtin::CaptureFlags(
Box::new(expand_func_calls(re, func_name, params, body)),
Box::new(expand_func_calls(flags, func_name, params, body)),
),
Builtin::Sub(re, repl) => Builtin::Sub(
Box::new(expand_func_calls(re, func_name, params, body)),
Box::new(expand_func_calls(repl, func_name, params, body)),
),
Builtin::SubFlags(re, repl, flags) => Builtin::SubFlags(
Box::new(expand_func_calls(re, func_name, params, body)),
Box::new(expand_func_calls(repl, func_name, params, body)),
Box::new(expand_func_calls(flags, func_name, params, body)),
),
Builtin::Gsub(re, repl) => Builtin::Gsub(
Box::new(expand_func_calls(re, func_name, params, body)),
Box::new(expand_func_calls(repl, func_name, params, body)),
),
Builtin::GsubFlags(re, repl, flags) => Builtin::GsubFlags(
Box::new(expand_func_calls(re, func_name, params, body)),
Box::new(expand_func_calls(repl, func_name, params, body)),
Box::new(expand_func_calls(flags, func_name, params, body)),
),
Builtin::Scan(re) => {
Builtin::Scan(Box::new(expand_func_calls(re, func_name, params, body)))
}
Builtin::ScanFlags(re, flags) => Builtin::ScanFlags(
Box::new(expand_func_calls(re, func_name, params, body)),
Box::new(expand_func_calls(flags, func_name, params, body)),
),
Builtin::SplitRegex(re, flags) => Builtin::SplitRegex(
Box::new(expand_func_calls(re, func_name, params, body)),
Box::new(expand_func_calls(flags, func_name, params, body)),
),
Builtin::Splits(re) => {
Builtin::Splits(Box::new(expand_func_calls(re, func_name, params, body)))
}
Builtin::SplitsFlags(re, flags) => Builtin::SplitsFlags(
Box::new(expand_func_calls(re, func_name, params, body)),
Box::new(expand_func_calls(flags, func_name, params, body)),
),
Builtin::Recurse => Builtin::Recurse,
Builtin::RecurseF(f) => {
Builtin::RecurseF(Box::new(expand_func_calls(f, func_name, params, body)))
}
Builtin::RecurseCond(f, c) => Builtin::RecurseCond(
Box::new(expand_func_calls(f, func_name, params, body)),
Box::new(expand_func_calls(c, func_name, params, body)),
),
Builtin::Walk(f) => Builtin::Walk(Box::new(expand_func_calls(f, func_name, params, body))),
Builtin::IsValid(e) => {
Builtin::IsValid(Box::new(expand_func_calls(e, func_name, params, body)))
}
Builtin::Path(e) => Builtin::Path(Box::new(expand_func_calls(e, func_name, params, body))),
Builtin::PathNoArg => Builtin::PathNoArg,
Builtin::Parent => Builtin::Parent,
Builtin::ParentN(e) => {
Builtin::ParentN(Box::new(expand_func_calls(e, func_name, params, body)))
}
Builtin::Paths => Builtin::Paths,
Builtin::PathsFilter(e) => {
Builtin::PathsFilter(Box::new(expand_func_calls(e, func_name, params, body)))
}
Builtin::LeafPaths => Builtin::LeafPaths,
Builtin::SetPath(p, v) => Builtin::SetPath(
Box::new(expand_func_calls(p, func_name, params, body)),
Box::new(expand_func_calls(v, func_name, params, body)),
),
Builtin::DelPaths(e) => {
Builtin::DelPaths(Box::new(expand_func_calls(e, func_name, params, body)))
}
Builtin::Floor => Builtin::Floor,
Builtin::Ceil => Builtin::Ceil,
Builtin::Round => Builtin::Round,
Builtin::Sqrt => Builtin::Sqrt,
Builtin::Fabs => Builtin::Fabs,
Builtin::Log => Builtin::Log,
Builtin::Log10 => Builtin::Log10,
Builtin::Log2 => Builtin::Log2,
Builtin::Exp => Builtin::Exp,
Builtin::Exp10 => Builtin::Exp10,
Builtin::Exp2 => Builtin::Exp2,
Builtin::Pow(x, y) => Builtin::Pow(
Box::new(expand_func_calls(x, func_name, params, body)),
Box::new(expand_func_calls(y, func_name, params, body)),
),
Builtin::Sin => Builtin::Sin,
Builtin::Cos => Builtin::Cos,
Builtin::Tan => Builtin::Tan,
Builtin::Asin => Builtin::Asin,
Builtin::Acos => Builtin::Acos,
Builtin::Atan => Builtin::Atan,
Builtin::Atan2(x, y) => Builtin::Atan2(
Box::new(expand_func_calls(x, func_name, params, body)),
Box::new(expand_func_calls(y, func_name, params, body)),
),
Builtin::Sinh => Builtin::Sinh,
Builtin::Cosh => Builtin::Cosh,
Builtin::Tanh => Builtin::Tanh,
Builtin::Asinh => Builtin::Asinh,
Builtin::Acosh => Builtin::Acosh,
Builtin::Atanh => Builtin::Atanh,
Builtin::Infinite => Builtin::Infinite,
Builtin::Nan => Builtin::Nan,
Builtin::IsInfinite => Builtin::IsInfinite,
Builtin::IsNan => Builtin::IsNan,
Builtin::IsNormal => Builtin::IsNormal,
Builtin::IsFinite => Builtin::IsFinite,
Builtin::Debug => Builtin::Debug,
Builtin::DebugMsg(e) => {
Builtin::DebugMsg(Box::new(expand_func_calls(e, func_name, params, body)))
}
Builtin::Env => Builtin::Env,
Builtin::EnvVar(e) => {
Builtin::EnvVar(Box::new(expand_func_calls(e, func_name, params, body)))
}
Builtin::EnvObject(name) => Builtin::EnvObject(name.clone()),
Builtin::StrEnv(name) => Builtin::StrEnv(name.clone()),
Builtin::NullLit => Builtin::NullLit,
Builtin::Trim => Builtin::Trim,
Builtin::Ltrim => Builtin::Ltrim,
Builtin::Rtrim => Builtin::Rtrim,
Builtin::Transpose => Builtin::Transpose,
Builtin::BSearch(e) => {
Builtin::BSearch(Box::new(expand_func_calls(e, func_name, params, body)))
}
Builtin::ModuleMeta(e) => {
Builtin::ModuleMeta(Box::new(expand_func_calls(e, func_name, params, body)))
}
Builtin::Pick(e) => Builtin::Pick(Box::new(expand_func_calls(e, func_name, params, body))),
Builtin::Omit(e) => Builtin::Omit(Box::new(expand_func_calls(e, func_name, params, body))),
Builtin::Tag => Builtin::Tag,
Builtin::Anchor => Builtin::Anchor,
Builtin::Style => Builtin::Style,
Builtin::Kind => Builtin::Kind,
Builtin::Key => Builtin::Key,
Builtin::Line => Builtin::Line,
Builtin::Column => Builtin::Column,
Builtin::DocumentIndex => Builtin::DocumentIndex,
Builtin::Shuffle => Builtin::Shuffle,
Builtin::Pivot => Builtin::Pivot,
Builtin::SplitDoc => Builtin::SplitDoc,
Builtin::Del(e) => Builtin::Del(Box::new(expand_func_calls(e, func_name, params, body))),
Builtin::Now => Builtin::Now,
Builtin::Abs => Builtin::Abs,
Builtin::Builtins => Builtin::Builtins,
Builtin::Normals => Builtin::Normals,
Builtin::Finites => Builtin::Finites,
Builtin::Limit(n, e) => Builtin::Limit(
Box::new(expand_func_calls(n, func_name, params, body)),
Box::new(expand_func_calls(e, func_name, params, body)),
),
Builtin::FirstStream(e) => {
Builtin::FirstStream(Box::new(expand_func_calls(e, func_name, params, body)))
}
Builtin::LastStream(e) => {
Builtin::LastStream(Box::new(expand_func_calls(e, func_name, params, body)))
}
Builtin::NthStream(n, e) => Builtin::NthStream(
Box::new(expand_func_calls(n, func_name, params, body)),
Box::new(expand_func_calls(e, func_name, params, body)),
),
Builtin::Range(n) => {
Builtin::Range(Box::new(expand_func_calls(n, func_name, params, body)))
}
Builtin::RangeFromTo(from, to) => Builtin::RangeFromTo(
Box::new(expand_func_calls(from, func_name, params, body)),
Box::new(expand_func_calls(to, func_name, params, body)),
),
Builtin::RangeFromToBy(from, to, by) => Builtin::RangeFromToBy(
Box::new(expand_func_calls(from, func_name, params, body)),
Box::new(expand_func_calls(to, func_name, params, body)),
Box::new(expand_func_calls(by, func_name, params, body)),
),
Builtin::IsEmpty(e) => {
Builtin::IsEmpty(Box::new(expand_func_calls(e, func_name, params, body)))
}
Builtin::RecurseDown => Builtin::RecurseDown,
Builtin::Gmtime => Builtin::Gmtime,
Builtin::Localtime => Builtin::Localtime,
Builtin::Mktime => Builtin::Mktime,
Builtin::Strftime(e) => {
Builtin::Strftime(Box::new(expand_func_calls(e, func_name, params, body)))
}
Builtin::Strptime(e) => {
Builtin::Strptime(Box::new(expand_func_calls(e, func_name, params, body)))
}
Builtin::Todate => Builtin::Todate,
Builtin::Fromdate => Builtin::Fromdate,
Builtin::Todateiso8601 => Builtin::Todateiso8601,
Builtin::Fromdateiso8601 => Builtin::Fromdateiso8601,
Builtin::Combinations => Builtin::Combinations,
Builtin::CombinationsN(e) => {
Builtin::CombinationsN(Box::new(expand_func_calls(e, func_name, params, body)))
}
Builtin::Trunc => Builtin::Trunc,
Builtin::ToBoolean => Builtin::ToBoolean,
Builtin::Skip(n, e) => Builtin::Skip(
Box::new(expand_func_calls(n, func_name, params, body)),
Box::new(expand_func_calls(e, func_name, params, body)),
),
Builtin::FromUnix => Builtin::FromUnix,
Builtin::ToUnix => Builtin::ToUnix,
Builtin::Tz(e) => Builtin::Tz(Box::new(expand_func_calls(e, func_name, params, body))),
Builtin::Load(e) => Builtin::Load(Box::new(expand_func_calls(e, func_name, params, body))),
Builtin::AtOffset(e) => {
Builtin::AtOffset(Box::new(expand_func_calls(e, func_name, params, body)))
}
Builtin::AtPosition(line, col) => Builtin::AtPosition(
Box::new(expand_func_calls(line, func_name, params, body)),
Box::new(expand_func_calls(col, func_name, params, body)),
),
}
}
fn substitute_func_param_in_builtin(builtin: &Builtin, param: &str, arg: &Expr) -> Builtin {
match builtin {
Builtin::Type => Builtin::Type,
Builtin::IsNull => Builtin::IsNull,
Builtin::IsBoolean => Builtin::IsBoolean,
Builtin::IsNumber => Builtin::IsNumber,
Builtin::IsString => Builtin::IsString,
Builtin::IsArray => Builtin::IsArray,
Builtin::IsObject => Builtin::IsObject,
Builtin::Values => Builtin::Values,
Builtin::Nulls => Builtin::Nulls,
Builtin::Booleans => Builtin::Booleans,
Builtin::Numbers => Builtin::Numbers,
Builtin::Strings => Builtin::Strings,
Builtin::Arrays => Builtin::Arrays,
Builtin::Objects => Builtin::Objects,
Builtin::Iterables => Builtin::Iterables,
Builtin::Scalars => Builtin::Scalars,
Builtin::Length => Builtin::Length,
Builtin::Utf8ByteLength => Builtin::Utf8ByteLength,
Builtin::Keys => Builtin::Keys,
Builtin::KeysUnsorted => Builtin::KeysUnsorted,
Builtin::Has(e) => Builtin::Has(Box::new(substitute_func_param(e, param, arg))),
Builtin::In(e) => Builtin::In(Box::new(substitute_func_param(e, param, arg))),
Builtin::Select(e) => Builtin::Select(Box::new(substitute_func_param(e, param, arg))),
Builtin::Empty => Builtin::Empty,
Builtin::Map(e) => Builtin::Map(Box::new(substitute_func_param(e, param, arg))),
Builtin::MapValues(e) => Builtin::MapValues(Box::new(substitute_func_param(e, param, arg))),
Builtin::Add => Builtin::Add,
Builtin::Any => Builtin::Any,
Builtin::All => Builtin::All,
Builtin::Min => Builtin::Min,
Builtin::Max => Builtin::Max,
Builtin::MinBy(e) => Builtin::MinBy(Box::new(substitute_func_param(e, param, arg))),
Builtin::MaxBy(e) => Builtin::MaxBy(Box::new(substitute_func_param(e, param, arg))),
Builtin::AsciiDowncase => Builtin::AsciiDowncase,
Builtin::AsciiUpcase => Builtin::AsciiUpcase,
Builtin::Ltrimstr(e) => Builtin::Ltrimstr(Box::new(substitute_func_param(e, param, arg))),
Builtin::Rtrimstr(e) => Builtin::Rtrimstr(Box::new(substitute_func_param(e, param, arg))),
Builtin::Startswith(e) => {
Builtin::Startswith(Box::new(substitute_func_param(e, param, arg)))
}
Builtin::Endswith(e) => Builtin::Endswith(Box::new(substitute_func_param(e, param, arg))),
Builtin::Split(e) => Builtin::Split(Box::new(substitute_func_param(e, param, arg))),
Builtin::Join(e) => Builtin::Join(Box::new(substitute_func_param(e, param, arg))),
Builtin::Contains(e) => Builtin::Contains(Box::new(substitute_func_param(e, param, arg))),
Builtin::Inside(e) => Builtin::Inside(Box::new(substitute_func_param(e, param, arg))),
Builtin::First => Builtin::First,
Builtin::Last => Builtin::Last,
Builtin::Nth(e) => Builtin::Nth(Box::new(substitute_func_param(e, param, arg))),
Builtin::Reverse => Builtin::Reverse,
Builtin::Flatten => Builtin::Flatten,
Builtin::FlattenDepth(e) => {
Builtin::FlattenDepth(Box::new(substitute_func_param(e, param, arg)))
}
Builtin::GroupBy(e) => Builtin::GroupBy(Box::new(substitute_func_param(e, param, arg))),
Builtin::Unique => Builtin::Unique,
Builtin::UniqueBy(e) => Builtin::UniqueBy(Box::new(substitute_func_param(e, param, arg))),
Builtin::Sort => Builtin::Sort,
Builtin::SortBy(e) => Builtin::SortBy(Box::new(substitute_func_param(e, param, arg))),
Builtin::ToEntries => Builtin::ToEntries,
Builtin::FromEntries => Builtin::FromEntries,
Builtin::WithEntries(e) => {
Builtin::WithEntries(Box::new(substitute_func_param(e, param, arg)))
}
Builtin::ToString => Builtin::ToString,
Builtin::ToNumber => Builtin::ToNumber,
Builtin::ToJson => Builtin::ToJson,
Builtin::FromJson => Builtin::FromJson,
Builtin::Explode => Builtin::Explode,
Builtin::Implode => Builtin::Implode,
Builtin::Test(e) => Builtin::Test(Box::new(substitute_func_param(e, param, arg))),
Builtin::Indices(e) => Builtin::Indices(Box::new(substitute_func_param(e, param, arg))),
Builtin::Index(e) => Builtin::Index(Box::new(substitute_func_param(e, param, arg))),
Builtin::Rindex(e) => Builtin::Rindex(Box::new(substitute_func_param(e, param, arg))),
Builtin::ToJsonStream => Builtin::ToJsonStream,
Builtin::FromJsonStream => Builtin::FromJsonStream,
Builtin::GetPath(e) => Builtin::GetPath(Box::new(substitute_func_param(e, param, arg))),
Builtin::TestFlags(re, flags) => Builtin::TestFlags(
Box::new(substitute_func_param(re, param, arg)),
Box::new(substitute_func_param(flags, param, arg)),
),
Builtin::Match(re) => Builtin::Match(Box::new(substitute_func_param(re, param, arg))),
Builtin::MatchFlags(re, flags) => Builtin::MatchFlags(
Box::new(substitute_func_param(re, param, arg)),
Box::new(substitute_func_param(flags, param, arg)),
),
Builtin::Capture(e) => Builtin::Capture(Box::new(substitute_func_param(e, param, arg))),
Builtin::CaptureFlags(re, flags) => Builtin::CaptureFlags(
Box::new(substitute_func_param(re, param, arg)),
Box::new(substitute_func_param(flags, param, arg)),
),
Builtin::Sub(re, repl) => Builtin::Sub(
Box::new(substitute_func_param(re, param, arg)),
Box::new(substitute_func_param(repl, param, arg)),
),
Builtin::SubFlags(re, repl, flags) => Builtin::SubFlags(
Box::new(substitute_func_param(re, param, arg)),
Box::new(substitute_func_param(repl, param, arg)),
Box::new(substitute_func_param(flags, param, arg)),
),
Builtin::Gsub(re, repl) => Builtin::Gsub(
Box::new(substitute_func_param(re, param, arg)),
Box::new(substitute_func_param(repl, param, arg)),
),
Builtin::GsubFlags(re, repl, flags) => Builtin::GsubFlags(
Box::new(substitute_func_param(re, param, arg)),
Box::new(substitute_func_param(repl, param, arg)),
Box::new(substitute_func_param(flags, param, arg)),
),
Builtin::Scan(re) => Builtin::Scan(Box::new(substitute_func_param(re, param, arg))),
Builtin::ScanFlags(re, flags) => Builtin::ScanFlags(
Box::new(substitute_func_param(re, param, arg)),
Box::new(substitute_func_param(flags, param, arg)),
),
Builtin::SplitRegex(re, flags) => Builtin::SplitRegex(
Box::new(substitute_func_param(re, param, arg)),
Box::new(substitute_func_param(flags, param, arg)),
),
Builtin::Splits(re) => Builtin::Splits(Box::new(substitute_func_param(re, param, arg))),
Builtin::SplitsFlags(re, flags) => Builtin::SplitsFlags(
Box::new(substitute_func_param(re, param, arg)),
Box::new(substitute_func_param(flags, param, arg)),
),
Builtin::Recurse => Builtin::Recurse,
Builtin::RecurseF(f) => Builtin::RecurseF(Box::new(substitute_func_param(f, param, arg))),
Builtin::RecurseCond(f, c) => Builtin::RecurseCond(
Box::new(substitute_func_param(f, param, arg)),
Box::new(substitute_func_param(c, param, arg)),
),
Builtin::Walk(f) => Builtin::Walk(Box::new(substitute_func_param(f, param, arg))),
Builtin::IsValid(e) => Builtin::IsValid(Box::new(substitute_func_param(e, param, arg))),
Builtin::Path(e) => Builtin::Path(Box::new(substitute_func_param(e, param, arg))),
Builtin::PathNoArg => Builtin::PathNoArg,
Builtin::Parent => Builtin::Parent,
Builtin::ParentN(e) => Builtin::ParentN(Box::new(substitute_func_param(e, param, arg))),
Builtin::Paths => Builtin::Paths,
Builtin::PathsFilter(e) => {
Builtin::PathsFilter(Box::new(substitute_func_param(e, param, arg)))
}
Builtin::LeafPaths => Builtin::LeafPaths,
Builtin::SetPath(p, v) => Builtin::SetPath(
Box::new(substitute_func_param(p, param, arg)),
Box::new(substitute_func_param(v, param, arg)),
),
Builtin::DelPaths(e) => Builtin::DelPaths(Box::new(substitute_func_param(e, param, arg))),
Builtin::Floor => Builtin::Floor,
Builtin::Ceil => Builtin::Ceil,
Builtin::Round => Builtin::Round,
Builtin::Sqrt => Builtin::Sqrt,
Builtin::Fabs => Builtin::Fabs,
Builtin::Log => Builtin::Log,
Builtin::Log10 => Builtin::Log10,
Builtin::Log2 => Builtin::Log2,
Builtin::Exp => Builtin::Exp,
Builtin::Exp10 => Builtin::Exp10,
Builtin::Exp2 => Builtin::Exp2,
Builtin::Pow(x, y) => Builtin::Pow(
Box::new(substitute_func_param(x, param, arg)),
Box::new(substitute_func_param(y, param, arg)),
),
Builtin::Sin => Builtin::Sin,
Builtin::Cos => Builtin::Cos,
Builtin::Tan => Builtin::Tan,
Builtin::Asin => Builtin::Asin,
Builtin::Acos => Builtin::Acos,
Builtin::Atan => Builtin::Atan,
Builtin::Atan2(x, y) => Builtin::Atan2(
Box::new(substitute_func_param(x, param, arg)),
Box::new(substitute_func_param(y, param, arg)),
),
Builtin::Sinh => Builtin::Sinh,
Builtin::Cosh => Builtin::Cosh,
Builtin::Tanh => Builtin::Tanh,
Builtin::Asinh => Builtin::Asinh,
Builtin::Acosh => Builtin::Acosh,
Builtin::Atanh => Builtin::Atanh,
Builtin::Infinite => Builtin::Infinite,
Builtin::Nan => Builtin::Nan,
Builtin::IsInfinite => Builtin::IsInfinite,
Builtin::IsNan => Builtin::IsNan,
Builtin::IsNormal => Builtin::IsNormal,
Builtin::IsFinite => Builtin::IsFinite,
Builtin::Debug => Builtin::Debug,
Builtin::DebugMsg(e) => Builtin::DebugMsg(Box::new(substitute_func_param(e, param, arg))),
Builtin::Env => Builtin::Env,
Builtin::EnvVar(e) => Builtin::EnvVar(Box::new(substitute_func_param(e, param, arg))),
Builtin::EnvObject(name) => Builtin::EnvObject(name.clone()),
Builtin::StrEnv(name) => Builtin::StrEnv(name.clone()),
Builtin::NullLit => Builtin::NullLit,
Builtin::Trim => Builtin::Trim,
Builtin::Ltrim => Builtin::Ltrim,
Builtin::Rtrim => Builtin::Rtrim,
Builtin::Transpose => Builtin::Transpose,
Builtin::BSearch(e) => Builtin::BSearch(Box::new(substitute_func_param(e, param, arg))),
Builtin::ModuleMeta(e) => {
Builtin::ModuleMeta(Box::new(substitute_func_param(e, param, arg)))
}
Builtin::Pick(e) => Builtin::Pick(Box::new(substitute_func_param(e, param, arg))),
Builtin::Omit(e) => Builtin::Omit(Box::new(substitute_func_param(e, param, arg))),
Builtin::Tag => Builtin::Tag,
Builtin::Anchor => Builtin::Anchor,
Builtin::Style => Builtin::Style,
Builtin::Kind => Builtin::Kind,
Builtin::Key => Builtin::Key,
Builtin::Line => Builtin::Line,
Builtin::Column => Builtin::Column,
Builtin::DocumentIndex => Builtin::DocumentIndex,
Builtin::Shuffle => Builtin::Shuffle,
Builtin::Pivot => Builtin::Pivot,
Builtin::SplitDoc => Builtin::SplitDoc,
Builtin::Del(e) => Builtin::Del(Box::new(substitute_func_param(e, param, arg))),
Builtin::Now => Builtin::Now,
Builtin::Abs => Builtin::Abs,
Builtin::Builtins => Builtin::Builtins,
Builtin::Normals => Builtin::Normals,
Builtin::Finites => Builtin::Finites,
Builtin::Limit(n, e) => Builtin::Limit(
Box::new(substitute_func_param(n, param, arg)),
Box::new(substitute_func_param(e, param, arg)),
),
Builtin::FirstStream(e) => {
Builtin::FirstStream(Box::new(substitute_func_param(e, param, arg)))
}
Builtin::LastStream(e) => {
Builtin::LastStream(Box::new(substitute_func_param(e, param, arg)))
}
Builtin::NthStream(n, e) => Builtin::NthStream(
Box::new(substitute_func_param(n, param, arg)),
Box::new(substitute_func_param(e, param, arg)),
),
Builtin::Range(n) => Builtin::Range(Box::new(substitute_func_param(n, param, arg))),
Builtin::RangeFromTo(from, to) => Builtin::RangeFromTo(
Box::new(substitute_func_param(from, param, arg)),
Box::new(substitute_func_param(to, param, arg)),
),
Builtin::RangeFromToBy(from, to, by) => Builtin::RangeFromToBy(
Box::new(substitute_func_param(from, param, arg)),
Box::new(substitute_func_param(to, param, arg)),
Box::new(substitute_func_param(by, param, arg)),
),
Builtin::IsEmpty(e) => Builtin::IsEmpty(Box::new(substitute_func_param(e, param, arg))),
Builtin::RecurseDown => Builtin::RecurseDown,
Builtin::Gmtime => Builtin::Gmtime,
Builtin::Localtime => Builtin::Localtime,
Builtin::Mktime => Builtin::Mktime,
Builtin::Strftime(e) => Builtin::Strftime(Box::new(substitute_func_param(e, param, arg))),
Builtin::Strptime(e) => Builtin::Strptime(Box::new(substitute_func_param(e, param, arg))),
Builtin::Todate => Builtin::Todate,
Builtin::Fromdate => Builtin::Fromdate,
Builtin::Todateiso8601 => Builtin::Todateiso8601,
Builtin::Fromdateiso8601 => Builtin::Fromdateiso8601,
Builtin::Combinations => Builtin::Combinations,
Builtin::CombinationsN(e) => {
Builtin::CombinationsN(Box::new(substitute_func_param(e, param, arg)))
}
Builtin::Trunc => Builtin::Trunc,
Builtin::ToBoolean => Builtin::ToBoolean,
Builtin::Skip(n, e) => Builtin::Skip(
Box::new(substitute_func_param(n, param, arg)),
Box::new(substitute_func_param(e, param, arg)),
),
Builtin::FromUnix => Builtin::FromUnix,
Builtin::ToUnix => Builtin::ToUnix,
Builtin::Tz(e) => Builtin::Tz(Box::new(substitute_func_param(e, param, arg))),
Builtin::Load(e) => Builtin::Load(Box::new(substitute_func_param(e, param, arg))),
Builtin::AtOffset(e) => Builtin::AtOffset(Box::new(substitute_func_param(e, param, arg))),
Builtin::AtPosition(line, col) => Builtin::AtPosition(
Box::new(substitute_func_param(line, param, arg)),
Box::new(substitute_func_param(col, param, arg)),
),
}
}
fn eval_func_call<'a, W: Clone + AsRef<[u64]>>(
name: &str,
_args: &[Expr],
_value: StandardJson<'a, W>,
_optional: bool,
) -> QueryResult<'a, W> {
QueryResult::Error(EvalError::new(format!("undefined function: {}", name)))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::jq::parse;
use crate::json::JsonIndex;
macro_rules! query {
($json:expr, $expr:expr, $pattern:pat $(if $guard:expr)? => $body:expr) => {{
let json_bytes: &[u8] = $json;
let index = JsonIndex::build(json_bytes);
let cursor = index.root(json_bytes);
let expr = parse($expr).unwrap();
match eval::<Vec<u64>, JqSemantics>(&expr, cursor) {
$pattern $(if $guard)? => $body,
other => panic!("unexpected result: {:?}", other),
}
}};
}
#[test]
fn test_identity() {
query!(br#"{"foo": 1}"#, ".", QueryResult::OneCursor(_) => {});
}
#[test]
fn test_field_access() {
query!(br#"{"name": "Alice", "age": 30}"#, ".name",
QueryResult::One(StandardJson::String(s)) => {
assert_eq!(s.as_str().unwrap().as_ref(), "Alice");
}
);
query!(br#"{"name": "Alice", "age": 30}"#, ".age",
QueryResult::One(StandardJson::Number(n)) => {
assert_eq!(n.as_i64().unwrap(), 30);
}
);
}
#[test]
fn test_missing_field() {
query!(br#"{"name": "Alice"}"#, ".missing",
QueryResult::One(StandardJson::Null) => {}
);
query!(br#"{"name": "Alice"}"#, ".missing?",
QueryResult::One(StandardJson::Null) => {}
);
}
#[test]
fn test_missing_field_iteration() {
query!(
br#"[{"status_code": 404}, {"msg": "no status"}, {"msg": "also none"}]"#,
"[.[].status_code]",
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr.len(), 3);
assert_eq!(arr[0], OwnedValue::Int(404));
assert_eq!(arr[1], OwnedValue::Null);
assert_eq!(arr[2], OwnedValue::Null);
}
);
}
#[test]
fn test_field_on_non_object() {
query!(br#"123"#, ".field",
QueryResult::Error(e) => {
assert!(e.message.contains("expected object"));
}
);
query!(br#"123"#, ".field?",
QueryResult::None => {}
);
query!(br#"null"#, ".field",
QueryResult::One(StandardJson::Null) => {}
);
query!(br#"null"#, ".field?",
QueryResult::One(StandardJson::Null) => {}
);
}
#[test]
fn test_nested_missing_field() {
query!(br#"{"a": {"b": 1}}"#, ".a.missing",
QueryResult::One(StandardJson::Null) => {}
);
query!(br#"{"a": null}"#, ".a.missing",
QueryResult::One(StandardJson::Null) => {}
);
query!(br#"{"a": null}"#, ".a.missing?",
QueryResult::One(StandardJson::Null) => {}
);
}
#[test]
fn test_array_index() {
query!(br#"[10, 20, 30]"#, ".[0]",
QueryResult::One(StandardJson::Number(n)) => {
assert_eq!(n.as_i64().unwrap(), 10);
}
);
query!(br#"[10, 20, 30]"#, ".[2]",
QueryResult::One(StandardJson::Number(n)) => {
assert_eq!(n.as_i64().unwrap(), 30);
}
);
query!(br#"[10, 20, 30]"#, ".[-1]",
QueryResult::One(StandardJson::Number(n)) => {
assert_eq!(n.as_i64().unwrap(), 30);
}
);
}
#[test]
fn test_iterate() {
query!(br#"[1, 2, 3]"#, ".[]",
QueryResult::Many(values) => {
assert_eq!(values.len(), 3);
}
);
}
#[test]
fn test_chained() {
query!(br#"{"users": [{"name": "Alice"}, {"name": "Bob"}]}"#, ".users[0].name",
QueryResult::One(StandardJson::String(s)) => {
assert_eq!(s.as_str().unwrap().as_ref(), "Alice");
}
);
query!(br#"{"users": [{"name": "Alice"}, {"name": "Bob"}]}"#, ".users[].name",
QueryResult::Many(values) => {
assert_eq!(values.len(), 2);
match &values[0] {
StandardJson::String(s) => {
assert_eq!(s.as_str().unwrap().as_ref(), "Alice");
}
other => panic!("unexpected: {:?}", other),
}
}
);
}
#[test]
fn test_slice() {
query!(br#"[0, 1, 2, 3, 4, 5]"#, ".[1:4]",
QueryResult::Many(values) => {
assert_eq!(values.len(), 3);
}
);
query!(br#"[0, 1, 2, 3, 4, 5]"#, ".[2:]",
QueryResult::Many(values) => {
assert_eq!(values.len(), 4); }
);
query!(br#"[0, 1, 2, 3, 4, 5]"#, ".[:2]",
QueryResult::Many(values) => {
assert_eq!(values.len(), 2); }
);
}
#[test]
fn test_comma() {
query!(br#"{"a": 1, "b": 2}"#, ".a, .b",
QueryResult::Many(values) => {
assert_eq!(values.len(), 2);
}
);
}
#[test]
fn test_literals() {
query!(br#"{}"#, "null",
QueryResult::Owned(OwnedValue::Null) => {}
);
query!(br#"{}"#, "true",
QueryResult::Owned(OwnedValue::Bool(true)) => {}
);
query!(br#"{}"#, "42",
QueryResult::Owned(OwnedValue::Int(42)) => {}
);
query!(br#"{}"#, "\"hello\"",
QueryResult::Owned(OwnedValue::String(s)) if s == "hello" => {}
);
}
#[test]
fn test_array_construction() {
query!(br#"{"a": 1, "b": 2}"#, "[.a, .b]",
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr.len(), 2);
assert_eq!(arr[0], OwnedValue::Int(1));
assert_eq!(arr[1], OwnedValue::Int(2));
}
);
query!(br#"{}"#, "[]",
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr.len(), 0);
}
);
}
#[test]
fn test_object_construction() {
query!(br#"{"name": "Alice", "age": 30}"#, "{name: .name, years: .age}",
QueryResult::Owned(OwnedValue::Object(obj)) => {
assert_eq!(obj.len(), 2);
assert!(obj.contains_key("name"));
assert!(obj.contains_key("years"));
}
);
query!(br#"{}"#, "{}",
QueryResult::Owned(OwnedValue::Object(obj)) => {
assert_eq!(obj.len(), 0);
}
);
}
#[test]
fn test_recursive_descent() {
query!(br#"{"a": {"b": 1}}"#, "..",
QueryResult::Many(values) => {
assert_eq!(values.len(), 3);
}
);
}
#[test]
fn test_parentheses() {
query!(br#"{"foo": {"bar": 1}}"#, "(.foo).bar",
QueryResult::One(StandardJson::Number(n)) => {
assert_eq!(n.as_i64().unwrap(), 1);
}
);
}
#[test]
fn test_arithmetic_add() {
query!(br#"{"a": 10, "b": 5}"#, ".a + .b",
QueryResult::Owned(OwnedValue::Int(15)) => {}
);
query!(br#"{"a": 1.5, "b": 2.5}"#, ".a + .b",
QueryResult::Owned(OwnedValue::Float(f)) if (f - 4.0).abs() < 0.001 => {}
);
query!(br#"{"a": "hello", "b": " world"}"#, ".a + .b",
QueryResult::Owned(OwnedValue::String(s)) if s == "hello world" => {}
);
query!(br#"{"a": [1, 2], "b": [3, 4]}"#, ".a + .b",
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr.len(), 4);
}
);
}
#[test]
fn test_arithmetic_sub() {
query!(br#"{"a": 10, "b": 3}"#, ".a - .b",
QueryResult::Owned(OwnedValue::Int(7)) => {}
);
}
#[test]
fn test_arithmetic_mul() {
query!(br#"{"a": 6, "b": 7}"#, ".a * .b",
QueryResult::Owned(OwnedValue::Int(42)) => {}
);
query!(br#"{"s": "ab", "n": 3}"#, ".s * .n",
QueryResult::Owned(OwnedValue::String(s)) if s == "ababab" => {}
);
}
#[test]
fn test_arithmetic_div() {
query!(br#"{"a": 10, "b": 4}"#, ".a / .b",
QueryResult::Owned(OwnedValue::Float(f)) if (f - 2.5).abs() < 0.001 => {}
);
query!(br#"{"s": "a,b,c", "sep": ","}"#, ".s / .sep",
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr.len(), 3);
}
);
}
#[test]
fn test_arithmetic_mod() {
query!(br#"{"a": 10, "b": 3}"#, ".a % .b",
QueryResult::Owned(OwnedValue::Int(1)) => {}
);
}
#[test]
fn test_arithmetic_precedence() {
query!(br#"{}"#, "2 + 3 * 4",
QueryResult::Owned(OwnedValue::Int(14)) => {}
);
query!(br#"{}"#, "(2 + 3) * 4",
QueryResult::Owned(OwnedValue::Int(20)) => {}
);
}
#[test]
fn test_comparison_eq() {
query!(br#"{"a": 1, "b": 1}"#, ".a == .b",
QueryResult::Owned(OwnedValue::Bool(true)) => {}
);
query!(br#"{"a": 1, "b": 2}"#, ".a == .b",
QueryResult::Owned(OwnedValue::Bool(false)) => {}
);
query!(br#"{"a": "foo", "b": "foo"}"#, ".a == .b",
QueryResult::Owned(OwnedValue::Bool(true)) => {}
);
}
#[test]
fn test_comparison_ne() {
query!(br#"{"a": 1, "b": 2}"#, ".a != .b",
QueryResult::Owned(OwnedValue::Bool(true)) => {}
);
}
#[test]
fn test_comparison_lt() {
query!(br#"{"a": 1, "b": 2}"#, ".a < .b",
QueryResult::Owned(OwnedValue::Bool(true)) => {}
);
query!(br#"{"a": 2, "b": 1}"#, ".a < .b",
QueryResult::Owned(OwnedValue::Bool(false)) => {}
);
}
#[test]
fn test_comparison_le() {
query!(br#"{"a": 1, "b": 1}"#, ".a <= .b",
QueryResult::Owned(OwnedValue::Bool(true)) => {}
);
}
#[test]
fn test_comparison_gt() {
query!(br#"{"a": 2, "b": 1}"#, ".a > .b",
QueryResult::Owned(OwnedValue::Bool(true)) => {}
);
}
#[test]
fn test_comparison_ge() {
query!(br#"{"a": 2, "b": 2}"#, ".a >= .b",
QueryResult::Owned(OwnedValue::Bool(true)) => {}
);
}
#[test]
fn test_boolean_and() {
query!(br#"{"a": true, "b": true}"#, ".a and .b",
QueryResult::Owned(OwnedValue::Bool(true)) => {}
);
query!(br#"{"a": true, "b": false}"#, ".a and .b",
QueryResult::Owned(OwnedValue::Bool(false)) => {}
);
query!(br#"{"a": false}"#, ".a and .nonexistent",
QueryResult::Owned(OwnedValue::Bool(false)) => {}
);
}
#[test]
fn test_boolean_or() {
query!(br#"{"a": false, "b": true}"#, ".a or .b",
QueryResult::Owned(OwnedValue::Bool(true)) => {}
);
query!(br#"{"a": false, "b": false}"#, ".a or .b",
QueryResult::Owned(OwnedValue::Bool(false)) => {}
);
query!(br#"{"a": true}"#, ".a or .nonexistent",
QueryResult::Owned(OwnedValue::Bool(true)) => {}
);
}
#[test]
fn test_boolean_not() {
query!(br#"true"#, ". | not",
QueryResult::Owned(OwnedValue::Bool(false)) => {}
);
query!(br#"false"#, ". | not",
QueryResult::Owned(OwnedValue::Bool(true)) => {}
);
query!(br#"null"#, ". | not",
QueryResult::Owned(OwnedValue::Bool(true)) => {}
);
query!(br#"0"#, ". | not",
QueryResult::Owned(OwnedValue::Bool(false)) => {}
);
}
#[test]
fn test_alternative() {
query!(br#"{"a": 1}"#, ".a // 0",
QueryResult::One(StandardJson::Number(n)) => {
assert_eq!(n.as_i64().unwrap(), 1);
}
);
query!(br#"{"a": null}"#, ".a // 0",
QueryResult::Owned(OwnedValue::Int(0)) => {}
);
query!(br#"{}"#, ".missing? // \"default\"",
QueryResult::Owned(OwnedValue::String(s)) if s == "default" => {}
);
query!(br#"{"a": null, "b": null}"#, ".a // .b // 42",
QueryResult::Owned(OwnedValue::Int(42)) => {}
);
}
#[test]
fn test_complex_expressions() {
query!(br#"{"x": 10}"#, ".x > 5 and .x < 20",
QueryResult::Owned(OwnedValue::Bool(true)) => {}
);
query!(br#"{"val": 3}"#, ".val > 0 // false",
QueryResult::Owned(OwnedValue::Bool(true)) => {}
);
}
#[test]
fn test_if_then_else() {
query!(br#"{"a": true}"#, "if .a then 1 else 2 end",
QueryResult::Owned(OwnedValue::Int(1)) => {}
);
query!(br#"{"a": false}"#, "if .a then 1 else 2 end",
QueryResult::Owned(OwnedValue::Int(2)) => {}
);
query!(br#"{"x": 10}"#, "if .x > 5 then \"big\" else \"small\" end",
QueryResult::Owned(OwnedValue::String(s)) if s == "big" => {}
);
query!(br#"{"a": null}"#, "if .a then 1 else 2 end",
QueryResult::Owned(OwnedValue::Int(2)) => {}
);
query!(br#"{"a": 0}"#, "if .a then 1 else 2 end",
QueryResult::Owned(OwnedValue::Int(1)) => {}
);
}
#[test]
fn test_if_elif() {
query!(br#"{"x": 1}"#, "if .x == 1 then \"one\" elif .x == 2 then \"two\" else \"other\" end",
QueryResult::Owned(OwnedValue::String(s)) if s == "one" => {}
);
query!(br#"{"x": 2}"#, "if .x == 1 then \"one\" elif .x == 2 then \"two\" else \"other\" end",
QueryResult::Owned(OwnedValue::String(s)) if s == "two" => {}
);
query!(br#"{"x": 3}"#, "if .x == 1 then \"one\" elif .x == 2 then \"two\" else \"other\" end",
QueryResult::Owned(OwnedValue::String(s)) if s == "other" => {}
);
}
#[test]
fn test_if_no_else() {
query!(br#"{"a": false}"#, "if .a then 1 end",
QueryResult::Owned(OwnedValue::Null) => {}
);
query!(br#"{"a": true}"#, "if .a then 1 end",
QueryResult::Owned(OwnedValue::Int(1)) => {}
);
}
#[test]
fn test_if_with_expressions() {
query!(br#"{"x": 5}"#, "if .x > 0 then .x * 2 else .x end",
QueryResult::Owned(OwnedValue::Int(10)) => {}
);
query!(br#"{"type": "a", "a": 1, "b": 2}"#, "if .type == \"a\" then .a else .b end",
QueryResult::One(StandardJson::Number(n)) => {
assert_eq!(n.as_i64().unwrap(), 1);
}
);
}
#[test]
fn test_try_catch_success() {
query!(br#"{"a": 1}"#, "try .a catch 0",
QueryResult::One(StandardJson::Number(n)) => {
assert_eq!(n.as_i64().unwrap(), 1);
}
);
query!(br#"{"a": 1}"#, "try .a",
QueryResult::One(StandardJson::Number(n)) => {
assert_eq!(n.as_i64().unwrap(), 1);
}
);
}
#[test]
fn test_try_catch_error() {
query!(br#"{}"#, "try .missing catch \"default\"",
QueryResult::One(StandardJson::Null) => {}
);
query!(br#"{}"#, "try .missing",
QueryResult::One(StandardJson::Null) => {}
);
query!(br#"123"#, "try .foo catch \"default\"",
QueryResult::Owned(OwnedValue::String(s)) if s == "default" => {}
);
query!(br#"123"#, "try .foo",
QueryResult::None => {}
);
query!(br#"123"#, "try .foo catch null",
QueryResult::Owned(OwnedValue::Null) => {}
);
}
#[test]
fn test_try_catch_optional() {
query!(br#"{}"#, "try .missing? catch \"default\"",
QueryResult::One(StandardJson::Null) => {}
);
query!(br#"123"#, "try .foo? catch \"default\"",
QueryResult::None => {}
);
}
#[test]
fn test_error_basic() {
query!(br#"{}"#, "error",
QueryResult::Error(e) => {
assert_eq!(e.message, "null");
}
);
query!(br#"{}"#, "error(\"something went wrong\")",
QueryResult::Error(e) => {
assert_eq!(e.message, "something went wrong");
}
);
query!(br#"{}"#, "error(42)",
QueryResult::Error(e) => {
assert_eq!(e.message, "42");
}
);
}
#[test]
fn test_error_with_field() {
query!(br#"{"msg": "custom error"}"#, "error(.msg)",
QueryResult::Error(e) => {
assert_eq!(e.message, "custom error");
}
);
}
#[test]
fn test_try_catch_raised_error() {
query!(br#"{}"#, "try error(\"oops\") catch \"caught\"",
QueryResult::Owned(OwnedValue::String(s)) if s == "caught" => {}
);
query!(br#"{}"#, "try error(\"oops\")",
QueryResult::None => {}
);
}
#[test]
fn test_control_flow_combinations() {
query!(br#"{"a": true}"#, "try (if .a then .missing else .a end) catch \"error\"",
QueryResult::One(StandardJson::Null) => {}
);
query!(br#"{"a": 123}"#, "try (if true then .a.foo else 0 end) catch \"error\"",
QueryResult::Owned(OwnedValue::String(s)) if s == "error" => {}
);
query!(br#"{"a": true}"#, "if .a then try .missing catch 0 else 1 end",
QueryResult::One(StandardJson::Null) => {}
);
query!(br#"{"a": 123}"#, "if true then try .a.foo catch 0 else 1 end",
QueryResult::Owned(OwnedValue::Int(0)) => {}
);
query!(br#"{"x": 15}"#, "if .x < 10 then \"small\" elif .x < 20 then \"medium\" else \"large\" end",
QueryResult::Owned(OwnedValue::String(s)) if s == "medium" => {}
);
}
#[test]
fn test_builtin_type() {
query!(br#"null"#, "type",
QueryResult::Owned(OwnedValue::String(s)) if s == "null" => {}
);
query!(br#"true"#, "type",
QueryResult::Owned(OwnedValue::String(s)) if s == "boolean" => {}
);
query!(br#"42"#, "type",
QueryResult::Owned(OwnedValue::String(s)) if s == "number" => {}
);
query!(br#""hello""#, "type",
QueryResult::Owned(OwnedValue::String(s)) if s == "string" => {}
);
query!(br#"[1, 2]"#, "type",
QueryResult::Owned(OwnedValue::String(s)) if s == "array" => {}
);
query!(br#"{"a": 1}"#, "type",
QueryResult::Owned(OwnedValue::String(s)) if s == "object" => {}
);
}
#[test]
fn test_builtin_is_type() {
query!(br#"null"#, "isnull",
QueryResult::Owned(OwnedValue::Bool(true)) => {}
);
query!(br#"1"#, "isnull",
QueryResult::Owned(OwnedValue::Bool(false)) => {}
);
query!(br#"true"#, "isboolean",
QueryResult::Owned(OwnedValue::Bool(true)) => {}
);
query!(br#"1"#, "isboolean",
QueryResult::Owned(OwnedValue::Bool(false)) => {}
);
query!(br#"42"#, "isnumber",
QueryResult::Owned(OwnedValue::Bool(true)) => {}
);
query!(br#""42""#, "isnumber",
QueryResult::Owned(OwnedValue::Bool(false)) => {}
);
query!(br#""hello""#, "isstring",
QueryResult::Owned(OwnedValue::Bool(true)) => {}
);
query!(br#"42"#, "isstring",
QueryResult::Owned(OwnedValue::Bool(false)) => {}
);
query!(br#"[1, 2]"#, "isarray",
QueryResult::Owned(OwnedValue::Bool(true)) => {}
);
query!(br#"{}"#, "isarray",
QueryResult::Owned(OwnedValue::Bool(false)) => {}
);
query!(br#"{"a": 1}"#, "isobject",
QueryResult::Owned(OwnedValue::Bool(true)) => {}
);
query!(br#"[]"#, "isobject",
QueryResult::Owned(OwnedValue::Bool(false)) => {}
);
}
#[test]
fn test_builtin_length() {
query!(br#"null"#, "length",
QueryResult::Owned(OwnedValue::Int(0)) => {}
);
query!(br#""hello""#, "length",
QueryResult::Owned(OwnedValue::Int(5)) => {}
);
query!(b"\"h\\u00e9llo\"", "length",
QueryResult::Owned(OwnedValue::Int(5)) => {}
);
query!(br#"[1, 2, 3]"#, "length",
QueryResult::Owned(OwnedValue::Int(3)) => {}
);
query!(br#"{"a": 1, "b": 2}"#, "length",
QueryResult::Owned(OwnedValue::Int(2)) => {}
);
query!(br#"-5"#, "length",
QueryResult::Owned(OwnedValue::Int(5)) => {}
);
}
#[test]
fn test_builtin_utf8bytelength() {
query!(br#""hello""#, "utf8bytelength",
QueryResult::Owned(OwnedValue::Int(5)) => {}
);
query!(b"\"h\\u00e9llo\"", "utf8bytelength",
QueryResult::Owned(OwnedValue::Int(6)) => {}
);
}
#[test]
fn test_builtin_keys() {
query!(br#"{"b": 2, "a": 1, "c": 3}"#, "keys",
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr.len(), 3);
assert_eq!(arr[0], OwnedValue::String("a".into()));
assert_eq!(arr[1], OwnedValue::String("b".into()));
assert_eq!(arr[2], OwnedValue::String("c".into()));
}
);
query!(br#"["x", "y", "z"]"#, "keys",
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr.len(), 3);
assert_eq!(arr[0], OwnedValue::Int(0));
assert_eq!(arr[1], OwnedValue::Int(1));
assert_eq!(arr[2], OwnedValue::Int(2));
}
);
}
#[test]
fn test_builtin_keys_unsorted() {
query!(br#"{"b": 2, "a": 1}"#, "keys_unsorted",
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr.len(), 2);
}
);
}
#[test]
fn test_builtin_has() {
query!(br#"{"a": 1, "b": 2}"#, "has(\"a\")",
QueryResult::Owned(OwnedValue::Bool(true)) => {}
);
query!(br#"{"a": 1, "b": 2}"#, "has(\"c\")",
QueryResult::Owned(OwnedValue::Bool(false)) => {}
);
query!(br#"[1, 2, 3]"#, "has(0)",
QueryResult::Owned(OwnedValue::Bool(true)) => {}
);
query!(br#"[1, 2, 3]"#, "has(5)",
QueryResult::Owned(OwnedValue::Bool(false)) => {}
);
}
#[test]
fn test_builtin_in() {
query!(br#"{"a": 1, "b": 2}"#, "has(\"a\")",
QueryResult::Owned(OwnedValue::Bool(true)) => {}
);
}
#[test]
fn test_builtin_select() {
query!(br#"5"#, "select(. > 3)",
QueryResult::One(StandardJson::Number(n)) => {
assert_eq!(n.as_i64().unwrap(), 5);
}
);
query!(br#"2"#, "select(. > 3)",
QueryResult::None => {}
);
}
#[test]
fn test_builtin_empty() {
query!(br#"1"#, "empty",
QueryResult::None => {}
);
}
#[test]
fn test_builtin_map() {
query!(br#"[1, 2, 3]"#, "map(. * 2)",
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr.len(), 3);
assert_eq!(arr[0], OwnedValue::Int(2));
assert_eq!(arr[1], OwnedValue::Int(4));
assert_eq!(arr[2], OwnedValue::Int(6));
}
);
query!(br#"[1, 2, 3]"#, "map(. + 1)",
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr[0], OwnedValue::Int(2));
}
);
}
#[test]
fn test_builtin_map_values() {
query!(br#"{"a": 1, "b": 2}"#, "map_values(. * 10)",
QueryResult::Owned(OwnedValue::Object(obj)) => {
assert_eq!(obj.get("a"), Some(&OwnedValue::Int(10)));
assert_eq!(obj.get("b"), Some(&OwnedValue::Int(20)));
}
);
query!(br#"[1, 2, 3]"#, "map_values(. + 1)",
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr[0], OwnedValue::Int(2));
assert_eq!(arr[1], OwnedValue::Int(3));
assert_eq!(arr[2], OwnedValue::Int(4));
}
);
}
#[test]
fn test_builtin_add() {
query!(br#"[1, 2, 3]"#, "add",
QueryResult::Owned(OwnedValue::Int(6)) => {}
);
query!(br#"["a", "b", "c"]"#, "add",
QueryResult::Owned(OwnedValue::String(s)) if s == "abc" => {}
);
query!(br#"[[1], [2], [3]]"#, "add",
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr.len(), 3);
}
);
query!(br#"[]"#, "add",
QueryResult::Owned(OwnedValue::Null) => {}
);
}
#[test]
fn test_builtin_any() {
query!(br#"[true, false]"#, "any",
QueryResult::Owned(OwnedValue::Bool(true)) => {}
);
query!(br#"[false, false]"#, "any",
QueryResult::Owned(OwnedValue::Bool(false)) => {}
);
query!(br#"[null, null]"#, "any",
QueryResult::Owned(OwnedValue::Bool(false)) => {}
);
query!(br#"[1, 0]"#, "any",
QueryResult::Owned(OwnedValue::Bool(true)) => {} );
}
#[test]
fn test_builtin_all() {
query!(br#"[true, true]"#, "all",
QueryResult::Owned(OwnedValue::Bool(true)) => {}
);
query!(br#"[true, false]"#, "all",
QueryResult::Owned(OwnedValue::Bool(false)) => {}
);
query!(br#"[1, 2, 3]"#, "all",
QueryResult::Owned(OwnedValue::Bool(true)) => {} );
}
#[test]
fn test_builtin_min() {
query!(br#"[3, 1, 2]"#, "min",
QueryResult::Owned(OwnedValue::Int(1)) => {}
);
query!(br#"["c", "a", "b"]"#, "min",
QueryResult::Owned(OwnedValue::String(s)) if s == "a" => {}
);
query!(br#"[]"#, "min",
QueryResult::Owned(OwnedValue::Null) => {}
);
}
#[test]
fn test_builtin_max() {
query!(br#"[3, 1, 2]"#, "max",
QueryResult::Owned(OwnedValue::Int(3)) => {}
);
query!(br#"["c", "a", "b"]"#, "max",
QueryResult::Owned(OwnedValue::String(s)) if s == "c" => {}
);
query!(br#"[]"#, "max",
QueryResult::Owned(OwnedValue::Null) => {}
);
}
#[test]
fn test_builtin_min_by() {
query!(br#"[{"a": 3}, {"a": 1}, {"a": 2}]"#, "min_by(.a)",
QueryResult::Owned(OwnedValue::Object(obj)) => {
assert_eq!(obj.get("a"), Some(&OwnedValue::Int(1)));
}
);
}
#[test]
fn test_builtin_max_by() {
query!(br#"[{"a": 3}, {"a": 1}, {"a": 2}]"#, "max_by(.a)",
QueryResult::Owned(OwnedValue::Object(obj)) => {
assert_eq!(obj.get("a"), Some(&OwnedValue::Int(3)));
}
);
}
#[test]
fn test_builtin_combinations() {
query!(br#"[1, 2, 3, 4, 5]"#, "map(. * 2)",
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr.len(), 5);
assert_eq!(arr[0], OwnedValue::Int(2));
}
);
query!(br#"[1, 2, 3, 4, 5]"#, "[.[] | select(. > 2)]",
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr.len(), 3);
}
);
query!(br#"{"a": 1, "b": 2}"#, "keys",
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr.len(), 2);
}
);
}
#[test]
fn test_builtin_ascii_downcase() {
query!(br#""HELLO World""#, "ascii_downcase",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "hello world");
}
);
query!(b"\"H\\u00e9LLO\"", "ascii_downcase",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "h\u{00e9}llo");
}
);
}
#[test]
fn test_builtin_ascii_upcase() {
query!(br#""hello World""#, "ascii_upcase",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "HELLO WORLD");
}
);
}
#[test]
fn test_builtin_ltrimstr() {
query!(br#""hello world""#, r#"ltrimstr("hello ")"#,
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "world");
}
);
query!(br#""hello world""#, r#"ltrimstr("goodbye")"#,
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "hello world");
}
);
}
#[test]
fn test_builtin_rtrimstr() {
query!(br#""hello world""#, r#"rtrimstr(" world")"#,
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "hello");
}
);
query!(br#""hello world""#, r#"rtrimstr("goodbye")"#,
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "hello world");
}
);
}
#[test]
fn test_builtin_startswith() {
query!(br#""hello world""#, r#"startswith("hello")"#,
QueryResult::Owned(OwnedValue::Bool(b)) => {
assert!(b);
}
);
query!(br#""hello world""#, r#"startswith("world")"#,
QueryResult::Owned(OwnedValue::Bool(b)) => {
assert!(!b);
}
);
}
#[test]
fn test_builtin_endswith() {
query!(br#""hello world""#, r#"endswith("world")"#,
QueryResult::Owned(OwnedValue::Bool(b)) => {
assert!(b);
}
);
query!(br#""hello world""#, r#"endswith("hello")"#,
QueryResult::Owned(OwnedValue::Bool(b)) => {
assert!(!b);
}
);
}
#[test]
fn test_builtin_split() {
query!(br#""a,b,c""#, r#"split(",")"#,
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr.len(), 3);
assert_eq!(arr[0], OwnedValue::String("a".into()));
assert_eq!(arr[1], OwnedValue::String("b".into()));
assert_eq!(arr[2], OwnedValue::String("c".into()));
}
);
}
#[test]
fn test_builtin_join() {
query!(br#"["a", "b", "c"]"#, r#"join(",")"#,
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "a,b,c");
}
);
query!(br#"["a", null, "c"]"#, r#"join("-")"#,
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "a-c");
}
);
}
#[test]
fn test_builtin_contains() {
query!(br#""hello world""#, r#"contains("world")"#,
QueryResult::Owned(OwnedValue::Bool(b)) => {
assert!(b);
}
);
query!(br#"[1, 2, 3]"#, r#"contains([2])"#,
QueryResult::Owned(OwnedValue::Bool(b)) => {
assert!(b);
}
);
query!(br#"{"a": 1, "b": 2}"#, r#"contains({"a": 1})"#,
QueryResult::Owned(OwnedValue::Bool(b)) => {
assert!(b);
}
);
}
#[test]
fn test_builtin_inside() {
query!(br#"[2]"#, r#"inside([1, 2, 3])"#,
QueryResult::Owned(OwnedValue::Bool(b)) => {
assert!(b);
}
);
query!(br#"{"a": 1}"#, r#"inside({"a": 1, "b": 2})"#,
QueryResult::Owned(OwnedValue::Bool(b)) => {
assert!(b);
}
);
}
#[test]
fn test_builtin_first() {
query!(br#"[1, 2, 3]"#, "first",
QueryResult::One(StandardJson::Number(n)) => {
assert_eq!(n.as_i64().unwrap(), 1);
}
);
}
#[test]
fn test_builtin_last() {
query!(br#"[1, 2, 3]"#, "last",
QueryResult::Owned(OwnedValue::Int(n)) => {
assert_eq!(n, 3);
}
);
}
#[test]
fn test_builtin_nth() {
query!(br#"[10, 20, 30]"#, "nth(1)",
QueryResult::One(StandardJson::Number(n)) => {
assert_eq!(n.as_i64().unwrap(), 20);
}
);
}
#[test]
fn test_builtin_reverse() {
query!(br#"[1, 2, 3]"#, "reverse",
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr, vec![OwnedValue::Int(3), OwnedValue::Int(2), OwnedValue::Int(1)]);
}
);
query!(br#""hello""#, "reverse",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "olleh");
}
);
}
#[test]
fn test_builtin_flatten() {
query!(br#"[[1, 2], [3, [4]]]"#, "flatten",
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr.len(), 4);
assert_eq!(arr[0], OwnedValue::Int(1));
assert_eq!(arr[3], OwnedValue::Array(vec![OwnedValue::Int(4)]));
}
);
query!(br#"[[1], [[2]], [[[3]]]]"#, "flatten(2)",
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr.len(), 3);
assert_eq!(arr[0], OwnedValue::Int(1));
assert_eq!(arr[1], OwnedValue::Int(2));
assert_eq!(arr[2], OwnedValue::Array(vec![OwnedValue::Int(3)]));
}
);
}
#[test]
fn test_builtin_group_by() {
query!(br#"[{"a": 1}, {"a": 2}, {"a": 1}]"#, "group_by(.a)",
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr.len(), 2);
}
);
}
#[test]
fn test_builtin_unique() {
query!(br#"[1, 2, 1, 3, 2]"#, "unique",
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr, vec![OwnedValue::Int(1), OwnedValue::Int(2), OwnedValue::Int(3)]);
}
);
}
#[test]
fn test_builtin_unique_by() {
query!(br#"[{"a": 1, "b": 1}, {"a": 1, "b": 2}, {"a": 2, "b": 3}]"#, "unique_by(.a)",
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr.len(), 2);
}
);
}
#[test]
fn test_builtin_sort() {
query!(br#"[3, 1, 2]"#, "sort",
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr, vec![OwnedValue::Int(1), OwnedValue::Int(2), OwnedValue::Int(3)]);
}
);
}
#[test]
fn test_builtin_sort_by() {
query!(br#"[{"a": 3}, {"a": 1}, {"a": 2}]"#, "sort_by(.a)",
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr.len(), 3);
if let OwnedValue::Object(obj) = &arr[0] {
assert_eq!(obj.get("a"), Some(&OwnedValue::Int(1)));
}
}
);
}
#[test]
fn test_builtin_to_entries() {
query!(br#"{"a": 1, "b": 2}"#, "to_entries",
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr.len(), 2);
if let OwnedValue::Object(obj) = &arr[0] {
assert!(obj.contains_key("key"));
assert!(obj.contains_key("value"));
}
}
);
}
#[test]
fn test_builtin_from_entries() {
query!(br#"[{"key": "a", "value": 1}, {"key": "b", "value": 2}]"#, "from_entries",
QueryResult::Owned(OwnedValue::Object(obj)) => {
assert_eq!(obj.get("a"), Some(&OwnedValue::Int(1)));
assert_eq!(obj.get("b"), Some(&OwnedValue::Int(2)));
}
);
query!(br#"[{"name": "x", "value": 10}]"#, "from_entries",
QueryResult::Owned(OwnedValue::Object(obj)) => {
assert_eq!(obj.get("x"), Some(&OwnedValue::Int(10)));
}
);
}
#[test]
fn test_builtin_with_entries() {
query!(br#"{"a": 1, "b": 2}"#, "with_entries({key: .key, value: .value})",
QueryResult::Owned(OwnedValue::Object(obj)) => {
assert_eq!(obj.get("a"), Some(&OwnedValue::Int(1)));
assert_eq!(obj.get("b"), Some(&OwnedValue::Int(2)));
}
);
}
#[test]
fn test_string_interpolation() {
query!(br#"{"name": "Alice"}"#, r#""Hello \(.name)""#,
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "Hello Alice");
}
);
query!(br#"{"first": "John", "last": "Doe"}"#, r#""\(.first) \(.last)""#,
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "John Doe");
}
);
query!(br#"{"count": 42}"#, r#""Count: \(.count)""#,
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "Count: 42");
}
);
}
#[test]
fn test_format_json() {
query!(br#"{"a": 1}"#, "@json",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, r#"{"a":1}"#);
}
);
}
#[test]
fn test_format_text() {
query!(br#""hello""#, "@text",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "hello");
}
);
query!(br#"42"#, "@text",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "42");
}
);
}
#[test]
fn test_format_uri() {
query!(br#""hello world""#, "@uri",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "hello%20world");
}
);
}
#[test]
fn test_format_csv() {
query!(br#"["a", "b", "c"]"#, "@csv",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "a,b,c");
}
);
query!(br#"["hello, world", "test"]"#, "@csv",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "\"hello, world\",test");
}
);
}
#[test]
fn test_format_tsv() {
query!(br#"["a", "b", "c"]"#, "@tsv",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "a\tb\tc");
}
);
}
#[test]
fn test_format_dsv_pipe() {
query!(br#"["a", "b", "c"]"#, r#"@dsv("|")"#,
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "a|b|c");
}
);
}
#[test]
fn test_format_dsv_semicolon() {
query!(br#"["a", "b", "c"]"#, r#"@dsv(";")"#,
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "a;b;c");
}
);
}
#[test]
fn test_format_dsv_with_quoting() {
query!(br#"["a", "b|c", "d"]"#, r#"@dsv("|")"#,
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, r#"a|"b|c"|d"#);
}
);
}
#[test]
fn test_format_dsv_with_quotes_in_data() {
query!(br#"["a", "b\"c", "d"]"#, r#"@dsv(",")"#,
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, r#"a,"b""c",d"#);
}
);
}
#[test]
fn test_format_dsv_with_newline() {
query!(br#"["a", "b\nc", "d"]"#, r#"@dsv(",")"#,
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "a,\"b\nc\",d");
}
);
}
#[test]
fn test_format_base64() {
query!(br#""hello""#, "@base64",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "aGVsbG8=");
}
);
}
#[test]
fn test_format_base64d() {
query!(br#""aGVsbG8=""#, "@base64d",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "hello");
}
);
}
#[test]
fn test_format_html() {
query!(br#""<script>alert('xss')</script>""#, "@html",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "<script>alert('xss')</script>");
}
);
}
#[test]
fn test_format_sh() {
query!(br#""hello world""#, "@sh",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "'hello world'");
}
);
query!(br#""it's a test""#, "@sh",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "'it'\\''s a test'");
}
);
}
#[test]
fn test_format_yaml() {
query!(br#"{"a": 1, "b": 2}"#, "@yaml",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "{a: 1, b: 2}");
}
);
query!(br#"[1, 2, 3]"#, "@yaml",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "[1, 2, 3]");
}
);
query!(br#"{"name": "test", "items": [1, 2]}"#, "@yaml",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "{name: test, items: [1, 2]}");
}
);
query!(br#"{"value": "true"}"#, "@yaml",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "{value: \"true\"}");
}
);
query!(br#"{"flag": true, "nothing": null}"#, "@yaml",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "{flag: true, nothing: null}");
}
);
query!(br#"[]"#, "@yaml",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "[]");
}
);
query!(br#"{}"#, "@yaml",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "{}");
}
);
query!(br#"3.14"#, "@yaml",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "3.14");
}
);
}
#[test]
fn test_format_props() {
query!(br#"{"database": "postgres", "port": 5432}"#, "@props",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "database = postgres\nport = 5432");
}
);
query!(br#"{"nested": {"a": 1, "b": 2}, "top": "value"}"#, "@props",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "nested.a = 1\nnested.b = 2\ntop = value");
}
);
query!(br#"{"arr": [1, 2, 3]}"#, "@props",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "arr.0 = 1\narr.1 = 2\narr.2 = 3");
}
);
query!(br#"{"a": {"b": {"c": 42}}}"#, "@props",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "a.b.c = 42");
}
);
query!(br#""just a string""#, "@props",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "just a string");
}
);
query!(br#"null"#, "@props",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "null");
}
);
query!(br#"{"enabled": true, "disabled": false}"#, "@props",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "enabled = true\ndisabled = false");
}
);
query!(br#"{"key": "value=with=equals"}"#, "@props",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "key = value=with=equals");
}
);
query!(br#"{}"#, "@props",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "");
}
);
query!(br#"[1, 2, 3]"#, "@props",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "0 = 1\n1 = 2\n2 = 3");
}
);
}
#[test]
fn test_builtin_tostring() {
query!(br#"42"#, "tostring",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "42");
}
);
query!(br#"true"#, "tostring",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "true");
}
);
query!(br#"null"#, "tostring",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "null");
}
);
}
#[test]
fn test_builtin_tonumber() {
query!(br#""42""#, "tonumber",
QueryResult::Owned(OwnedValue::Int(n)) => {
assert_eq!(n, 42);
}
);
query!(br#""2.75""#, "tonumber",
QueryResult::Owned(OwnedValue::Float(f)) => {
assert!((f - 2.75).abs() < 0.001);
}
);
query!(br#"42"#, "tonumber",
QueryResult::Owned(OwnedValue::Int(n)) => {
assert_eq!(n, 42);
}
);
}
#[test]
fn test_builtin_explode() {
query!(br#""abc""#, "explode",
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr.len(), 3);
assert_eq!(arr[0], OwnedValue::Int(97)); assert_eq!(arr[1], OwnedValue::Int(98)); assert_eq!(arr[2], OwnedValue::Int(99)); }
);
}
#[test]
fn test_builtin_implode() {
query!(br#"[97, 98, 99]"#, "implode",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "abc");
}
);
}
#[test]
fn test_builtin_test() {
query!(br#""hello world""#, r#"test("world")"#,
QueryResult::Owned(OwnedValue::Bool(b)) => {
assert!(b);
}
);
query!(br#""hello world""#, r#"test("xyz")"#,
QueryResult::Owned(OwnedValue::Bool(b)) => {
assert!(!b);
}
);
}
#[test]
fn test_builtin_indices() {
query!(br#""abcabc""#, r#"indices("bc")"#,
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr.len(), 2);
assert_eq!(arr[0], OwnedValue::Int(1));
assert_eq!(arr[1], OwnedValue::Int(4));
}
);
}
#[test]
fn test_builtin_index() {
query!(br#""hello world""#, r#"index("world")"#,
QueryResult::Owned(OwnedValue::Int(n)) => {
assert_eq!(n, 6);
}
);
query!(br#""hello world""#, r#"index("xyz")"#,
QueryResult::Owned(OwnedValue::Null) => {}
);
}
#[test]
fn test_builtin_rindex() {
query!(br#""abcabc""#, r#"rindex("bc")"#,
QueryResult::Owned(OwnedValue::Int(n)) => {
assert_eq!(n, 4);
}
);
}
#[test]
fn test_builtin_getpath() {
query!(br#"{"a": {"b": 42}}"#, r#"getpath(["a", "b"])"#,
QueryResult::Owned(OwnedValue::Int(n)) => {
assert_eq!(n, 42);
}
);
query!(br#"[1, 2, 3]"#, r#"getpath([1])"#,
QueryResult::Owned(OwnedValue::Int(n)) => {
assert_eq!(n, 2);
}
);
query!(br#"[1, 2, 3]"#, r#"getpath([-1])"#,
QueryResult::Owned(OwnedValue::Int(n)) => {
assert_eq!(n, 3);
}
);
query!(br#"[1, 2, 3]"#, r#"getpath([-2])"#,
QueryResult::Owned(OwnedValue::Int(n)) => {
assert_eq!(n, 2);
}
);
query!(br#"{"a": [10, 20, 30]}"#, r#"getpath(["a", -1])"#,
QueryResult::Owned(OwnedValue::Int(n)) => {
assert_eq!(n, 30);
}
);
}
#[cfg(feature = "regex")]
#[test]
fn test_regex_scan() {
query!(br#""test abc test""#, r#"scan("test")"#,
QueryResult::ManyOwned(matches) => {
assert_eq!(matches.len(), 2);
assert_eq!(matches[0], OwnedValue::String("test".to_string()));
assert_eq!(matches[1], OwnedValue::String("test".to_string()));
}
);
}
#[cfg(feature = "regex")]
#[test]
fn test_regex_splits() {
query!(br#""a1b2c3d""#, r#"splits("[0-9]")"#,
QueryResult::Owned(OwnedValue::Array(parts)) => {
assert_eq!(parts.len(), 4);
assert_eq!(parts[0], OwnedValue::String("a".to_string()));
assert_eq!(parts[1], OwnedValue::String("b".to_string()));
assert_eq!(parts[2], OwnedValue::String("c".to_string()));
assert_eq!(parts[3], OwnedValue::String("d".to_string()));
}
);
}
#[cfg(feature = "regex")]
#[test]
fn test_regex_sub() {
query!(br#""hello world world""#, r#"sub("world"; "there")"#,
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "hello there world");
}
);
}
#[cfg(feature = "regex")]
#[test]
fn test_regex_gsub() {
query!(br#""hello world world""#, r#"gsub("world"; "there")"#,
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "hello there there");
}
);
query!(br#""a1b2c3""#, r#"gsub("[0-9]"; "X")"#,
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "aXbXcX");
}
);
}
#[cfg(feature = "regex")]
#[test]
fn test_regex_match() {
query!(br#""test123test""#, r#"match("[0-9]+")"#,
QueryResult::Owned(OwnedValue::Object(obj)) => {
assert_eq!(obj.get("string"), Some(&OwnedValue::String("123".to_string())));
assert_eq!(obj.get("offset"), Some(&OwnedValue::Int(4)));
assert_eq!(obj.get("length"), Some(&OwnedValue::Int(3)));
}
);
query!(br#""hello""#, r#"match("[0-9]+")"#,
QueryResult::Owned(OwnedValue::Null) => {}
);
}
#[cfg(feature = "regex")]
#[test]
fn test_regex_capture() {
query!(br#""foo bar""#, r#"capture("(?P<first>\\w+) (?P<second>\\w+)")"#,
QueryResult::Owned(OwnedValue::Object(obj)) => {
assert_eq!(obj.get("first"), Some(&OwnedValue::String("foo".to_string())));
assert_eq!(obj.get("second"), Some(&OwnedValue::String("bar".to_string())));
}
);
}
#[test]
fn test_variable_binding_as() {
query!(br#"{"foo": 10, "bar": 5}"#, r#".foo as $x | .bar + $x"#,
QueryResult::Owned(OwnedValue::Int(15)) => {}
);
query!(br#"{"name": "Alice", "age": 30}"#, r#".name as $n | {name: $n, greeting: "Hello"}"#,
QueryResult::Owned(OwnedValue::Object(obj)) => {
assert_eq!(obj.get("name"), Some(&OwnedValue::String("Alice".to_string())));
assert_eq!(obj.get("greeting"), Some(&OwnedValue::String("Hello".to_string())));
}
);
}
#[test]
fn test_reduce() {
query!(br#"[1, 2, 3, 4, 5]"#, r#"reduce .[] as $x (0; . + $x)"#,
QueryResult::Owned(OwnedValue::Int(15)) => {}
);
query!(br#"["a", "b", "c"]"#, r#"reduce .[] as $x (0; . + 1)"#,
QueryResult::Owned(OwnedValue::Int(3)) => {}
);
}
#[test]
fn test_foreach() {
query!(br#"[1, 2, 3]"#, r#"[foreach .[] as $x (0; . + $x)]"#,
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr.len(), 3);
assert_eq!(arr[0], OwnedValue::Int(1));
assert_eq!(arr[1], OwnedValue::Int(3));
assert_eq!(arr[2], OwnedValue::Int(6));
}
);
}
#[test]
fn test_range() {
query!(br#"null"#, r#"[range(5)]"#,
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr, vec![
OwnedValue::Int(0),
OwnedValue::Int(1),
OwnedValue::Int(2),
OwnedValue::Int(3),
OwnedValue::Int(4),
]);
}
);
query!(br#"null"#, r#"[range(2;5)]"#,
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr, vec![
OwnedValue::Int(2),
OwnedValue::Int(3),
OwnedValue::Int(4),
]);
}
);
query!(br#"null"#, r#"[range(0;10;2)]"#,
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr, vec![
OwnedValue::Int(0),
OwnedValue::Int(2),
OwnedValue::Int(4),
OwnedValue::Int(6),
OwnedValue::Int(8),
]);
}
);
}
#[test]
fn test_limit() {
query!(br#"null"#, r#"[limit(3; range(10))]"#,
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr, vec![
OwnedValue::Int(0),
OwnedValue::Int(1),
OwnedValue::Int(2),
]);
}
);
}
#[test]
fn test_first_last_expr() {
query!(br#"[1, 2, 3]"#, r#"first(.[])"#,
QueryResult::One(StandardJson::Number(n)) => {
assert_eq!(n.as_i64().unwrap(), 1);
}
);
query!(br#"[1, 2, 3]"#, r#"last(.[])"#,
QueryResult::One(StandardJson::Number(n)) => {
assert_eq!(n.as_i64().unwrap(), 3);
}
);
}
#[test]
fn test_until() {
query!(br#"1"#, r#"until(. >= 10; . * 2)"#,
QueryResult::Owned(OwnedValue::Int(16)) => {}
);
}
#[test]
fn test_while() {
query!(br#"1"#, r#"[while(. < 10; . * 2)]"#,
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr, vec![
OwnedValue::Int(1),
OwnedValue::Int(2),
OwnedValue::Int(4),
OwnedValue::Int(8),
]);
}
);
}
#[test]
fn test_repeat() {
query!(br#"1"#, r#"[limit(5; repeat(. * 2))]"#,
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr, vec![
OwnedValue::Int(2),
OwnedValue::Int(2),
OwnedValue::Int(2),
OwnedValue::Int(2),
OwnedValue::Int(2),
]);
}
);
}
#[test]
fn test_repeat_identity() {
query!(br#""hello""#, r#"[limit(3; repeat(.))]"#,
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr, vec![
OwnedValue::String("hello".into()),
OwnedValue::String("hello".into()),
OwnedValue::String("hello".into()),
]);
}
);
}
#[test]
fn test_recurse() {
query!(br#"{"a": 1, "b": {"c": 2}}"#, r#"[recurse | .a? // .c? // empty]"#,
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert!(arr.len() >= 2);
}
);
}
#[test]
fn test_isvalid() {
query!(br#"{"a": 1}"#, r#"isvalid(.a)"#,
QueryResult::Owned(OwnedValue::Bool(true)) => {}
);
query!(br#"{"a": 1}"#, r#"isvalid(.b)"#,
QueryResult::Owned(OwnedValue::Bool(true)) => {}
);
query!(br#"123"#, r#"isvalid(.foo)"#,
QueryResult::Owned(OwnedValue::Bool(false)) => {}
);
}
#[test]
fn test_destructuring_object_pattern() {
query!(br#"{"name": "Alice", "age": 30}"#, r#". as {name: $n, age: $a} | "\($n) is \($a)""#,
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "Alice is 30");
}
);
}
#[test]
fn test_destructuring_array_pattern() {
query!(br#"[1, 2, 3]"#, r#". as [$a, $b] | $a + $b"#,
QueryResult::Owned(OwnedValue::Int(3)) => {}
);
}
#[test]
fn test_destructuring_nested_pattern() {
query!(br#"{"user": {"name": "Bob", "id": 42}}"#, r#". as {user: {name: $n}} | $n"#,
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "Bob");
}
);
}
#[test]
fn test_function_def_simple() {
query!(br#"5"#, r#"def double: . * 2; double"#,
QueryResult::Owned(OwnedValue::Int(10)) => {}
);
}
#[test]
fn test_function_def_with_params() {
query!(br#"null"#, r#"def addtwo(a; b): a + b; addtwo(3; 4)"#,
QueryResult::Owned(OwnedValue::Int(7)) => {}
);
}
#[test]
fn test_function_def_chained() {
query!(br#"5"#, r#"def inc: . + 1; . | inc"#,
QueryResult::Owned(OwnedValue::Int(6)) => {}
);
query!(br#"5"#, r#"def double: . * 2; def inc: . + 1; inc"#,
QueryResult::Owned(OwnedValue::Int(6)) => {}
);
query!(br#"5"#, r#"def double: . * 2; def inc: . + 1; double"#,
QueryResult::Owned(OwnedValue::Int(10)) => {}
);
query!(br#"5"#, r#"def inc: . + 1; def double: . * 2; double | inc"#,
QueryResult::Owned(OwnedValue::Int(11)) => {}
);
}
#[test]
fn test_function_uses_input() {
query!(br#"{"x": 10}"#, r#"def getx: .x; getx"#,
QueryResult::One(StandardJson::Number(n)) => {
assert_eq!(n.as_i64().unwrap(), 10);
}
);
}
#[test]
fn test_function_with_filter_param() {
query!(br#"[1, 2, 3]"#, r#"def apply(f): map(f); apply(. * 2)"#,
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr, vec![
OwnedValue::Int(2),
OwnedValue::Int(4),
OwnedValue::Int(6),
]);
}
);
}
#[test]
fn test_floor() {
query!(b"3.7", "floor", QueryResult::Owned(OwnedValue::Int(n)) => {
assert_eq!(n, 3);
});
query!(b"-3.2", "floor", QueryResult::Owned(OwnedValue::Int(n)) => {
assert_eq!(n, -4);
});
}
#[test]
fn test_ceil() {
query!(b"3.2", "ceil", QueryResult::Owned(OwnedValue::Int(n)) => {
assert_eq!(n, 4);
});
query!(b"-3.7", "ceil", QueryResult::Owned(OwnedValue::Int(n)) => {
assert_eq!(n, -3);
});
}
#[test]
fn test_round() {
query!(b"3.4", "round", QueryResult::Owned(OwnedValue::Int(n)) => {
assert_eq!(n, 3);
});
query!(b"3.5", "round", QueryResult::Owned(OwnedValue::Int(n)) => {
assert_eq!(n, 4);
});
}
#[test]
fn test_sqrt() {
query!(b"9", "sqrt", QueryResult::Owned(OwnedValue::Float(n)) => {
assert!((n - 3.0).abs() < f64::EPSILON);
});
query!(b"2", "sqrt", QueryResult::Owned(OwnedValue::Float(n)) => {
assert!((n - core::f64::consts::SQRT_2).abs() < 1e-10);
});
}
#[test]
fn test_fabs() {
query!(b"-5", "fabs", QueryResult::Owned(OwnedValue::Float(n)) => {
assert!((n - 5.0).abs() < f64::EPSILON);
});
query!(b"5", "fabs", QueryResult::Owned(OwnedValue::Float(n)) => {
assert!((n - 5.0).abs() < f64::EPSILON);
});
}
#[test]
fn test_log() {
query!(b"2.718281828459045", "log", QueryResult::Owned(OwnedValue::Float(n)) => {
assert!((n - 1.0).abs() < 1e-10);
});
}
#[test]
fn test_exp() {
query!(b"1", "exp", QueryResult::Owned(OwnedValue::Float(n)) => {
assert!((n - core::f64::consts::E).abs() < 1e-10);
});
}
#[test]
fn test_pow() {
query!(b"2", "pow(.; 3)", QueryResult::Owned(OwnedValue::Float(n)) => {
assert!((n - 8.0).abs() < f64::EPSILON);
});
}
#[test]
fn test_sin_cos_tan() {
query!(b"0", "sin", QueryResult::Owned(OwnedValue::Float(n)) => {
assert!(n.abs() < f64::EPSILON);
});
query!(b"0", "cos", QueryResult::Owned(OwnedValue::Float(n)) => {
assert!((n - 1.0).abs() < f64::EPSILON);
});
query!(b"0", "tan", QueryResult::Owned(OwnedValue::Float(n)) => {
assert!(n.abs() < f64::EPSILON);
});
}
#[test]
fn test_infinite_nan() {
query!(b"null", "infinite", QueryResult::Owned(OwnedValue::Float(n)) => {
assert!(n.is_infinite() && n > 0.0);
});
query!(b"null", "nan", QueryResult::Owned(OwnedValue::Float(n)) => {
assert!(n.is_nan());
});
}
#[test]
fn test_isinfinite_isnan_isnormal() {
query!(b"1.0", "isinfinite", QueryResult::Owned(OwnedValue::Bool(b)) => {
assert!(!b);
});
query!(b"1.0", "isnan", QueryResult::Owned(OwnedValue::Bool(b)) => {
assert!(!b);
});
query!(b"1.0", "isnormal", QueryResult::Owned(OwnedValue::Bool(b)) => {
assert!(b);
});
query!(b"1.0", "isfinite", QueryResult::Owned(OwnedValue::Bool(b)) => {
assert!(b);
});
}
#[test]
fn test_trim() {
query!(br#"" hello world ""#, "trim", QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "hello world");
});
}
#[test]
fn test_ltrim() {
query!(br#"" hello""#, "ltrim", QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "hello");
});
}
#[test]
fn test_rtrim() {
query!(br#""hello ""#, "rtrim", QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "hello");
});
}
#[test]
fn test_transpose() {
query!(br#"[[1, 2], [3, 4], [5, 6]]"#, "transpose",
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr.len(), 2);
match &arr[0] {
OwnedValue::Array(inner) => {
assert_eq!(inner.len(), 3);
assert_eq!(inner[0], OwnedValue::Int(1));
assert_eq!(inner[1], OwnedValue::Int(3));
assert_eq!(inner[2], OwnedValue::Int(5));
}
_ => panic!("expected array"),
}
}
);
}
#[test]
fn test_bsearch() {
query!(br#"[1, 2, 3, 4, 5]"#, "bsearch(3)",
QueryResult::Owned(OwnedValue::Int(idx)) => {
assert_eq!(idx, 2);
}
);
query!(br#"[1, 2, 4, 5]"#, "bsearch(3)",
QueryResult::Owned(OwnedValue::Object(obj)) => {
assert_eq!(obj.get("index"), Some(&OwnedValue::Int(2)));
}
);
}
#[test]
fn test_paths() {
query!(br#"{"a": 1, "b": {"c": 2}}"#, "paths",
QueryResult::ManyOwned(paths) => {
assert_eq!(paths.len(), 3);
assert_eq!(paths[0], OwnedValue::Array(vec![OwnedValue::String("a".into())]));
assert_eq!(paths[1], OwnedValue::Array(vec![OwnedValue::String("b".into())]));
assert_eq!(paths[2], OwnedValue::Array(vec![
OwnedValue::String("b".into()),
OwnedValue::String("c".into())
]));
}
);
}
#[test]
fn test_paths_single() {
query!(br#"{"a": 1}"#, "paths",
QueryResult::Owned(OwnedValue::Array(path)) => {
assert_eq!(path, vec![OwnedValue::String("a".into())]);
}
);
}
#[test]
fn test_paths_collected() {
query!(br#"{"a": 1, "b": 2}"#, "[paths]",
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr.len(), 2);
}
);
}
#[test]
fn test_leaf_paths() {
query!(br#"{"a": 1, "b": {"c": 2}}"#, "leaf_paths",
QueryResult::ManyOwned(paths) => {
assert_eq!(paths.len(), 2);
assert_eq!(paths[0], OwnedValue::Array(vec![OwnedValue::String("a".into())]));
assert_eq!(paths[1], OwnedValue::Array(vec![
OwnedValue::String("b".into()),
OwnedValue::String("c".into())
]));
}
);
}
#[test]
fn test_leaf_paths_single() {
query!(br#"{"a": 1}"#, "leaf_paths",
QueryResult::Owned(OwnedValue::Array(path)) => {
assert_eq!(path, vec![OwnedValue::String("a".into())]);
}
);
}
#[test]
fn test_leaf_paths_array() {
query!(br#"[1, [2, 3]]"#, "[leaf_paths]",
QueryResult::Owned(OwnedValue::Array(paths)) => {
assert_eq!(paths.len(), 3);
}
);
}
#[test]
fn test_leaf_paths_with_null() {
query!(br#"{"a": null, "b": 1}"#, "[leaf_paths]",
QueryResult::Owned(OwnedValue::Array(paths)) => {
assert_eq!(paths.len(), 2);
}
);
}
#[test]
fn test_leaf_paths_empty_containers() {
query!(br#"{"a": [], "b": {}}"#, "[leaf_paths]",
QueryResult::Owned(OwnedValue::Array(paths)) => {
assert_eq!(paths.len(), 2);
}
);
}
#[test]
fn test_setpath() {
query!(br#"{"a": 1}"#, r#"setpath(["b"]; 2)"#,
QueryResult::Owned(OwnedValue::Object(obj)) => {
assert_eq!(obj.get("a"), Some(&OwnedValue::Int(1)));
assert_eq!(obj.get("b"), Some(&OwnedValue::Int(2)));
}
);
}
#[test]
fn test_delpaths() {
query!(br#"{"a": 1, "b": 2}"#, r#"delpaths([["b"]])"#,
QueryResult::Owned(OwnedValue::Object(obj)) => {
assert_eq!(obj.get("a"), Some(&OwnedValue::Int(1)));
assert_eq!(obj.get("b"), None);
}
);
}
#[test]
fn test_debug() {
query!(b"42", "debug", QueryResult::Owned(OwnedValue::Int(n)) => {
assert_eq!(n, 42);
});
}
#[test]
fn test_log10_log2() {
query!(b"100", "log10", QueryResult::Owned(OwnedValue::Float(n)) => {
assert!((n - 2.0).abs() < 1e-10);
});
query!(b"8", "log2", QueryResult::Owned(OwnedValue::Float(n)) => {
assert!((n - 3.0).abs() < 1e-10);
});
}
#[test]
fn test_exp10_exp2() {
query!(b"2", "exp10", QueryResult::Owned(OwnedValue::Float(n)) => {
assert!((n - 100.0).abs() < 1e-10);
});
query!(b"3", "exp2", QueryResult::Owned(OwnedValue::Float(n)) => {
assert!((n - 8.0).abs() < 1e-10);
});
}
#[test]
fn test_asin_acos_atan() {
query!(b"0", "asin", QueryResult::Owned(OwnedValue::Float(n)) => {
assert!(n.abs() < 1e-10);
});
query!(b"1", "acos", QueryResult::Owned(OwnedValue::Float(n)) => {
assert!(n.abs() < 1e-10);
});
query!(b"0", "atan", QueryResult::Owned(OwnedValue::Float(n)) => {
assert!(n.abs() < 1e-10);
});
}
#[test]
fn test_atan2() {
query!(b"1", "atan2(1; 1)", QueryResult::Owned(OwnedValue::Float(n)) => {
assert!((n - core::f64::consts::FRAC_PI_4).abs() < 1e-10);
});
}
#[test]
fn test_sinh_cosh_tanh() {
query!(b"0", "sinh", QueryResult::Owned(OwnedValue::Float(n)) => {
assert!(n.abs() < 1e-10);
});
query!(b"0", "cosh", QueryResult::Owned(OwnedValue::Float(n)) => {
assert!((n - 1.0).abs() < 1e-10);
});
query!(b"0", "tanh", QueryResult::Owned(OwnedValue::Float(n)) => {
assert!(n.abs() < 1e-10);
});
}
#[test]
fn test_asinh_acosh_atanh() {
query!(b"0", "asinh", QueryResult::Owned(OwnedValue::Float(n)) => {
assert!(n.abs() < 1e-10);
});
query!(b"1", "acosh", QueryResult::Owned(OwnedValue::Float(n)) => {
assert!(n.abs() < 1e-10);
});
query!(b"0", "atanh", QueryResult::Owned(OwnedValue::Float(n)) => {
assert!(n.abs() < 1e-10);
});
}
#[test]
fn test_env() {
query!(b"null", "env", QueryResult::Owned(OwnedValue::Object(obj)) => {
#[cfg(feature = "std")]
assert!(!obj.is_empty(), "env should return non-empty object in std context");
#[cfg(not(feature = "std"))]
assert!(obj.is_empty(), "env should return empty object in no_std context");
});
}
#[test]
fn test_dollar_env() {
query!(b"null", "$ENV", QueryResult::Owned(OwnedValue::Object(obj)) => {
#[cfg(feature = "std")]
assert!(!obj.is_empty(), "$ENV should return non-empty object in std context");
#[cfg(not(feature = "std"))]
assert!(obj.is_empty(), "$ENV should return empty object in no_std context");
});
}
#[test]
fn test_dollar_env_field_access() {
query!(b"null", "$ENV.PATH", QueryResult::Owned(OwnedValue::String(s)) => {
#[cfg(feature = "std")]
assert!(!s.is_empty(), "PATH should be non-empty");
});
}
#[test]
fn test_dollar_env_missing_var() {
query!(b"null", "$ENV.NONEXISTENT_VAR_12345",
QueryResult::Owned(OwnedValue::Null) => {}
);
query!(b"null", "$ENV.NONEXISTENT_VAR_12345?",
QueryResult::Owned(OwnedValue::Null) => {}
);
}
#[test]
fn test_dollar_env_bracket_access() {
query!(b"null", r#"$ENV["PATH"]"#, QueryResult::Owned(OwnedValue::String(s)) => {
#[cfg(feature = "std")]
assert!(!s.is_empty(), "PATH should be non-empty");
});
}
#[test]
fn test_env_var() {
query!(b"null", "env(PATH)", QueryResult::Owned(OwnedValue::String(s)) => {
#[cfg(feature = "std")]
assert!(!s.is_empty(), "PATH should be non-empty");
});
}
#[test]
fn test_strenv() {
query!(b"null", "strenv(PATH)", QueryResult::Owned(OwnedValue::String(s)) => {
#[cfg(feature = "std")]
assert!(!s.is_empty(), "PATH should be non-empty");
});
}
#[test]
fn test_env_field_access() {
query!(b"null", "env.PATH", QueryResult::Owned(OwnedValue::String(s)) => {
#[cfg(feature = "std")]
assert!(!s.is_empty(), "PATH should be non-empty");
});
}
#[test]
fn test_env_bracket_access() {
query!(b"null", r#"env["PATH"]"#, QueryResult::Owned(OwnedValue::String(s)) => {
#[cfg(feature = "std")]
assert!(!s.is_empty(), "PATH should be non-empty");
});
}
#[test]
fn test_env_missing_var() {
query!(b"null", "env.NONEXISTENT_VAR_12345?",
QueryResult::Owned(OwnedValue::Null) => {}
);
}
#[test]
fn test_null_literal() {
query!(b"42", "null", QueryResult::Owned(OwnedValue::Null) => {});
}
#[test]
fn test_modulemeta() {
query!(b"null", r#"modulemeta("test")"#, QueryResult::Owned(OwnedValue::Null) => {});
}
#[test]
fn test_path_expr() {
query!(br#"{"a": 1, "b": 2}"#, "path(.a)",
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr.len(), 1);
assert_eq!(arr[0], OwnedValue::String("a".into()));
}
);
query!(br#"{"a": {"b": {"c": 1}}}"#, "path(.a.b.c)",
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr.len(), 3);
assert_eq!(arr[0], OwnedValue::String("a".into()));
assert_eq!(arr[1], OwnedValue::String("b".into()));
assert_eq!(arr[2], OwnedValue::String("c".into()));
}
);
query!(br#"[10, 20, 30]"#, "path(.[1])",
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr.len(), 1);
assert_eq!(arr[0], OwnedValue::Int(1));
}
);
query!(br#"[10, 20, 30]"#, "path(.[-1])",
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr.len(), 1);
assert_eq!(arr[0], OwnedValue::Int(-1));
}
);
query!(br#"{"a": 1}"#, "path(.)",
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert!(arr.is_empty()); }
);
}
#[test]
fn test_paths_filter() {
query!(br#"{"a": 1, "b": "hello", "c": 2}"#, "paths(type == \"number\")",
QueryResult::ManyOwned(paths) => {
assert_eq!(paths.len(), 2);
assert_eq!(paths[0], OwnedValue::Array(vec![OwnedValue::String("a".into())]));
assert_eq!(paths[1], OwnedValue::Array(vec![OwnedValue::String("c".into())]));
}
);
}
#[test]
fn test_paths_filter_single() {
query!(br#"{"a": 1, "b": "hello"}"#, "paths(type == \"number\")",
QueryResult::Owned(OwnedValue::Array(path)) => {
assert_eq!(path, vec![OwnedValue::String("a".into())]);
}
);
}
#[test]
fn test_paths_filter_none() {
query!(br#"{"a": "x", "b": "y"}"#, "paths(type == \"number\")",
QueryResult::None => {}
);
}
#[test]
fn test_paths_filter_collected() {
query!(br#"{"a": 1, "b": "hello", "c": 2}"#, "[paths(type == \"number\")]",
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr.len(), 2);
}
);
}
#[test]
fn test_walk() {
query!(br#"{"a": 1, "b": [2, 3]}"#, "walk(if type == \"number\" then . + 10 else . end)",
QueryResult::Owned(OwnedValue::Object(obj)) => {
assert_eq!(obj.get("a"), Some(&OwnedValue::Int(11)));
match obj.get("b") {
Some(OwnedValue::Array(arr)) => {
assert_eq!(arr[0], OwnedValue::Int(12));
assert_eq!(arr[1], OwnedValue::Int(13));
}
_ => panic!("expected array for b"),
}
}
);
}
#[test]
fn test_tojsonstream() {
query!(br#"{"a": 1}"#, "tojsonstream",
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert!(!arr.is_empty());
}
);
}
#[test]
fn test_fromjsonstream() {
query!(br#"[[["a"], 1]]"#, "fromjsonstream",
QueryResult::Owned(OwnedValue::Array(_)) => {
}
);
}
#[test]
fn test_getpath() {
query!(br#"{"a": {"b": 42}}"#, r#"getpath(["a", "b"])"#,
QueryResult::Owned(OwnedValue::Int(n)) => {
assert_eq!(n, 42);
}
);
query!(br#"{"a": 1}"#, r#"getpath(["missing"])"#,
QueryResult::Owned(OwnedValue::Null) => {}
);
}
#[test]
fn test_debug_msg() {
query!(b"42", r#"debug("test message")"#, QueryResult::Owned(OwnedValue::Int(n)) => {
assert_eq!(n, 42);
});
}
#[test]
fn test_gmtime() {
query!(b"0", r#"gmtime"#,
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr.len(), 8);
assert_eq!(arr[0], OwnedValue::Int(1970)); assert_eq!(arr[1], OwnedValue::Int(0)); assert_eq!(arr[2], OwnedValue::Int(1)); assert_eq!(arr[3], OwnedValue::Int(0)); assert_eq!(arr[4], OwnedValue::Int(0)); assert_eq!(arr[5], OwnedValue::Int(0)); assert_eq!(arr[6], OwnedValue::Int(4)); assert_eq!(arr[7], OwnedValue::Int(0)); }
);
}
#[test]
fn test_mktime() {
query!(b"[1970,0,1,0,0,0,4,0]", r#"mktime"#,
QueryResult::Owned(OwnedValue::Float(n)) => {
assert_eq!(n, 0.0);
}
);
query!(b"[2024,0,15,10,30,0,1,14]", r#"mktime"#,
QueryResult::Owned(OwnedValue::Float(n)) => {
assert_eq!(n, 1705314600.0);
}
);
}
#[test]
fn test_strftime() {
query!(b"[2024,0,15,10,30,0,1,14]", r#"strftime("%Y-%m-%d")"#,
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "2024-01-15");
}
);
query!(b"[2024,0,15,10,30,0,1,14]", r#"strftime("%Y-%m-%dT%H:%M:%SZ")"#,
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "2024-01-15T10:30:00Z");
}
);
}
#[test]
fn test_strptime() {
query!(br#""2024-01-15T10:30:00Z""#, r#"strptime("%Y-%m-%dT%H:%M:%SZ")"#,
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr.len(), 8);
assert_eq!(arr[0], OwnedValue::Int(2024)); assert_eq!(arr[1], OwnedValue::Int(0)); assert_eq!(arr[2], OwnedValue::Int(15)); assert_eq!(arr[3], OwnedValue::Int(10)); assert_eq!(arr[4], OwnedValue::Int(30)); assert_eq!(arr[5], OwnedValue::Int(0)); }
);
}
#[test]
fn test_todate() {
query!(b"0", r#"todate"#,
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "1970-01-01T00:00:00Z");
}
);
}
#[test]
fn test_fromdate() {
query!(br#""1970-01-01T00:00:00Z""#, r#"fromdate"#,
QueryResult::Owned(OwnedValue::Float(n)) => {
assert_eq!(n, 0.0);
}
);
query!(br#""2024-01-15T10:30:00Z""#, r#"fromdate"#,
QueryResult::Owned(OwnedValue::Float(n)) => {
assert_eq!(n, 1705314600.0);
}
);
}
#[test]
fn test_date_roundtrip() {
query!(b"1705314600", r#"gmtime | mktime"#,
QueryResult::Owned(OwnedValue::Float(n)) => {
assert_eq!(n, 1705314600.0);
}
);
query!(b"1705314600", r#"todate | fromdate"#,
QueryResult::Owned(OwnedValue::Float(n)) => {
assert_eq!(n, 1705314600.0);
}
);
}
#[test]
fn test_from_unix() {
query!(b"0", r#"from_unix"#,
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "1970-01-01T00:00:00Z");
}
);
query!(b"1705314600", r#"from_unix"#,
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "2024-01-15T10:30:00Z");
}
);
}
#[test]
fn test_to_unix() {
query!(br#""1970-01-01T00:00:00Z""#, r#"to_unix"#,
QueryResult::Owned(OwnedValue::Float(n)) => {
assert_eq!(n, 0.0);
}
);
query!(br#""2024-01-15T10:30:00Z""#, r#"to_unix"#,
QueryResult::Owned(OwnedValue::Float(n)) => {
assert_eq!(n, 1705314600.0);
}
);
}
#[test]
fn test_from_unix_to_unix_roundtrip() {
query!(b"1705314600", r#"from_unix | to_unix"#,
QueryResult::Owned(OwnedValue::Float(n)) => {
assert_eq!(n, 1705314600.0);
}
);
}
#[test]
fn test_tz_utc() {
query!(b"1705314600", r#"tz("UTC")"#,
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "2024-01-15T10:30:00Z");
}
);
query!(b"1705314600", r#"tz("GMT")"#,
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "2024-01-15T10:30:00Z");
}
);
}
#[test]
fn test_tz_abbreviations() {
query!(b"1705314600", r#"tz("EST")"#,
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "2024-01-15T05:30:00-05:00");
}
);
query!(b"1705314600", r#"tz("PST")"#,
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "2024-01-15T02:30:00-08:00");
}
);
query!(b"1705314600", r#"tz("JST")"#,
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "2024-01-15T19:30:00+09:00");
}
);
query!(b"1705314600", r#"tz("CET")"#,
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "2024-01-15T11:30:00+01:00");
}
);
}
#[test]
fn test_tz_iana_names() {
query!(b"1705314600", r#"tz("Asia/Tokyo")"#,
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "2024-01-15T19:30:00+09:00");
}
);
query!(b"1705314600", r#"tz("Europe/Moscow")"#,
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "2024-01-15T13:30:00+03:00");
}
);
query!(b"1705314600", r#"tz("Pacific/Honolulu")"#,
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "2024-01-15T00:30:00-10:00");
}
);
}
#[test]
fn test_tz_numeric_offset() {
query!(b"1705314600", r#"tz("+05:30")"#,
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "2024-01-15T16:00:00+05:30");
}
);
query!(b"1705314600", r#"tz("-0800")"#,
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "2024-01-15T02:30:00-08:00");
}
);
query!(b"1705314600", r#"tz("+09")"#,
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "2024-01-15T19:30:00+09:00");
}
);
}
#[test]
fn test_tz_unknown_error() {
query!(b"1705314600", r#"tz("Unknown/Timezone")"#,
QueryResult::Error(e) => {
assert!(e.to_string().contains("unknown timezone"));
}
);
}
#[test]
fn test_tz_with_variable() {
query!(b"1705314600", r#""JST" as $tz | tz($tz)"#,
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "2024-01-15T19:30:00+09:00");
}
);
}
#[test]
fn test_simple_assign() {
query!(br#"{"a": 1, "b": 2}"#, r#".a = 42"#,
QueryResult::Owned(OwnedValue::Object(obj)) => {
let a = obj.get("a").unwrap();
assert_eq!(*a, OwnedValue::Int(42));
let b = obj.get("b").unwrap();
assert_eq!(*b, OwnedValue::Int(2));
}
);
}
#[test]
fn test_nested_assign() {
query!(br#"{"a": {"b": 1}}"#, r#".a.b = 99"#,
QueryResult::Owned(OwnedValue::Object(obj)) => {
let a = obj.get("a").unwrap();
if let OwnedValue::Object(inner) = a {
let b = inner.get("b").unwrap();
assert_eq!(*b, OwnedValue::Int(99));
} else {
panic!("Expected nested object");
}
}
);
}
#[test]
fn test_array_index_assign() {
query!(br#"[1, 2, 3]"#, r#".[1] = 99"#,
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr[0], OwnedValue::Int(1));
assert_eq!(arr[1], OwnedValue::Int(99));
assert_eq!(arr[2], OwnedValue::Int(3));
}
);
}
#[test]
fn test_update_assign() {
query!(br#"{"a": 5}"#, r#".a |= . + 1"#,
QueryResult::Owned(OwnedValue::Object(obj)) => {
let a = obj.get("a").unwrap();
assert_eq!(*a, OwnedValue::Int(6));
}
);
}
#[test]
fn test_update_assign_array() {
query!(br#"[1, 2, 3]"#, r#".[] |= . * 2"#,
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr[0], OwnedValue::Int(2));
assert_eq!(arr[1], OwnedValue::Int(4));
assert_eq!(arr[2], OwnedValue::Int(6));
}
);
}
#[test]
fn test_compound_assign_add() {
query!(br#"{"a": 5}"#, r#".a += 10"#,
QueryResult::Owned(OwnedValue::Object(obj)) => {
let a = obj.get("a").unwrap();
assert_eq!(*a, OwnedValue::Int(15));
}
);
}
#[test]
fn test_compound_assign_sub() {
query!(br#"{"a": 10}"#, r#".a -= 3"#,
QueryResult::Owned(OwnedValue::Object(obj)) => {
let a = obj.get("a").unwrap();
assert_eq!(*a, OwnedValue::Int(7));
}
);
}
#[test]
fn test_compound_assign_mul() {
query!(br#"{"a": 5}"#, r#".a *= 4"#,
QueryResult::Owned(OwnedValue::Object(obj)) => {
let a = obj.get("a").unwrap();
assert_eq!(*a, OwnedValue::Int(20));
}
);
}
#[test]
fn test_compound_assign_div() {
query!(br#"{"a": 10}"#, r#".a /= 2"#,
QueryResult::Owned(OwnedValue::Object(obj)) => {
let a = obj.get("a").unwrap();
match a {
OwnedValue::Float(f) => assert!((f - 5.0).abs() < 0.001),
OwnedValue::Int(i) => assert_eq!(*i, 5),
_ => panic!("Expected number, got {:?}", a),
}
}
);
}
#[test]
fn test_compound_assign_mod() {
query!(br#"{"a": 10}"#, r#".a %= 3"#,
QueryResult::Owned(OwnedValue::Object(obj)) => {
let a = obj.get("a").unwrap();
assert_eq!(*a, OwnedValue::Int(1));
}
);
}
#[test]
fn test_alternative_assign() {
query!(br#"{"a": null}"#, r#".a //= "default""#,
QueryResult::Owned(OwnedValue::Object(obj)) => {
let a = obj.get("a").unwrap();
assert_eq!(*a, OwnedValue::String("default".to_string()));
}
);
}
#[test]
fn test_alternative_assign_existing() {
query!(br#"{"a": "existing"}"#, r#".a //= "default""#,
QueryResult::Owned(OwnedValue::Object(obj)) => {
let a = obj.get("a").unwrap();
assert_eq!(*a, OwnedValue::String("existing".to_string()));
}
);
}
#[test]
fn test_del_field() {
query!(br#"{"a": 1, "b": 2}"#, r#"del(.a)"#,
QueryResult::Owned(OwnedValue::Object(obj)) => {
assert!(!obj.contains_key("a"));
assert!(obj.contains_key("b"));
}
);
}
#[test]
fn test_del_array_element() {
query!(br#"[1, 2, 3]"#, r#"del(.[1])"#,
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr.len(), 2);
assert_eq!(arr[0], OwnedValue::Int(1));
assert_eq!(arr[1], OwnedValue::Int(3));
}
);
}
#[test]
fn test_del_nested() {
query!(br#"{"a": {"b": 1, "c": 2}}"#, r#"del(.a.b)"#,
QueryResult::Owned(OwnedValue::Object(obj)) => {
let a = obj.get("a").unwrap();
if let OwnedValue::Object(inner) = a {
assert!(!inner.contains_key("b"));
assert!(inner.contains_key("c"));
} else {
panic!("Expected nested object");
}
}
);
}
#[test]
fn test_chained_assign() {
query!(br#"{"a": 0, "b": 0}"#, r#".a = 1 | .b = 2"#,
QueryResult::Owned(OwnedValue::Object(obj)) => {
let a = obj.get("a").unwrap();
assert_eq!(*a, OwnedValue::Int(1));
let b = obj.get("b").unwrap();
assert_eq!(*b, OwnedValue::Int(2));
}
);
}
#[test]
fn test_tag_null() {
query!(br#"null"#, "tag",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "!!null");
}
);
}
#[test]
fn test_tag_bool() {
query!(br#"true"#, "tag",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "!!bool");
}
);
query!(br#"false"#, "tag",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "!!bool");
}
);
}
#[test]
fn test_tag_int() {
query!(br#"42"#, "tag",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "!!int");
}
);
}
#[test]
fn test_tag_float() {
query!(br#"3.14"#, "tag",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "!!float");
}
);
}
#[test]
fn test_tag_string() {
query!(br#""hello""#, "tag",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "!!str");
}
);
}
#[test]
fn test_tag_array() {
query!(br#"[1, 2, 3]"#, "tag",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "!!seq");
}
);
}
#[test]
fn test_tag_object() {
query!(br#"{"a": 1}"#, "tag",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "!!map");
}
);
}
#[test]
fn test_tag_nested() {
query!(br#"{"value": 42}"#, ".value | tag",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "!!int");
}
);
}
#[test]
fn test_anchor_returns_empty() {
query!(br#"{"a": 1}"#, "anchor",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "");
}
);
}
#[test]
fn test_style_returns_empty() {
query!(br#""hello""#, "style",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "");
}
);
query!(br#"[1, 2, 3]"#, "style",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "");
}
);
query!(br#"{"a": 1}"#, "style",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "");
}
);
}
#[test]
fn test_tag_with_map() {
query!(br#"[1, "hello", true, null]"#, "map(tag)",
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr.len(), 4);
assert_eq!(arr[0], OwnedValue::String("!!int".to_string()));
assert_eq!(arr[1], OwnedValue::String("!!str".to_string()));
assert_eq!(arr[2], OwnedValue::String("!!bool".to_string()));
assert_eq!(arr[3], OwnedValue::String("!!null".to_string()));
}
);
}
#[test]
fn test_kind_null() {
query!(b"null", "kind",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "scalar");
}
);
}
#[test]
fn test_kind_bool() {
query!(b"true", "kind",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "scalar");
}
);
query!(b"false", "kind",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "scalar");
}
);
}
#[test]
fn test_kind_number() {
query!(b"42", "kind",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "scalar");
}
);
query!(b"3.14", "kind",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "scalar");
}
);
}
#[test]
fn test_kind_string() {
query!(br#""hello""#, "kind",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "scalar");
}
);
}
#[test]
fn test_kind_array() {
query!(b"[1, 2, 3]", "kind",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "seq");
}
);
query!(b"[]", "kind",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "seq");
}
);
}
#[test]
fn test_kind_object() {
query!(br#"{"a": 1}"#, "kind",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "map");
}
);
query!(b"{}", "kind",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "map");
}
);
}
#[test]
fn test_kind_nested() {
query!(br#"{"items": [1, 2]}"#, ".items | kind",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "seq");
}
);
query!(br#"[{"a": 1}]"#, ".[0] | kind",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "map");
}
);
}
#[test]
fn test_kind_with_map() {
query!(br#"[1, "hello", [1,2], {"a": 1}]"#, "map(kind)",
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr.len(), 4);
assert_eq!(arr[0], OwnedValue::String("scalar".to_string()));
assert_eq!(arr[1], OwnedValue::String("scalar".to_string()));
assert_eq!(arr[2], OwnedValue::String("seq".to_string()));
assert_eq!(arr[3], OwnedValue::String("map".to_string()));
}
);
}
#[test]
fn test_key_object() {
query!(br#"{"a": 1, "b": 2, "c": 3}"#, ".[] | key",
QueryResult::ManyOwned(results) => {
assert_eq!(results.len(), 3);
let keys: Vec<String> = results.iter().filter_map(|v| {
if let OwnedValue::String(s) = v {
Some(s.clone())
} else {
None
}
}).collect();
assert!(keys.contains(&"a".to_string()));
assert!(keys.contains(&"b".to_string()));
assert!(keys.contains(&"c".to_string()));
}
);
}
#[test]
fn test_key_array() {
query!(b"[10, 20, 30]", ".[] | key",
QueryResult::ManyOwned(results) => {
assert_eq!(results.len(), 3);
let indices: Vec<i64> = results.iter().filter_map(|v| {
if let OwnedValue::Int(i) = v {
Some(*i)
} else {
None
}
}).collect();
assert_eq!(indices, vec![0, 1, 2]);
}
);
}
#[test]
fn test_key_at_root() {
query!(br#"{"a": 1}"#, "key",
QueryResult::Owned(OwnedValue::Null) => {}
);
}
#[test]
fn test_key_nested() {
query!(br#"{"outer": {"inner": 42}}"#, ".outer | .[] | key",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "inner");
}
);
}
#[test]
fn test_now() {
query!(b"null", "now",
QueryResult::Owned(OwnedValue::Float(n)) => {
assert!(n > 1577836800.0, "timestamp should be after 2020");
assert!(n < 4102444800.0, "timestamp should be before 2100");
}
);
}
#[test]
fn test_abs() {
query!(b"-5", "abs", QueryResult::Owned(OwnedValue::Float(n)) => {
assert!((n - 5.0).abs() < f64::EPSILON);
});
query!(b"5", "abs", QueryResult::Owned(OwnedValue::Float(n)) => {
assert!((n - 5.0).abs() < f64::EPSILON);
});
query!(b"-7.25", "abs", QueryResult::Owned(OwnedValue::Float(n)) => {
assert!((n - 7.25).abs() < f64::EPSILON);
});
}
#[test]
fn test_builtins() {
query!(b"null", "builtins | length",
QueryResult::Owned(OwnedValue::Int(n)) => {
assert!(n > 100, "should have many builtins, got {}", n);
}
);
query!(b"null", r#"builtins | map(select(startswith("now"))) | length"#,
QueryResult::Owned(OwnedValue::Int(n)) => {
assert!(n >= 1, "should have now builtin");
}
);
}
#[test]
fn test_normals() {
query!(b"5", "normals", QueryResult::One(_) => {});
query!(b"-3.14", "normals", QueryResult::One(_) => {});
query!(b"0", "normals", QueryResult::None => {}); query!(b"null", "normals", QueryResult::None => {}); query!(br#""string""#, "normals", QueryResult::None => {}); }
#[test]
fn test_finites() {
query!(b"5", "finites", QueryResult::One(_) => {});
query!(b"-3.14", "finites", QueryResult::One(_) => {});
query!(b"0", "finites", QueryResult::One(_) => {}); query!(b"null", "finites", QueryResult::None => {}); query!(br#""string""#, "finites", QueryResult::None => {}); }
#[test]
fn test_format_urid() {
query!(br#""hello%20world""#, "@urid",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "hello world");
}
);
query!(br#""foo%2Fbar""#, "@urid",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "foo/bar");
}
);
query!(br#""hello world""#, "@uri | @urid",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "hello world");
}
);
query!(br#""hello%GGworld""#, "@urid",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "hello%GGworld");
}
);
}
#[test]
fn test_loc_basic() {
query!(b"null", "$__loc__",
QueryResult::Owned(OwnedValue::Object(obj)) => {
assert_eq!(obj.get("file"), Some(&OwnedValue::String("<stdin>".into())));
assert_eq!(obj.get("line"), Some(&OwnedValue::Int(1)));
}
);
}
#[test]
fn test_loc_line_number() {
query!(b"null", "$__loc__.line",
QueryResult::Owned(OwnedValue::Int(n)) => {
assert_eq!(n, 1);
}
);
}
#[test]
fn test_loc_file() {
query!(b"null", "$__loc__.file",
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "<stdin>");
}
);
}
#[test]
fn test_loc_multiline() {
let filter = ".\n| $__loc__.line";
let json = b"null";
let index = crate::json::JsonIndex::build(json);
let cursor = index.root(json);
let expr = crate::jq::parse(filter).unwrap();
match eval::<Vec<u64>, JqSemantics>(&expr, cursor) {
QueryResult::Owned(OwnedValue::Int(n)) => {
assert_eq!(n, 2, "expected line 2 for $__loc__ on second line");
}
other => panic!("expected Int, got {:?}", other),
}
}
#[test]
fn test_loc_multiline_line3() {
let filter = ".\n|\n$__loc__.line";
let json = b"null";
let index = crate::json::JsonIndex::build(json);
let cursor = index.root(json);
let expr = crate::jq::parse(filter).unwrap();
match eval::<Vec<u64>, JqSemantics>(&expr, cursor) {
QueryResult::Owned(OwnedValue::Int(n)) => {
assert_eq!(n, 3, "expected line 3 for $__loc__ on third line");
}
other => panic!("expected Int, got {:?}", other),
}
}
#[test]
fn test_loc_in_function_def() {
let filter = "def f: $__loc__.line; f";
let json = b"null";
let index = crate::json::JsonIndex::build(json);
let cursor = index.root(json);
let expr = crate::jq::parse(filter).unwrap();
match eval::<Vec<u64>, JqSemantics>(&expr, cursor) {
QueryResult::Owned(OwnedValue::Int(n)) => {
assert_eq!(n, 1, "expected line 1 for $__loc__ in function on line 1");
}
other => panic!("expected Int, got {:?}", other),
}
}
#[test]
fn test_limit_stream() {
query!(b"[1, 2, 3, 4, 5]", "[limit(2; .[])]",
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr.len(), 2);
assert_eq!(arr[0], OwnedValue::Int(1));
assert_eq!(arr[1], OwnedValue::Int(2));
}
);
}
#[test]
fn test_limit_zero() {
query!(b"[1, 2, 3]", "[limit(0; .[])]",
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr.len(), 0);
}
);
}
#[test]
fn test_limit_exceeds() {
query!(b"[1, 2]", "[limit(10; .[])]",
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr.len(), 2);
assert_eq!(arr[0], OwnedValue::Int(1));
assert_eq!(arr[1], OwnedValue::Int(2));
}
);
}
#[test]
fn test_first_stream() {
query!(b"[1, 2, 3]", "first(.[])",
QueryResult::One(StandardJson::Number(n)) => {
assert_eq!(n.as_i64().unwrap(), 1);
}
);
}
#[test]
fn test_first_no_arg() {
query!(b"[1, 2, 3]", "first",
QueryResult::One(StandardJson::Number(n)) => {
assert_eq!(n.as_i64().unwrap(), 1);
}
);
}
#[test]
fn test_last_stream() {
query!(b"[1, 2, 3]", "last(.[])",
QueryResult::One(StandardJson::Number(n)) => {
assert_eq!(n.as_i64().unwrap(), 3);
}
);
}
#[test]
fn test_last_no_arg() {
query!(b"[1, 2, 3]", "last",
QueryResult::Owned(OwnedValue::Int(n)) => {
assert_eq!(n, 3);
}
);
}
#[test]
fn test_nth_stream() {
query!(b"[10, 20, 30]", "nth(1; .[])",
QueryResult::Owned(OwnedValue::Int(n)) => {
assert_eq!(n, 20);
}
);
}
#[test]
fn test_nth_no_arg() {
query!(b"[10, 20, 30]", "nth(1)",
QueryResult::One(StandardJson::Number(n)) => {
assert_eq!(n.as_i64().unwrap(), 20);
}
);
}
#[test]
fn test_range_simple() {
query!(b"null", "[range(3)]",
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr.len(), 3);
assert_eq!(arr[0], OwnedValue::Int(0));
assert_eq!(arr[1], OwnedValue::Int(1));
assert_eq!(arr[2], OwnedValue::Int(2));
}
);
}
#[test]
fn test_range_from_to() {
query!(b"null", "[range(2; 5)]",
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr.len(), 3);
assert_eq!(arr[0], OwnedValue::Int(2));
assert_eq!(arr[1], OwnedValue::Int(3));
assert_eq!(arr[2], OwnedValue::Int(4));
}
);
}
#[test]
fn test_range_from_to_by() {
query!(b"null", "[range(0; 10; 3)]",
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr.len(), 4);
assert_eq!(arr[0], OwnedValue::Int(0));
assert_eq!(arr[1], OwnedValue::Int(3));
assert_eq!(arr[2], OwnedValue::Int(6));
assert_eq!(arr[3], OwnedValue::Int(9));
}
);
}
#[test]
fn test_range_negative_step() {
query!(b"null", "[range(5; 0; -2)]",
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr.len(), 3);
assert_eq!(arr[0], OwnedValue::Int(5));
assert_eq!(arr[1], OwnedValue::Int(3));
assert_eq!(arr[2], OwnedValue::Int(1));
}
);
}
#[test]
fn test_isempty_false() {
query!(b"[1, 2, 3]", "isempty(.[])",
QueryResult::Owned(OwnedValue::Bool(b)) => {
assert!(!b);
}
);
}
#[test]
fn test_isempty_true() {
query!(b"[]", "isempty(.[])",
QueryResult::Owned(OwnedValue::Bool(b)) => {
assert!(b);
}
);
}
#[test]
fn test_first_with_select() {
query!(b"[1, 2, 3, 4, 5]", "first(.[] | select(. > 2))",
QueryResult::One(StandardJson::Number(n)) => {
assert_eq!(n.as_i64().unwrap(), 3);
}
);
}
#[test]
fn test_recurse_down() {
query!(br#"{"a": 1}"#, "[recurse_down]",
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr.len(), 2);
}
);
}
#[test]
fn test_recurse_with_filter() {
query!(br#"{"name": "root", "children": [{"name": "a", "children": [{"name": "b"}]}, {"name": "c"}]}"#,
"[recurse(.children[]?) | .name]",
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr.len(), 4);
}
);
}
#[test]
fn test_recurse_with_condition() {
query!(b"1", "[recurse(. + 1; . < 5)]",
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr.len(), 4);
assert_eq!(arr[0], OwnedValue::Int(1));
assert_eq!(arr[3], OwnedValue::Int(4));
}
);
}
#[test]
fn test_walk_strings() {
query!(br#"{"name": "alice", "nested": {"value": "bob"}}"#,
"walk(if type == \"string\" then ascii_upcase else . end)",
QueryResult::Owned(OwnedValue::Object(obj)) => {
assert_eq!(obj.get("name"), Some(&OwnedValue::String("ALICE".into())));
if let Some(OwnedValue::Object(nested)) = obj.get("nested") {
assert_eq!(nested.get("value"), Some(&OwnedValue::String("BOB".into())));
} else {
panic!("expected nested object");
}
}
);
}
#[test]
fn test_walk_arrays() {
query!(br#"{"items": [1, 2, 3], "nested": {"more": [4, 5]}}"#,
"walk(if type == \"array\" then reverse else . end)",
QueryResult::Owned(OwnedValue::Object(obj)) => {
if let Some(OwnedValue::Array(items)) = obj.get("items") {
assert_eq!(items[0], OwnedValue::Int(3));
assert_eq!(items[1], OwnedValue::Int(2));
assert_eq!(items[2], OwnedValue::Int(1));
} else {
panic!("expected items array");
}
}
);
}
#[test]
fn test_indices_string() {
query!(br#""abcabc""#, r#"indices("bc")"#,
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr, vec![OwnedValue::Int(1), OwnedValue::Int(4)]);
}
);
}
#[test]
fn test_indices_array() {
query!(br#"[1, 2, 3, 1, 2]"#, "indices(1)",
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr, vec![OwnedValue::Int(0), OwnedValue::Int(3)]);
}
);
}
#[test]
fn test_indices_array_string() {
query!(br#"["a", "b", "a", "c"]"#, r#"indices("a")"#,
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr, vec![OwnedValue::Int(0), OwnedValue::Int(2)]);
}
);
}
#[test]
fn test_indices_not_found() {
query!(br#""abc""#, r#"indices("xyz")"#,
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert!(arr.is_empty());
}
);
}
#[test]
fn test_index_string() {
query!(br#""abcabc""#, r#"index("bc")"#,
QueryResult::Owned(OwnedValue::Int(n)) => {
assert_eq!(n, 1);
}
);
}
#[test]
fn test_index_array() {
query!(br#"[1, 2, 3, 1, 2]"#, "index(2)",
QueryResult::Owned(OwnedValue::Int(n)) => {
assert_eq!(n, 1);
}
);
}
#[test]
fn test_index_not_found() {
query!(br#""abc""#, r#"index("xyz")"#,
QueryResult::Owned(OwnedValue::Null) => {}
);
}
#[test]
fn test_rindex_string() {
query!(br#""abcabc""#, r#"rindex("bc")"#,
QueryResult::Owned(OwnedValue::Int(n)) => {
assert_eq!(n, 4);
}
);
}
#[test]
fn test_rindex_array() {
query!(br#"[1, 2, 3, 1, 2]"#, "rindex(2)",
QueryResult::Owned(OwnedValue::Int(n)) => {
assert_eq!(n, 4);
}
);
}
#[test]
fn test_rindex_not_found() {
query!(br#""abc""#, r#"rindex("xyz")"#,
QueryResult::Owned(OwnedValue::Null) => {}
);
}
#[test]
fn test_indices_array_object() {
query!(br#"[{"a":1}, {"b":2}, {"a":1}]"#, r#"indices({"a":1})"#,
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr, vec![OwnedValue::Int(0), OwnedValue::Int(2)]);
}
);
}
#[test]
fn test_index_array_null() {
query!(br#"[1, null, 2, null]"#, "index(null)",
QueryResult::Owned(OwnedValue::Int(n)) => {
assert_eq!(n, 1);
}
);
}
#[test]
fn test_omit_object() {
query!(br#"{"a":1, "b":2, "c":3}"#, r#"omit(["a", "c"])"#,
QueryResult::Owned(OwnedValue::Object(obj)) => {
assert_eq!(obj.len(), 1);
assert_eq!(obj.get("b"), Some(&OwnedValue::Int(2)));
assert!(obj.get("a").is_none());
assert!(obj.get("c").is_none());
}
);
}
#[test]
fn test_omit_object_nonexistent_keys() {
query!(br#"{"a":1, "b":2}"#, r#"omit(["c", "d"])"#,
QueryResult::Owned(OwnedValue::Object(obj)) => {
assert_eq!(obj.len(), 2);
assert_eq!(obj.get("a"), Some(&OwnedValue::Int(1)));
assert_eq!(obj.get("b"), Some(&OwnedValue::Int(2)));
}
);
}
#[test]
fn test_omit_array() {
query!(br#"["a", "b", "c", "d"]"#, "omit([0, 2])",
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr.len(), 2);
assert_eq!(arr[0], OwnedValue::String("b".to_string()));
assert_eq!(arr[1], OwnedValue::String("d".to_string()));
}
);
}
#[test]
fn test_omit_array_negative_index() {
query!(br#"["a", "b", "c", "d"]"#, "omit([-1])",
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr.len(), 3);
assert_eq!(arr[0], OwnedValue::String("a".to_string()));
assert_eq!(arr[1], OwnedValue::String("b".to_string()));
assert_eq!(arr[2], OwnedValue::String("c".to_string()));
}
);
}
#[test]
fn test_omit_array_out_of_bounds() {
query!(br#"["a", "b", "c"]"#, "omit([10, -10])",
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr.len(), 3);
}
);
}
#[test]
fn test_omit_empty_keys() {
query!(br#"{"a":1, "b":2}"#, "omit([])",
QueryResult::Owned(OwnedValue::Object(obj)) => {
assert_eq!(obj.len(), 2);
}
);
}
#[test]
fn test_omit_all_keys() {
query!(br#"{"a":1, "b":2}"#, r#"omit(["a", "b"])"#,
QueryResult::Owned(OwnedValue::Object(obj)) => {
assert_eq!(obj.len(), 0);
}
);
}
#[test]
fn test_omit_preserves_order() {
query!(br#"{"c":3, "a":1, "b":2, "d":4}"#, r#"omit(["a", "d"])"#,
QueryResult::Owned(OwnedValue::Object(obj)) => {
let keys: Vec<&String> = obj.keys().collect();
assert_eq!(keys, vec!["c", "b"]);
}
);
}
#[test]
fn test_document_index_parses() {
let expr = crate::jq::parse("document_index").unwrap();
assert!(matches!(
expr,
crate::jq::Expr::Builtin(crate::jq::Builtin::DocumentIndex)
));
}
#[test]
fn test_di_parses() {
let expr = crate::jq::parse("di").unwrap();
assert!(matches!(
expr,
crate::jq::Expr::Builtin(crate::jq::Builtin::DocumentIndex)
));
}
#[test]
fn test_document_index_json_returns_zero() {
query!(br#"{"name": "test"}"#, "document_index",
QueryResult::Owned(OwnedValue::Int(0)) => {}
);
}
#[test]
fn test_di_json_returns_zero() {
query!(br#"[1, 2, 3]"#, "di",
QueryResult::Owned(OwnedValue::Int(0)) => {}
);
}
#[test]
fn test_document_index_in_select() {
let expr = crate::jq::parse("select(document_index == 0)").unwrap();
assert!(matches!(expr, crate::jq::Expr::Builtin(_)));
}
#[test]
fn test_shuffle_parses() {
let expr = crate::jq::parse("shuffle").unwrap();
assert!(matches!(
expr,
crate::jq::Expr::Builtin(crate::jq::Builtin::Shuffle)
));
}
#[test]
#[cfg(feature = "cli")]
fn test_shuffle_returns_array_same_length() {
query!(br#"[1, 2, 3, 4, 5]"#, "shuffle",
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr.len(), 5);
assert!(arr.contains(&OwnedValue::Int(1)));
assert!(arr.contains(&OwnedValue::Int(2)));
assert!(arr.contains(&OwnedValue::Int(3)));
assert!(arr.contains(&OwnedValue::Int(4)));
assert!(arr.contains(&OwnedValue::Int(5)));
}
);
}
#[test]
#[cfg(feature = "cli")]
fn test_shuffle_preserves_element_types() {
query!(br#"["a", 1, true, null]"#, "shuffle",
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr.len(), 4);
assert!(arr.contains(&OwnedValue::String("a".to_string())));
assert!(arr.contains(&OwnedValue::Int(1)));
assert!(arr.contains(&OwnedValue::Bool(true)));
assert!(arr.contains(&OwnedValue::Null));
}
);
}
#[test]
#[cfg(feature = "cli")]
fn test_shuffle_empty_array() {
query!(br#"[]"#, "shuffle",
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert!(arr.is_empty());
}
);
}
#[test]
#[cfg(feature = "cli")]
fn test_shuffle_single_element() {
query!(br#"[42]"#, "shuffle",
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr, vec![OwnedValue::Int(42)]);
}
);
}
#[test]
fn test_shuffle_type_error_on_non_array() {
query!(br#""not an array""#, "shuffle",
QueryResult::Error(err) => {
let msg = format!("{}", err);
assert!(msg.contains("array") || msg.contains("cli"));
}
);
}
#[test]
#[cfg(feature = "cli")]
fn test_shuffle_in_pipeline() {
query!(br#"[3, 1, 2]"#, "shuffle | length",
QueryResult::Owned(OwnedValue::Int(3)) => {}
);
}
#[test]
fn test_pivot_parses() {
let expr = crate::jq::parse("pivot").unwrap();
assert!(matches!(
expr,
crate::jq::Expr::Builtin(crate::jq::Builtin::Pivot)
));
}
#[test]
fn test_pivot_array_of_arrays() {
query!(br#"[[1, 2], [3, 4]]"#, "pivot",
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr.len(), 2);
assert_eq!(arr[0], OwnedValue::Array(vec![OwnedValue::Int(1), OwnedValue::Int(3)]));
assert_eq!(arr[1], OwnedValue::Array(vec![OwnedValue::Int(2), OwnedValue::Int(4)]));
}
);
}
#[test]
fn test_pivot_array_of_arrays_3x3() {
query!(br#"[[1, 2, 3], [4, 5, 6], [7, 8, 9]]"#, "pivot",
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr.len(), 3);
assert_eq!(arr[0], OwnedValue::Array(vec![OwnedValue::Int(1), OwnedValue::Int(4), OwnedValue::Int(7)]));
assert_eq!(arr[1], OwnedValue::Array(vec![OwnedValue::Int(2), OwnedValue::Int(5), OwnedValue::Int(8)]));
assert_eq!(arr[2], OwnedValue::Array(vec![OwnedValue::Int(3), OwnedValue::Int(6), OwnedValue::Int(9)]));
}
);
}
#[test]
fn test_pivot_array_of_arrays_ragged() {
query!(br#"[[1, 2], [3]]"#, "pivot",
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr.len(), 2);
assert_eq!(arr[0], OwnedValue::Array(vec![OwnedValue::Int(1), OwnedValue::Int(3)]));
assert_eq!(arr[1], OwnedValue::Array(vec![OwnedValue::Int(2), OwnedValue::Null]));
}
);
}
#[test]
fn test_pivot_array_of_objects() {
query!(br#"[{"name": "Alice", "age": 30}, {"name": "Bob", "age": 25}]"#, "pivot",
QueryResult::Owned(OwnedValue::Object(obj)) => {
assert_eq!(obj.len(), 2);
assert_eq!(obj.get("name"), Some(&OwnedValue::Array(vec![
OwnedValue::String("Alice".to_string()),
OwnedValue::String("Bob".to_string())
])));
assert_eq!(obj.get("age"), Some(&OwnedValue::Array(vec![
OwnedValue::Int(30),
OwnedValue::Int(25)
])));
}
);
}
#[test]
fn test_pivot_array_of_objects_missing_keys() {
query!(br#"[{"a": 1}, {"a": 2, "b": 3}]"#, "pivot",
QueryResult::Owned(OwnedValue::Object(obj)) => {
assert_eq!(obj.len(), 2);
assert_eq!(obj.get("a"), Some(&OwnedValue::Array(vec![
OwnedValue::Int(1),
OwnedValue::Int(2)
])));
assert_eq!(obj.get("b"), Some(&OwnedValue::Array(vec![
OwnedValue::Null,
OwnedValue::Int(3)
])));
}
);
}
#[test]
fn test_pivot_empty_array() {
query!(br#"[]"#, "pivot",
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert!(arr.is_empty());
}
);
}
#[test]
fn test_pivot_array_of_empty_arrays() {
query!(br#"[[], []]"#, "pivot",
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert!(arr.is_empty());
}
);
}
#[test]
fn test_pivot_array_of_empty_objects() {
query!(br#"[{}, {}]"#, "pivot",
QueryResult::Owned(OwnedValue::Object(obj)) => {
assert!(obj.is_empty());
}
);
}
#[test]
fn test_pivot_type_error_on_non_array() {
query!(br#""not an array""#, "pivot",
QueryResult::Error(err) => {
let msg = format!("{}", err);
assert!(msg.contains("array"));
}
);
}
#[test]
fn test_pivot_error_on_mixed_types() {
query!(br#"[[1], {"a": 2}]"#, "pivot",
QueryResult::Error(err) => {
let msg = format!("{}", err);
assert!(msg.contains("array of arrays") || msg.contains("array of objects"));
}
);
}
#[test]
fn test_pivot_error_on_scalars() {
query!(br#"[1, 2, 3]"#, "pivot",
QueryResult::Error(err) => {
let msg = format!("{}", err);
assert!(msg.contains("array of arrays") || msg.contains("array of objects"));
}
);
}
#[test]
fn test_pivot_in_pipeline() {
query!(br#"[[1, 2], [3, 4]]"#, "pivot | .[0]",
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr, vec![OwnedValue::Int(1), OwnedValue::Int(3)]);
}
);
}
#[test]
fn test_pivot_double_pivot_identity() {
query!(br#"[[1, 2], [3, 4]]"#, "pivot | pivot",
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr.len(), 2);
assert_eq!(arr[0], OwnedValue::Array(vec![OwnedValue::Int(1), OwnedValue::Int(2)]));
assert_eq!(arr[1], OwnedValue::Array(vec![OwnedValue::Int(3), OwnedValue::Int(4)]));
}
);
}
#[cfg(feature = "std")]
mod load_tests {
use super::*;
use std::fs;
fn with_temp_file<F, R>(name: &str, content: &str, f: F) -> R
where
F: FnOnce(&str) -> R,
{
let path = format!("/tmp/succinctly_test_{}", name);
fs::write(&path, content).unwrap();
let result = f(&path);
let _ = fs::remove_file(&path);
result
}
#[test]
fn test_load_json_file() {
with_temp_file(
"load_test.json",
r#"{"name": "test", "value": 42}"#,
|path| {
let json_bytes: &[u8] = b"null";
let index = JsonIndex::build(json_bytes);
let cursor = index.root(json_bytes);
let query = format!(r#"load("{}")"#, path);
let expr = parse(&query).unwrap();
match eval::<Vec<u64>, JqSemantics>(&expr, cursor) {
QueryResult::Owned(OwnedValue::Object(obj)) => {
assert_eq!(
obj.get("name"),
Some(&OwnedValue::String("test".to_string()))
);
assert_eq!(obj.get("value"), Some(&OwnedValue::Int(42)));
}
other => panic!("unexpected result: {:?}", other),
}
},
);
}
#[test]
fn test_load_yaml_file() {
with_temp_file("load_test.yaml", "name: test\nvalue: 42\n", |path| {
let json_bytes: &[u8] = b"null";
let index = JsonIndex::build(json_bytes);
let cursor = index.root(json_bytes);
let query = format!(r#"load("{}")"#, path);
let expr = parse(&query).unwrap();
match eval::<Vec<u64>, JqSemantics>(&expr, cursor) {
QueryResult::Owned(OwnedValue::Object(obj)) => {
assert_eq!(
obj.get("name"),
Some(&OwnedValue::String("test".to_string()))
);
assert_eq!(obj.get("value"), Some(&OwnedValue::Int(42)));
}
other => panic!("unexpected result: {:?}", other),
}
});
}
#[test]
fn test_load_yml_extension() {
with_temp_file("load_test.yml", "items:\n - a\n - b\n", |path| {
let json_bytes: &[u8] = b"null";
let index = JsonIndex::build(json_bytes);
let cursor = index.root(json_bytes);
let query = format!(r#"load("{}")"#, path);
let expr = parse(&query).unwrap();
match eval::<Vec<u64>, JqSemantics>(&expr, cursor) {
QueryResult::Owned(OwnedValue::Object(obj)) => {
let items = obj.get("items").unwrap();
match items {
OwnedValue::Array(arr) => {
assert_eq!(arr.len(), 2);
assert_eq!(arr[0], OwnedValue::String("a".to_string()));
assert_eq!(arr[1], OwnedValue::String("b".to_string()));
}
_ => panic!("expected array"),
}
}
other => panic!("unexpected result: {:?}", other),
}
});
}
#[test]
fn test_load_nonexistent_file() {
let json_bytes: &[u8] = b"null";
let index = JsonIndex::build(json_bytes);
let cursor = index.root(json_bytes);
let expr = parse(r#"load("/tmp/nonexistent_file_12345.yaml")"#).unwrap();
match eval::<Vec<u64>, JqSemantics>(&expr, cursor) {
QueryResult::Error(err) => {
assert!(
err.message.contains("Failed to read file")
|| err.message.contains("No such file")
);
}
other => panic!("expected error, got: {:?}", other),
}
}
#[test]
fn test_load_optional_nonexistent() {
let json_bytes: &[u8] = b"null";
let index = JsonIndex::build(json_bytes);
let cursor = index.root(json_bytes);
let expr = parse(r#"try load("/tmp/nonexistent_file_12345.yaml") catch null"#).unwrap();
match eval::<Vec<u64>, JqSemantics>(&expr, cursor) {
QueryResult::Owned(OwnedValue::Null) => {}
other => panic!("expected null, got: {:?}", other),
}
}
#[test]
fn test_load_with_try_catch() {
let json_bytes: &[u8] = b"null";
let index = JsonIndex::build(json_bytes);
let cursor = index.root(json_bytes);
let expr =
parse(r#"try load("/tmp/nonexistent_file_12345.yaml") catch "not found""#).unwrap();
match eval::<Vec<u64>, JqSemantics>(&expr, cursor) {
QueryResult::Owned(OwnedValue::String(s)) => {
assert_eq!(s, "not found");
}
other => panic!("expected 'not found', got: {:?}", other),
}
}
#[test]
fn test_load_combined_with_input() {
with_temp_file("config.yaml", "setting: enabled\n", |path| {
let json_bytes: &[u8] = br#"{"name": "main"}"#;
let index = JsonIndex::build(json_bytes);
let cursor = index.root(json_bytes);
let query = format!(r#". + {{config: load("{}")}}"#, path);
let expr = parse(&query).unwrap();
match eval::<Vec<u64>, JqSemantics>(&expr, cursor) {
QueryResult::Owned(OwnedValue::Object(obj)) => {
assert_eq!(
obj.get("name"),
Some(&OwnedValue::String("main".to_string()))
);
let config = obj.get("config").unwrap();
match config {
OwnedValue::Object(cfg) => {
assert_eq!(
cfg.get("setting"),
Some(&OwnedValue::String("enabled".to_string()))
);
}
_ => panic!("expected config to be object"),
}
}
other => panic!("unexpected result: {:?}", other),
}
});
}
#[test]
fn test_load_multi_document_yaml() {
with_temp_file("multi.yaml", "---\nname: doc1\n---\nname: doc2\n", |path| {
let json_bytes: &[u8] = b"null";
let index = JsonIndex::build(json_bytes);
let cursor = index.root(json_bytes);
let query = format!(r#"load("{}")"#, path);
let expr = parse(&query).unwrap();
match eval::<Vec<u64>, JqSemantics>(&expr, cursor) {
QueryResult::Owned(OwnedValue::Array(arr)) => {
assert_eq!(arr.len(), 2);
match &arr[0] {
OwnedValue::Object(obj) => {
assert_eq!(
obj.get("name"),
Some(&OwnedValue::String("doc1".to_string()))
);
}
_ => panic!("expected first doc to be object"),
}
match &arr[1] {
OwnedValue::Object(obj) => {
assert_eq!(
obj.get("name"),
Some(&OwnedValue::String("doc2".to_string()))
);
}
_ => panic!("expected second doc to be object"),
}
}
other => panic!("expected array of documents, got: {:?}", other),
}
});
}
#[test]
fn test_load_with_dynamic_path() {
with_temp_file("dynamic.json", r#"{"loaded": true}"#, |path| {
let json_bytes = format!(r#"{{"path": "{}"}}"#, path);
let json_bytes = json_bytes.as_bytes();
let index = JsonIndex::build(json_bytes);
let cursor = index.root(json_bytes);
let expr = parse(r#"load(.path)"#).unwrap();
match eval::<Vec<u64>, JqSemantics>(&expr, cursor) {
QueryResult::Owned(OwnedValue::Object(obj)) => {
assert_eq!(obj.get("loaded"), Some(&OwnedValue::Bool(true)));
}
other => panic!("unexpected result: {:?}", other),
}
});
}
}
}