use crate::filter::{ArithOp, AssignOp, BoolOp, Env, Filter, ObjKey, Pattern, PatternKey};
use crate::value::Value;
use std::cell::{Cell, RefCell};
use std::collections::{HashMap, VecDeque};
use std::sync::Arc;
const MAX_EVAL_DEPTH: usize = 256;
use super::value_ops::{arith_values, compare_values, recurse};
pub fn match_pattern(pattern: &Pattern, value: &Value, env: &Env) -> Option<Env> {
match_pattern_inner(pattern, value, env, false)
}
fn try_match_pattern(pattern: &Pattern, value: &Value, env: &Env) -> Option<Env> {
match_pattern_inner(pattern, value, env, true)
}
fn match_pattern_inner(pattern: &Pattern, value: &Value, env: &Env, strict: bool) -> Option<Env> {
match pattern {
Pattern::Var(name) => Some(env.bind_var(name.clone(), value.clone())),
Pattern::Array(patterns) => {
if strict && !matches!(value, Value::Array(_)) {
return None;
}
let mut new_env = env.clone();
for (i, pat) in patterns.iter().enumerate() {
let elem = match value {
Value::Array(arr) => arr.get(i).cloned().unwrap_or(Value::Null),
_ => Value::Null,
};
new_env = match_pattern_inner(pat, &elem, &new_env, strict)?;
}
Some(new_env)
}
Pattern::Object(pairs) => {
if strict && !matches!(value, Value::Object(_)) {
return None;
}
let mut new_env = env.clone();
for (key, pat) in pairs {
let (key_str, bind_var) = match key {
PatternKey::Name(s) => (s.clone(), None),
PatternKey::Var(s) => {
let k = s.strip_prefix('$').unwrap_or(s).to_string();
let bind = if *pat != Pattern::Var(s.clone()) {
Some(s.clone())
} else {
None
};
(k, bind)
}
PatternKey::Expr(expr) => {
let mut result = String::new();
eval(expr, value, &new_env, &mut |v| {
if let Value::String(s) = v {
result = s;
}
});
(result, None)
}
};
let field_val = match value {
Value::Object(obj) => obj
.iter()
.find(|(k, _)| k == &key_str)
.map(|(_, v)| v.clone())
.unwrap_or(Value::Null),
_ => Value::Null,
};
if let Some(var_name) = bind_var {
new_env = new_env.bind_var(var_name, field_val.clone());
}
new_env = match_pattern_inner(pat, &field_val, &new_env, strict)?;
}
Some(new_env)
}
}
}
fn collect_pattern_vars(pattern: &Pattern, vars: &mut Vec<String>) {
match pattern {
Pattern::Var(name) => {
if !vars.contains(name) {
vars.push(name.clone());
}
}
Pattern::Array(patterns) => {
for pat in patterns {
collect_pattern_vars(pat, vars);
}
}
Pattern::Object(pairs) => {
for (_, pat) in pairs {
collect_pattern_vars(pat, vars);
}
}
}
}
thread_local! {
pub(super) static LAST_ERROR: RefCell<Option<Value>> = const { RefCell::new(None) };
static BREAK_SIGNAL: RefCell<Option<String>> = const { RefCell::new(None) };
pub(super) static INPUT_QUEUE: RefCell<VecDeque<Value>> = const { RefCell::new(VecDeque::new()) };
static EVAL_DEPTH: Cell<usize> = const { Cell::new(0) };
}
thread_local! {
static MODULE_META_CACHE: RefCell<HashMap<String, Value>> = RefCell::new(HashMap::new());
static MODULE_SEARCH_PATHS: RefCell<Vec<std::path::PathBuf>> = const { RefCell::new(Vec::new()) };
}
struct EvalDepthGuard;
impl Drop for EvalDepthGuard {
fn drop(&mut self) {
EVAL_DEPTH.with(|d| d.set(d.get() - 1));
}
}
pub fn take_last_error() -> Option<Value> {
LAST_ERROR.with(|e| e.borrow_mut().take())
}
pub fn set_last_error(err: Value) {
LAST_ERROR.with(|e| *e.borrow_mut() = Some(err));
}
pub fn has_last_error() -> bool {
LAST_ERROR.with(|e| e.borrow().is_some())
}
pub fn set_module_metadata(meta: HashMap<String, Value>, search_paths: Vec<std::path::PathBuf>) {
MODULE_META_CACHE.with(|m: &RefCell<HashMap<String, Value>>| *m.borrow_mut() = meta);
MODULE_SEARCH_PATHS.with(|p: &RefCell<Vec<std::path::PathBuf>>| *p.borrow_mut() = search_paths);
}
pub(super) fn get_module_metadata(name: &str) -> Option<Value> {
MODULE_META_CACHE.with(|m: &RefCell<HashMap<String, Value>>| m.borrow().get(name).cloned())
}
pub(super) fn load_module_metadata_on_demand(name: &str, output: &mut dyn FnMut(Value)) {
let search_paths =
MODULE_SEARCH_PATHS.with(|p: &RefCell<Vec<std::path::PathBuf>>| p.borrow().clone());
if search_paths.is_empty() {
return;
}
let mut loader = super::module::ModuleLoader::new(search_paths);
let dummy_filter = super::Filter::Identity;
let import_filter = super::Filter::Import {
path: name.to_string(),
alias: name.to_string(),
is_data: false,
metadata: None,
rest: Box::new(dummy_filter),
};
if loader.resolve(&import_filter, super::Env::empty()).is_ok()
&& let Some(meta) = loader.get_module_metadata(name)
{
MODULE_META_CACHE.with(|m: &RefCell<HashMap<String, Value>>| {
m.borrow_mut().insert(name.to_string(), meta.clone());
});
output(meta);
}
}
pub fn set_input_queue(values: VecDeque<Value>) {
INPUT_QUEUE.with(|q| *q.borrow_mut() = values);
}
pub fn take_input_queue() -> VecDeque<Value> {
INPUT_QUEUE.with(|q| std::mem::take(&mut *q.borrow_mut()))
}
pub fn eval_filter(filter: &Filter, input: &Value, output: &mut dyn FnMut(Value)) {
LAST_ERROR.with(|e| *e.borrow_mut() = None);
BREAK_SIGNAL.with(|b| *b.borrow_mut() = None);
eval(filter, input, &Env::empty(), output);
}
pub fn eval_filter_with_env(
filter: &Filter,
input: &Value,
env: &Env,
output: &mut dyn FnMut(Value),
) {
LAST_ERROR.with(|e| *e.borrow_mut() = None);
BREAK_SIGNAL.with(|b| *b.borrow_mut() = None);
eval(filter, input, env, output);
}
pub fn eval(filter: &Filter, input: &Value, env: &Env, output: &mut dyn FnMut(Value)) {
if BREAK_SIGNAL.with(|b| b.borrow().is_some()) {
return;
}
EVAL_DEPTH.with(|d| d.set(d.get() + 1));
let _guard = EvalDepthGuard;
if EVAL_DEPTH.with(|d| d.get()) > MAX_EVAL_DEPTH {
LAST_ERROR.with(|e| {
*e.borrow_mut() = Some(Value::String(format!(
"Evaluation depth limit exceeded ({MAX_EVAL_DEPTH})"
)));
});
return;
}
match filter {
Filter::Identity => output(input.clone()),
Filter::Field(name) => match input {
Value::Object(obj) => {
for (k, v) in obj.iter() {
if k == name {
output(v.clone());
return;
}
}
output(Value::Null);
}
Value::Null => output(Value::Null),
_ => {
LAST_ERROR.with(|e| {
*e.borrow_mut() = Some(Value::String(format!(
"Cannot index {} with string \"{}\"",
input.type_name(),
name
)));
});
}
},
Filter::Index(idx_filter) => {
eval(idx_filter, input, env, &mut |idx| {
let idx = match &idx {
Value::Double(f, _) if f.is_nan() => {
if matches!(input, Value::Array(_) | Value::Null) {
output(Value::Null);
}
return;
}
Value::Double(f, _) if f.is_finite() => Value::Int(*f as i64),
_ => idx,
};
match (input, &idx) {
(Value::Array(arr), Value::Int(i)) => {
let index = if *i < 0 { arr.len() as i64 + i } else { *i };
if index >= 0 && (index as usize) < arr.len() {
output(arr[index as usize].clone());
} else {
output(Value::Null);
}
}
(Value::Object(obj), Value::String(key)) => {
for (k, v) in obj.iter() {
if k == key {
output(v.clone());
return;
}
}
output(Value::Null);
}
(Value::Null, _) => output(Value::Null),
_ => {
LAST_ERROR.with(|e| {
let idx_desc = match &idx {
Value::String(s) => format!("string \"{}\"", s),
_ => idx.type_name().to_string(),
};
*e.borrow_mut() = Some(Value::String(format!(
"Cannot index {} with {}",
input.type_name(),
idx_desc
)));
});
}
}
});
}
Filter::Pipe(left, right) => {
eval(left, input, env, &mut |intermediate| {
if LAST_ERROR.with(|e| e.borrow().is_some()) {
return;
}
eval(right, &intermediate, env, output);
});
}
Filter::Iterate => match input {
Value::Array(arr) => {
for v in arr.iter() {
output(v.clone());
}
}
Value::Object(obj) => {
for (_, v) in obj.iter() {
output(v.clone());
}
}
Value::Null => {
LAST_ERROR.with(|e| {
*e.borrow_mut() =
Some(Value::String("null is not iterable (null)".to_string()));
});
}
_ => {
LAST_ERROR.with(|e| {
*e.borrow_mut() = Some(Value::String(format!(
"Cannot iterate over {} ({})",
input.type_name(),
input.short_desc()
)));
});
}
},
Filter::Select(cond) => {
let mut is_truthy = false;
eval(cond, input, env, &mut |v| {
if v.is_truthy() {
is_truthy = true;
}
});
if is_truthy {
output(input.clone());
}
}
Filter::ObjectConstruct(pairs) => {
fn build_object(
pairs: &[(ObjKey, Box<Filter>)],
idx: usize,
current: &mut Vec<(String, Value)>,
input: &Value,
env: &Env,
output: &mut dyn FnMut(Value),
) {
if idx == pairs.len() {
output(Value::Object(Arc::new(current.clone())));
return;
}
let (key, val_filter) = &pairs[idx];
match key {
ObjKey::Name(s) => {
let key_str = s.clone();
eval(val_filter, input, env, &mut |v| {
current.push((key_str.clone(), v));
build_object(pairs, idx + 1, current, input, env, output);
current.pop();
});
}
ObjKey::Expr(expr) => {
eval(expr, input, env, &mut |kv| {
let key_str = match kv {
Value::String(s) => s,
_ => return,
};
eval(val_filter, input, env, &mut |v| {
current.push((key_str.clone(), v));
build_object(pairs, idx + 1, current, input, env, output);
current.pop();
});
});
}
}
}
let mut current = Vec::with_capacity(pairs.len());
build_object(pairs, 0, &mut current, input, env, output);
}
Filter::ArrayConstruct(expr) => {
let mut arr = Vec::new();
eval(expr, input, env, &mut |v| {
arr.push(v);
});
let has_error = LAST_ERROR.with(|e| e.borrow().is_some());
if !has_error {
output(Value::Array(Arc::new(arr)));
}
}
Filter::Literal(val) => output(val.clone()),
Filter::Compare(left, op, right) => {
eval(right, input, env, &mut |rval| {
eval(left, input, env, &mut |lval| {
let result = compare_values(&lval, op, &rval);
output(Value::Bool(result));
});
});
}
Filter::Arith(left, op, right) => {
eval(right, input, env, &mut |rval| {
eval(
left,
input,
env,
&mut |lval| match arith_values(&lval, op, &rval) {
Ok(result) => output(result),
Err(msg) => {
LAST_ERROR.with(|e| {
*e.borrow_mut() = Some(Value::String(msg));
});
}
},
);
});
}
Filter::Comma(items) => {
for item in items {
eval(item, input, env, output);
}
}
Filter::Recurse => {
recurse(input, output);
}
Filter::Builtin(name, args) => {
if let Some(func) = env.get_func(name, args.len()) {
eval_user_func(name, args.len(), func, args, input, env, output);
} else {
super::builtins::eval_builtin(name, args, input, env, output);
}
}
Filter::Not(inner) => {
eval(inner, input, env, &mut |v| {
output(Value::Bool(!v.is_truthy()));
});
}
Filter::BoolOp(left, op, right) => {
let mut lval = Value::Null;
eval(left, input, env, &mut |v| lval = v);
match op {
BoolOp::And => {
if lval.is_truthy() {
let mut rval = Value::Null;
eval(right, input, env, &mut |v| rval = v);
output(Value::Bool(rval.is_truthy()));
} else {
output(Value::Bool(false));
}
}
BoolOp::Or => {
if lval.is_truthy() {
output(Value::Bool(true));
} else {
let mut rval = Value::Null;
eval(right, input, env, &mut |v| rval = v);
output(Value::Bool(rval.is_truthy()));
}
}
}
}
Filter::IfThenElse(cond, then_branch, else_branch) => {
eval(cond, input, env, &mut |cond_val| {
if cond_val.is_truthy() {
eval(then_branch, input, env, output);
} else if let Some(else_br) = else_branch {
eval(else_br, input, env, output);
} else {
output(input.clone());
}
});
}
Filter::Alternative(left, right) => {
let mut truthy_vals = Vec::new();
eval(left, input, env, &mut |v| {
if v != Value::Null && v != Value::Bool(false) {
truthy_vals.push(v);
}
});
if !truthy_vals.is_empty() {
for v in truthy_vals {
output(v);
}
} else {
eval(right, input, env, output);
}
}
Filter::Try(inner) => {
LAST_ERROR.with(|e| e.borrow_mut().take());
let mut downstream_error: Option<Value> = None;
eval(inner, input, env, &mut |v| {
LAST_ERROR.with(|e| e.borrow_mut().take());
output(v);
if let Some(err) = LAST_ERROR.with(|e| e.borrow_mut().take()) {
downstream_error = Some(err);
}
});
LAST_ERROR.with(|e| e.borrow_mut().take());
if let Some(err) = downstream_error {
LAST_ERROR.with(|e| *e.borrow_mut() = Some(err));
}
}
Filter::TryCatch(body, handler) => {
LAST_ERROR.with(|e| e.borrow_mut().take());
let mut downstream_error: Option<Value> = None;
eval(body, input, env, &mut |v| {
LAST_ERROR.with(|e| e.borrow_mut().take());
output(v);
if let Some(err) = LAST_ERROR.with(|e| e.borrow_mut().take()) {
downstream_error = Some(err);
}
});
if let Some(err_val) = LAST_ERROR.with(|e| e.borrow_mut().take()) {
eval(handler, &err_val, env, output);
}
if let Some(err) = downstream_error {
LAST_ERROR.with(|e| *e.borrow_mut() = Some(err));
}
}
Filter::StringInterp(parts) => {
let mut result = String::new();
for part in parts {
match part {
crate::filter::StringPart::Lit(s) => result.push_str(s),
crate::filter::StringPart::Expr(f) => {
eval(f, input, env, &mut |v| match v {
Value::String(s) => result.push_str(&s),
Value::Int(n) => result.push_str(itoa::Buffer::new().format(n)),
Value::Double(f, _) => result.push_str(ryu::Buffer::new().format(f)),
Value::Bool(b) => result.push_str(if b { "true" } else { "false" }),
Value::Null => result.push_str("null"),
Value::Array(_) | Value::Object(_) => {
let mut buf = Vec::new();
crate::output::write_compact(&mut buf, &v, false).unwrap();
result.push_str(&String::from_utf8(buf).unwrap_or_default());
}
});
}
}
}
output(Value::String(result));
}
Filter::Neg(inner) => {
eval(inner, input, env, &mut |v| match v {
Value::Int(n) => output(
n.checked_neg()
.map_or_else(|| Value::Double(-(n as f64), None), Value::Int),
),
Value::Double(f, raw) => {
let preserve = crate::value::jq_compat() || f.is_infinite() || f == 0.0;
let neg_raw = if preserve {
raw.as_ref().map(|s| {
if let Some(rest) = s.strip_prefix('-') {
rest.into()
} else {
format!("-{s}").into_boxed_str()
}
})
} else {
None
};
output(Value::Double(-f, neg_raw));
}
_ => {
LAST_ERROR.with(|e| {
*e.borrow_mut() = Some(Value::String(format!(
"{} ({}) cannot be negated",
v.type_name(),
v.short_desc()
)));
});
}
});
}
Filter::Slice(start_f, end_f) => {
let start_val = start_f.as_ref().map(|f| {
let mut v = Value::Null;
eval(f, input, env, &mut |val| v = val);
if let Value::Double(f, _) = &v
&& f.is_finite()
{
return Value::Int(f.floor() as i64);
}
v
});
let end_val = end_f.as_ref().map(|f| {
let mut v = Value::Null;
eval(f, input, env, &mut |val| v = val);
if let Value::Double(f, _) = &v
&& f.is_finite()
{
return Value::Int(f.ceil() as i64);
}
v
});
match input {
Value::Array(arr) => {
let len = arr.len() as i64;
let s = resolve_slice_index(start_val.as_ref(), 0, len);
let e = resolve_slice_index(end_val.as_ref(), len, len);
if s < e {
output(Value::Array(Arc::new(arr[s as usize..e as usize].to_vec())));
} else {
output(Value::Array(Arc::new(vec![])));
}
}
Value::String(s) => {
let chars: Vec<char> = s.chars().collect();
let len = chars.len() as i64;
let start = resolve_slice_index(start_val.as_ref(), 0, len);
let end = resolve_slice_index(end_val.as_ref(), len, len);
if start < end {
let sliced: String = chars[start as usize..end as usize].iter().collect();
output(Value::String(sliced));
} else {
output(Value::String(String::new()));
}
}
Value::Null => output(Value::Null),
_ => {
LAST_ERROR.with(|e| {
*e.borrow_mut() = Some(Value::String(format!(
"{} ({}) cannot be sliced",
input.type_name(),
input.short_desc()
)));
});
}
}
}
Filter::Var(name) => {
if name == "$__loc__" {
let loc = Value::Object(Arc::new(vec![
("file".to_string(), Value::String("<top-level>".to_string())),
("line".to_string(), Value::Int(1)),
]));
output(loc);
} else if let Some(val) = env.get_var(name) {
output(val.clone());
} else {
super::builtins::eval_builtin(name, &[], input, env, output);
}
}
Filter::Bind(expr, pattern, body) => {
eval(expr, input, env, &mut |val| {
if let Some(new_env) = match_pattern(pattern, &val, env) {
eval(body, input, &new_env, output);
}
});
}
Filter::Reduce(source, pattern, init, update) => {
let mut acc = Value::Null;
eval(init, input, env, &mut |v| acc = v);
eval(source, input, env, &mut |val| {
if let Some(new_env) = match_pattern(pattern, &val, env) {
let cur = acc.clone();
eval(update, &cur, &new_env, &mut |v| acc = v);
}
});
output(acc);
}
Filter::Foreach(source, pattern, init, update, extract) => {
let mut init_vals = Vec::new();
eval(init, input, env, &mut |v| init_vals.push(v));
if init_vals.is_empty() {
init_vals.push(Value::Null);
}
for init_val in init_vals {
let mut acc = init_val;
eval(source, input, env, &mut |val| {
if BREAK_SIGNAL.with(|b| b.borrow().is_some()) {
return;
}
if let Some(new_env) = match_pattern(pattern, &val, env) {
let cur = acc.clone();
eval(update, &cur, &new_env, &mut |v| acc = v);
if let Some(ext) = extract {
eval(ext, &acc, &new_env, output);
} else {
output(acc.clone());
}
}
});
}
}
Filter::Assign(path_filter, op, rhs) => {
eval_assign(path_filter, *op, rhs, input, env, output);
}
Filter::Def {
name,
params,
body,
rest,
} => {
let func = super::UserFunc {
params: params.clone(),
body: (**body).clone(),
closure_env: env.clone(),
is_def: true,
};
let new_env = env.bind_func(name.clone(), params.len(), func);
eval(rest, input, &new_env, output);
}
Filter::AltBind(expr, patterns, body) => {
let mut all_vars = Vec::new();
for pat in patterns {
collect_pattern_vars(pat, &mut all_vars);
}
eval(expr, input, env, &mut |val| {
let mut base_env = env.clone();
for var in &all_vars {
base_env = base_env.bind_var(var.clone(), Value::Null);
}
for pat in patterns {
if let Some(new_env) = try_match_pattern(pat, &val, &base_env) {
eval(body, input, &new_env, output);
return;
}
}
LAST_ERROR.with(|e| {
*e.borrow_mut() = Some(Value::String(
"No pattern matched in ?// expression".to_string(),
));
});
});
}
Filter::PostfixIndex(base, idx_expr) => {
eval(base, input, env, &mut |base_val| {
eval(idx_expr, input, env, &mut |idx| {
let idx = match &idx {
Value::Double(f, _) if f.is_nan() => {
if matches!(base_val, Value::Array(_) | Value::Null) {
output(Value::Null);
}
return;
}
Value::Double(f, _) if f.is_finite() => Value::Int(*f as i64),
_ => idx,
};
match (&base_val, &idx) {
(Value::Array(arr), Value::Int(i)) => {
let index = if *i < 0 { arr.len() as i64 + i } else { *i };
if index >= 0 && (index as usize) < arr.len() {
output(arr[index as usize].clone());
} else {
output(Value::Null);
}
}
(Value::Object(obj), Value::String(key)) => {
for (k, v) in obj.iter() {
if k == key {
output(v.clone());
return;
}
}
output(Value::Null);
}
(Value::Null, _) => output(Value::Null),
_ => {
LAST_ERROR.with(|e| {
let idx_desc = match &idx {
Value::String(s) => format!("string \"{}\"", s),
_ => idx.type_name().to_string(),
};
*e.borrow_mut() = Some(Value::String(format!(
"Cannot index {} with {}",
base_val.type_name(),
idx_desc
)));
});
}
}
});
});
}
Filter::PostfixSlice(base, start_f, end_f) => {
eval(base, input, env, &mut |base_val| {
let start_val = start_f.as_ref().map(|f| {
let mut v = Value::Null;
eval(f, input, env, &mut |val| v = val);
if let Value::Double(f, _) = &v
&& f.is_finite()
{
return Value::Int(f.floor() as i64);
}
v
});
let end_val = end_f.as_ref().map(|f| {
let mut v = Value::Null;
eval(f, input, env, &mut |val| v = val);
if let Value::Double(f, _) = &v
&& f.is_finite()
{
return Value::Int(f.ceil() as i64);
}
v
});
match &base_val {
Value::Array(arr) => {
let len = arr.len() as i64;
let s = resolve_slice_index(start_val.as_ref(), 0, len);
let e = resolve_slice_index(end_val.as_ref(), len, len);
if s < e {
output(Value::Array(Arc::new(arr[s as usize..e as usize].to_vec())));
} else {
output(Value::Array(Arc::new(vec![])));
}
}
Value::String(s) => {
let chars: Vec<char> = s.chars().collect();
let len = chars.len() as i64;
let start = resolve_slice_index(start_val.as_ref(), 0, len);
let end = resolve_slice_index(end_val.as_ref(), len, len);
if start < end {
let sliced: String =
chars[start as usize..end as usize].iter().collect();
output(Value::String(sliced));
} else {
output(Value::String(String::new()));
}
}
Value::Null => output(Value::Null),
_ => {
LAST_ERROR.with(|e| {
*e.borrow_mut() = Some(Value::String(format!(
"{} ({}) cannot be sliced",
base_val.type_name(),
base_val.short_desc()
)));
});
}
}
});
}
Filter::Label(name, body) => {
eval(body, input, env, &mut |v| {
if BREAK_SIGNAL.with(|b| b.borrow().is_none()) {
output(v);
}
});
BREAK_SIGNAL.with(|b| {
if b.borrow().as_deref() == Some(name.as_str()) {
*b.borrow_mut() = None;
}
});
}
Filter::Break(name) => {
BREAK_SIGNAL.with(|b| {
*b.borrow_mut() = Some(name.clone());
});
}
Filter::Import { .. } | Filter::Include { .. } | Filter::ModuleDecl { .. } => {
set_last_error(Value::String(
"module system (import/include) requires -L library path".into(),
));
}
}
}
fn eval_user_func(
func_name: &str,
func_arity: usize,
func: &super::UserFunc,
args: &[Filter],
input: &Value,
caller_env: &Env,
output: &mut dyn FnMut(Value),
) {
let mut body_env = func.closure_env.clone();
if func.is_def {
let self_func = super::UserFunc {
params: func.params.clone(),
body: func.body.clone(),
closure_env: body_env.clone(),
is_def: true,
};
body_env = body_env.bind_func(func_name.to_string(), func_arity, self_func);
}
bind_params_and_eval(
&func.params,
args,
input,
caller_env,
body_env,
&func.body,
output,
);
}
fn bind_params_and_eval(
params: &[String],
args: &[Filter],
input: &Value,
caller_env: &Env,
body_env: Env,
body: &Filter,
output: &mut dyn FnMut(Value),
) {
if params.is_empty() {
eval(body, input, &body_env, output);
return;
}
let param_name = ¶ms[0];
let arg_filter = &args[0];
let rest_params = ¶ms[1..];
let rest_args = &args[1..];
if param_name.starts_with('$') {
eval(arg_filter, input, caller_env, &mut |val| {
let new_env = body_env.bind_var(param_name.clone(), val);
bind_params_and_eval(
rest_params,
rest_args,
input,
caller_env,
new_env,
body,
output,
);
});
} else {
let param_func = super::UserFunc {
params: vec![],
body: arg_filter.clone(),
closure_env: caller_env.clone(),
is_def: false,
};
let new_env = body_env.bind_func(param_name.clone(), 0, param_func);
bind_params_and_eval(
rest_params,
rest_args,
input,
caller_env,
new_env,
body,
output,
);
}
}
fn eval_assign(
path_filter: &Filter,
op: AssignOp,
rhs: &Filter,
input: &Value,
env: &Env,
output: &mut dyn FnMut(Value),
) {
if op == AssignOp::Set {
let mut rhs_vals = Vec::new();
eval(rhs, input, env, &mut |v| rhs_vals.push(v));
for rhs_val in rhs_vals {
let set_updater = |_current: &Value| -> Option<Value> { Some(rhs_val.clone()) };
if is_update_path_supported(path_filter) {
if let Some(result) = update_recursive(path_filter, input, env, &set_updater) {
output(result);
}
} else {
eval_assign_via_paths(path_filter, input, env, &set_updater, output);
}
}
return;
}
type Updater<'a> = Box<dyn Fn(&Value) -> Option<Value> + 'a>;
let updater: Updater<'_> = match op {
AssignOp::Update => Box::new(|current: &Value| {
let mut result = None;
eval(rhs, current, env, &mut |v| {
if result.is_none() {
result = Some(v);
}
});
result }),
AssignOp::Alt => Box::new(|current: &Value| {
if matches!(current, Value::Null | Value::Bool(false)) {
let mut result = None;
eval(rhs, input, env, &mut |v| {
if result.is_none() {
result = Some(v);
}
});
Some(result.unwrap_or_else(|| current.clone()))
} else {
Some(current.clone())
}
}),
_ => {
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,
_ => unreachable!(),
};
Box::new(move |current: &Value| {
let mut result = None;
eval(rhs, input, env, &mut |rhs_val| {
if result.is_none() {
match arith_values(current, &arith_op, &rhs_val) {
Ok(v) => result = Some(v),
Err(msg) => {
LAST_ERROR.with(|e| {
*e.borrow_mut() = Some(Value::String(msg));
});
}
}
}
});
result
})
}
};
if is_update_path_supported(path_filter) {
if let Some(result) = update_recursive(path_filter, input, env, &*updater) {
output(result);
}
return;
}
eval_assign_via_paths(path_filter, input, env, &*updater, output);
}
fn is_update_path_supported(f: &Filter) -> bool {
match f {
Filter::Identity | Filter::Field(_) | Filter::Iterate | Filter::Select(_) => true,
Filter::Index(_) | Filter::Slice(_, _) => true,
Filter::PostfixIndex(a, _) | Filter::PostfixSlice(a, _, _) => is_update_path_supported(a),
Filter::Pipe(a, b) => is_update_path_supported(a) && is_update_path_supported(b),
Filter::Comma(items) => items.iter().all(is_update_path_supported),
Filter::Bind(_, _, body) => is_update_path_supported(body),
_ => false,
}
}
fn update_recursive(
path_filter: &Filter,
input: &Value,
env: &Env,
updater: &dyn Fn(&Value) -> Option<Value>,
) -> Option<Value> {
match path_filter {
Filter::Identity => updater(input),
Filter::Field(name) => match input {
Value::Object(obj) => {
let mut result: Vec<(String, Value)> = Vec::with_capacity(obj.len());
let mut found = false;
for (k, v) in obj.iter() {
if k == name && !found {
found = true;
if let Some(new_v) = updater(v) {
result.push((k.clone(), new_v));
}
} else {
result.push((k.clone(), v.clone()));
}
}
if !found {
if let Some(new_v) = updater(&Value::Null) {
result.push((name.clone(), new_v));
} else if LAST_ERROR.with(|e| e.borrow().is_some()) {
return None; }
}
Some(Value::Object(Arc::new(result)))
}
Value::Null => {
if let Some(new_v) = updater(&Value::Null) {
Some(Value::Object(Arc::new(vec![(name.clone(), new_v)])))
} else if LAST_ERROR.with(|e| e.borrow().is_some()) {
None } else {
Some(Value::Null)
}
}
_ => Some(input.clone()),
},
Filter::Iterate => match input {
Value::Array(arr) => {
let mut result = Vec::with_capacity(arr.len());
for elem in arr.iter() {
if let Some(new_elem) = updater(elem) {
result.push(new_elem);
}
}
Some(Value::Array(Arc::new(result)))
}
Value::Object(obj) => {
let mut result = Vec::with_capacity(obj.len());
for (k, v) in obj.iter() {
if let Some(new_v) = updater(v) {
result.push((k.clone(), new_v));
}
}
Some(Value::Object(Arc::new(result)))
}
_ => Some(input.clone()),
},
Filter::Index(idx_f) => {
let mut raw_indices = Vec::new();
eval(idx_f, input, env, &mut |v| raw_indices.push(v));
let mut indices = Vec::new();
for v in raw_indices {
match &v {
Value::Double(f, _) if f.is_nan() => {
LAST_ERROR.with(|e| {
*e.borrow_mut() = Some(Value::String(
"Cannot set array element at NaN index".into(),
));
});
return None;
}
Value::Double(f, _) if f.is_finite() => {
indices.push(Value::Int(*f as i64));
}
_ => indices.push(v),
}
}
match input {
Value::Array(arr) => {
let mut result = arr.as_ref().clone();
let mut to_delete = Vec::new();
for idx_val in &indices {
if let Value::Int(i) = idx_val {
let idx = if *i < 0 {
(result.len() as i64 + i).max(0) as usize
} else {
*i as usize
};
while result.len() <= idx {
result.push(Value::Null);
}
match updater(&result[idx]) {
Some(new_v) => result[idx] = new_v,
None => to_delete.push(idx),
}
}
}
to_delete.sort_unstable();
to_delete.dedup();
for idx in to_delete.into_iter().rev() {
if idx < result.len() {
result.remove(idx);
}
}
Some(Value::Array(Arc::new(result)))
}
Value::Object(obj) => {
let mut result: Vec<(String, Value)> = obj.as_ref().clone();
let mut keys_to_delete = Vec::new();
for idx_val in &indices {
if let Value::String(k) = idx_val {
if let Some(entry) = result.iter_mut().find(|(ek, _)| ek == k) {
match updater(&entry.1) {
Some(new_v) => entry.1 = new_v,
None => keys_to_delete.push(k.clone()),
}
} else if let Some(new_v) = updater(&Value::Null) {
result.push((k.clone(), new_v));
}
}
}
result.retain(|(k, _)| !keys_to_delete.contains(k));
Some(Value::Object(Arc::new(result)))
}
Value::Null => {
if let Some(idx_val) = indices.first() {
match idx_val {
Value::Int(i) => {
if *i < 0 {
LAST_ERROR.with(|e| {
*e.borrow_mut() = Some(Value::String(
"Out of bounds negative array index".into(),
));
});
return None;
}
if *i > 1_000_000 {
LAST_ERROR.with(|e| {
*e.borrow_mut() =
Some(Value::String("Array index too large".into()));
});
return None;
}
let idx = *i as usize;
let mut arr = vec![Value::Null; idx + 1];
if let Some(new_v) = updater(&Value::Null) {
arr[idx] = new_v;
} else if LAST_ERROR.with(|e| e.borrow().is_some()) {
return None;
}
Some(Value::Array(Arc::new(arr)))
}
Value::String(k) => {
if let Some(new_v) = updater(&Value::Null) {
Some(Value::Object(Arc::new(vec![(k.clone(), new_v)])))
} else if LAST_ERROR.with(|e| e.borrow().is_some()) {
None
} else {
Some(Value::Null)
}
}
_ => Some(Value::Null),
}
} else {
Some(Value::Null)
}
}
_ => Some(input.clone()),
}
}
Filter::Slice(start_f, end_f) => {
match input {
Value::String(_) => {
LAST_ERROR.with(|e| {
*e.borrow_mut() = Some(Value::String("Cannot update string slices".into()));
});
None
}
Value::Array(arr) => {
let len = arr.len() as i64;
let s = resolve_slice_index(
start_f
.as_ref()
.map(|f| {
let mut v = Value::Null;
eval(f, input, env, &mut |val| v = val);
if let Value::Double(fv, _) = &v
&& fv.is_finite()
{
return Value::Int(fv.floor() as i64);
}
v
})
.as_ref(),
0,
len,
);
let e = resolve_slice_index(
end_f
.as_ref()
.map(|f| {
let mut v = Value::Null;
eval(f, input, env, &mut |val| v = val);
if let Value::Double(fv, _) = &v
&& fv.is_finite()
{
return Value::Int(fv.ceil() as i64);
}
v
})
.as_ref(),
len,
len,
);
let s = s as usize;
let e = e as usize;
if let Some(new_val) =
updater(&Value::Array(Arc::new(arr[s..e.min(arr.len())].to_vec())))
{
let mut result = Vec::new();
result.extend_from_slice(&arr[..s]);
if let Value::Array(new_arr) = &new_val {
result.extend_from_slice(new_arr);
} else {
result.push(new_val);
}
if e < arr.len() {
result.extend_from_slice(&arr[e..]);
}
Some(Value::Array(Arc::new(result)))
} else {
let mut result = Vec::new();
result.extend_from_slice(&arr[..s]);
if e < arr.len() {
result.extend_from_slice(&arr[e..]);
}
Some(Value::Array(Arc::new(result)))
}
}
_ => Some(input.clone()),
}
}
Filter::PostfixIndex(base, idx) => {
let idx_filter = Filter::Index(idx.clone());
update_recursive(base, input, env, &|val: &Value| -> Option<Value> {
update_recursive(&idx_filter, val, env, updater)
})
}
Filter::PostfixSlice(base, s, e) => {
let slice_filter = Filter::Slice(s.clone(), e.clone());
update_recursive(base, input, env, &|val: &Value| -> Option<Value> {
update_recursive(&slice_filter, val, env, updater)
})
}
Filter::Pipe(a, b) => {
update_recursive(a, input, env, &|val: &Value| -> Option<Value> {
update_recursive(b, val, env, updater)
})
}
Filter::Select(cond) => {
let mut is_match = false;
eval(cond, input, env, &mut |v| {
if v.is_truthy() {
is_match = true;
}
});
if is_match {
updater(input)
} else {
Some(input.clone())
}
}
Filter::Comma(items) => {
let mut result = input.clone();
for item in items {
if let Some(updated) = update_recursive(item, &result, env, updater) {
result = updated;
}
}
Some(result)
}
Filter::Bind(source, pattern, body) => {
let mut result = input.clone();
eval(source, input, env, &mut |val| {
if let Some(new_env) = match_pattern(pattern, &val, env)
&& let Some(updated) = update_recursive(body, &result, &new_env, updater)
{
result = updated;
}
});
Some(result)
}
_ => unreachable!("checked by is_update_path_supported"),
}
}
fn eval_assign_via_paths(
path_filter: &Filter,
input: &Value,
env: &Env,
updater: &dyn Fn(&Value) -> Option<Value>,
output: &mut dyn FnMut(Value),
) {
use super::value_ops;
let mut paths: Vec<Vec<Value>> = Vec::new();
value_ops::path_of_env(path_filter, input, &mut Vec::new(), env, &mut |p| {
if let Value::Array(arr) = p {
paths.push(arr.as_ref().clone());
}
});
if paths.is_empty() && has_last_error() {
return;
}
let mut result = input.clone();
let mut deletions: Vec<Vec<Value>> = Vec::new();
for path in &paths {
let current = value_ops::get_path(&result, path);
match updater(¤t) {
Some(new_val) => match value_ops::set_path(&result, path, &new_val) {
Ok(v) => result = v,
Err(msg) => {
LAST_ERROR.with(|e| *e.borrow_mut() = Some(Value::String(msg)));
return;
}
},
None => {
deletions.push(path.clone());
}
}
}
deletions.sort_by(|a, b| match (a.last(), b.last()) {
(Some(Value::Int(ai)), Some(Value::Int(bi))) => bi.cmp(ai),
_ => std::cmp::Ordering::Equal,
});
for path in &deletions {
result = value_ops::del_path(&result, path);
}
output(result);
}
pub(crate) fn resolve_slice_index(val: Option<&Value>, default: i64, len: i64) -> i64 {
let idx = match val {
Some(Value::Int(n)) => *n,
Some(Value::Double(f, _)) if f.is_finite() => *f as i64,
_ => return default.clamp(0, len),
};
let resolved = if idx < 0 { len + idx } else { idx };
resolved.clamp(0, len)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::filter::value_ops::values_order;
fn eval_one(filter: &Filter, input: &Value) -> Value {
let mut results = Vec::new();
eval_filter(filter, input, &mut |v| results.push(v));
assert_eq!(results.len(), 1, "expected 1 result, got {:?}", results);
results.into_iter().next().unwrap()
}
fn eval_all(filter: &Filter, input: &Value) -> Vec<Value> {
let mut results = Vec::new();
eval_filter(filter, input, &mut |v| results.push(v));
results
}
fn parse(s: &str) -> Filter {
crate::filter::parse(s).unwrap()
}
fn obj(pairs: &[(&str, Value)]) -> Value {
Value::Object(Arc::new(
pairs
.iter()
.map(|(k, v)| (k.to_string(), v.clone()))
.collect(),
))
}
#[test]
fn eval_identity() {
let v = Value::Int(42);
assert_eq!(eval_one(&parse("."), &v), Value::Int(42));
}
#[test]
fn eval_field() {
let input = obj(&[("name", Value::String("alice".into()))]);
assert_eq!(
eval_one(&parse(".name"), &input),
Value::String("alice".into())
);
}
#[test]
fn eval_nested_field() {
let input = obj(&[("a", obj(&[("b", Value::Int(1))]))]);
assert_eq!(eval_one(&parse(".a.b"), &input), Value::Int(1));
}
#[test]
fn eval_missing_field() {
let input = obj(&[("x", Value::Int(1))]);
assert_eq!(eval_one(&parse(".y"), &input), Value::Null);
}
#[test]
fn eval_iterate_array() {
let input = Value::Array(Arc::new(vec![Value::Int(1), Value::Int(2), Value::Int(3)]));
assert_eq!(
eval_all(&parse(".[]"), &input),
vec![Value::Int(1), Value::Int(2), Value::Int(3)]
);
}
#[test]
fn eval_iterate_object() {
let input = obj(&[("a", Value::Int(1)), ("b", Value::Int(2))]);
let results = eval_all(&parse(".[]"), &input);
assert_eq!(results, vec![Value::Int(1), Value::Int(2)]);
}
#[test]
fn eval_pipe() {
let input = Value::Array(Arc::new(vec![
obj(&[("name", Value::String("alice".into()))]),
obj(&[("name", Value::String("bob".into()))]),
]));
assert_eq!(
eval_all(&parse(".[] | .name"), &input),
vec![Value::String("alice".into()), Value::String("bob".into()),]
);
}
#[test]
fn eval_select() {
let input = Value::Array(Arc::new(vec![
obj(&[("x", Value::Int(1))]),
obj(&[("x", Value::Int(5))]),
obj(&[("x", Value::Int(3))]),
]));
let results = eval_all(&parse(".[] | select(.x > 2)"), &input);
assert_eq!(results.len(), 2);
}
#[test]
fn eval_object_construct() {
let input = obj(&[
("name", Value::String("alice".into())),
("age", Value::Int(30)),
]);
let result = eval_one(&parse("{name: .name}"), &input);
assert_eq!(result, obj(&[("name", Value::String("alice".into()))]));
}
#[test]
fn eval_array_construct() {
let input = Value::Array(Arc::new(vec![
obj(&[("x", Value::Int(1))]),
obj(&[("x", Value::Int(2))]),
]));
let result = eval_one(&parse("[.[] | .x]"), &input);
assert_eq!(
result,
Value::Array(Arc::new(vec![Value::Int(1), Value::Int(2)]))
);
}
#[test]
fn eval_index() {
let input = Value::Array(Arc::new(vec![
Value::Int(10),
Value::Int(20),
Value::Int(30),
]));
assert_eq!(eval_one(&parse(".[1]"), &input), Value::Int(20));
}
#[test]
fn eval_negative_index() {
let input = Value::Array(Arc::new(vec![
Value::Int(10),
Value::Int(20),
Value::Int(30),
]));
assert_eq!(eval_one(&parse(".[-1]"), &input), Value::Int(30));
}
#[test]
fn eval_arithmetic() {
let input = obj(&[("x", Value::Int(10))]);
assert_eq!(eval_one(&parse(".x + 5"), &input), Value::Int(15));
assert_eq!(eval_one(&parse(".x - 3"), &input), Value::Int(7));
assert_eq!(eval_one(&parse(".x * 2"), &input), Value::Int(20));
}
#[test]
fn eval_comparison() {
let input = obj(&[("x", Value::Int(5))]);
assert_eq!(eval_one(&parse(".x > 3"), &input), Value::Bool(true));
assert_eq!(eval_one(&parse(".x < 3"), &input), Value::Bool(false));
assert_eq!(eval_one(&parse(".x == 5"), &input), Value::Bool(true));
}
#[test]
fn eval_comma() {
let input = obj(&[("a", Value::Int(1)), ("b", Value::Int(2))]);
assert_eq!(
eval_all(&parse(".a, .b"), &input),
vec![Value::Int(1), Value::Int(2)]
);
}
#[test]
fn eval_length() {
assert_eq!(
eval_one(
&parse("length"),
&Value::Array(Arc::new(vec![Value::Int(1), Value::Int(2)]))
),
Value::Int(2)
);
assert_eq!(
eval_one(&parse("length"), &Value::String("hello".into())),
Value::Int(5)
);
}
#[test]
fn eval_keys() {
let input = obj(&[("b", Value::Int(2)), ("a", Value::Int(1))]);
assert_eq!(
eval_one(&parse("keys"), &input),
Value::Array(Arc::new(vec![
Value::String("a".into()),
Value::String("b".into())
]))
);
}
#[test]
fn eval_type() {
assert_eq!(
eval_one(&parse("type"), &Value::Int(42)),
Value::String("number".into())
);
}
#[test]
fn eval_if_then_else() {
assert_eq!(
eval_one(
&parse("if . > 5 then \"big\" else \"small\" end"),
&Value::Int(10)
),
Value::String("big".into())
);
}
#[test]
fn eval_alternative() {
assert_eq!(eval_one(&parse(".x // 42"), &obj(&[])), Value::Int(42));
assert_eq!(
eval_one(&parse(".x // 42"), &obj(&[("x", Value::Int(7))])),
Value::Int(7)
);
}
#[test]
fn eval_map() {
let input = Value::Array(Arc::new(vec![Value::Int(1), Value::Int(2), Value::Int(3)]));
assert_eq!(
eval_one(&parse("map(. + 10)"), &input),
Value::Array(Arc::new(vec![
Value::Int(11),
Value::Int(12),
Value::Int(13)
]))
);
}
#[test]
fn eval_add() {
let input = Value::Array(Arc::new(vec![Value::Int(1), Value::Int(2), Value::Int(3)]));
assert_eq!(eval_one(&parse("add"), &input), Value::Int(6));
}
#[test]
fn eval_has() {
let input = obj(&[("name", Value::String("alice".into()))]);
assert_eq!(eval_one(&parse("has(\"name\")"), &input), Value::Bool(true));
assert_eq!(eval_one(&parse("has(\"age\")"), &input), Value::Bool(false));
}
#[test]
fn eval_sort() {
let input = Value::Array(Arc::new(vec![Value::Int(3), Value::Int(1), Value::Int(2)]));
assert_eq!(
eval_one(&parse("sort"), &input),
Value::Array(Arc::new(vec![Value::Int(1), Value::Int(2), Value::Int(3)]))
);
}
#[test]
fn eval_precedence_mul_before_add() {
assert_eq!(eval_one(&parse("1 + 2 * 3"), &Value::Null), Value::Int(7));
}
#[test]
fn eval_precedence_div_before_sub() {
assert_eq!(eval_one(&parse("10 - 6 / 2"), &Value::Null), Value::Int(7));
}
#[test]
fn eval_precedence_complex() {
assert_eq!(
eval_one(&parse("2 * 3 + 4 * 5"), &Value::Null),
Value::Int(26)
);
}
#[test]
fn eval_sort_mixed_types() {
let input = Value::Array(Arc::new(vec![
Value::Int(3),
Value::String("a".into()),
Value::Null,
Value::Bool(true),
Value::Bool(false),
Value::Int(1),
]));
let result = eval_one(&parse("sort"), &input);
assert_eq!(
result,
Value::Array(Arc::new(vec![
Value::Null,
Value::Bool(false),
Value::Bool(true),
Value::Int(1),
Value::Int(3),
Value::String("a".into()),
]))
);
}
#[test]
fn eval_values_order_cross_type() {
assert_eq!(
values_order(&Value::Null, &Value::Bool(false)),
Some(std::cmp::Ordering::Less)
);
assert_eq!(
values_order(&Value::Bool(true), &Value::Int(0)),
Some(std::cmp::Ordering::Less)
);
assert_eq!(
values_order(&Value::Int(999), &Value::String("".into())),
Some(std::cmp::Ordering::Less)
);
}
#[test]
fn eval_values_order_arrays() {
let a = Value::Array(Arc::new(vec![Value::Int(1), Value::Int(2)]));
let b = Value::Array(Arc::new(vec![Value::Int(1), Value::Int(3)]));
assert_eq!(values_order(&a, &b), Some(std::cmp::Ordering::Less));
let c = Value::Array(Arc::new(vec![Value::Int(1)]));
assert_eq!(values_order(&c, &a), Some(std::cmp::Ordering::Less));
}
#[test]
fn eval_unique_sorts() {
let input = Value::Array(Arc::new(vec![
Value::Int(3),
Value::Int(1),
Value::Int(2),
Value::Int(1),
Value::Int(3),
]));
assert_eq!(
eval_one(&parse("unique"), &input),
Value::Array(Arc::new(vec![Value::Int(1), Value::Int(2), Value::Int(3)]))
);
}
#[test]
fn eval_range_single() {
assert_eq!(
eval_all(&parse("range(4)"), &Value::Null),
vec![Value::Int(0), Value::Int(1), Value::Int(2), Value::Int(3)]
);
}
#[test]
fn eval_range_two_args() {
assert_eq!(
eval_all(&parse("range(2;5)"), &Value::Null),
vec![Value::Int(2), Value::Int(3), Value::Int(4)]
);
}
#[test]
fn eval_range_three_args() {
assert_eq!(
eval_all(&parse("range(0;10;3)"), &Value::Null),
vec![Value::Int(0), Value::Int(3), Value::Int(6), Value::Int(9)]
);
}
#[test]
fn eval_range_empty() {
assert_eq!(eval_all(&parse("range(0)"), &Value::Null), vec![]);
}
#[test]
fn eval_floor() {
assert_eq!(
eval_one(&parse("floor"), &Value::Double(3.7, None)),
Value::Int(3)
);
assert_eq!(
eval_one(&parse("floor"), &Value::Double(-1.2, None)),
Value::Int(-2)
);
}
#[test]
fn eval_ceil() {
assert_eq!(
eval_one(&parse("ceil"), &Value::Double(3.2, None)),
Value::Int(4)
);
}
#[test]
fn eval_round() {
assert_eq!(
eval_one(&parse("round"), &Value::Double(3.5, None)),
Value::Int(4)
);
assert_eq!(
eval_one(&parse("round"), &Value::Double(3.4, None)),
Value::Int(3)
);
}
#[test]
fn eval_sqrt() {
assert_eq!(
eval_one(&parse("sqrt"), &Value::Int(9)),
Value::Double(3.0, None)
);
}
#[test]
fn eval_fabs() {
assert_eq!(
eval_one(&parse("fabs"), &Value::Double(-5.5, None)),
Value::Double(5.5, None)
);
}
#[test]
fn eval_nan_isnan() {
let nan = eval_one(&parse("nan"), &Value::Null);
assert!(matches!(nan, Value::Double(f, _) if f.is_nan()));
assert_eq!(
eval_one(&parse("nan | isnan"), &Value::Null),
Value::Bool(true)
);
}
#[test]
fn eval_infinite() {
assert_eq!(
eval_one(&parse("infinite | isinfinite"), &Value::Null),
Value::Bool(true)
);
assert_eq!(
eval_one(&parse("1 | isinfinite"), &Value::Null),
Value::Bool(false)
);
}
#[test]
fn eval_isfinite() {
assert_eq!(
eval_one(&parse("1 | isfinite"), &Value::Null),
Value::Bool(true)
);
}
#[test]
fn eval_pow() {
assert_eq!(
eval_one(&parse("pow(2;10)"), &Value::Null),
Value::Double(1024.0, None)
);
}
#[test]
fn eval_log_exp_roundtrip() {
let result = eval_one(&parse("log | exp"), &Value::Int(5));
match result {
Value::Double(f, _) => assert!((f - 5.0).abs() < 1e-10),
Value::Int(5) => {} other => panic!("expected ~5.0, got {other:?}"),
}
}
#[test]
fn eval_length_number_abs() {
assert_eq!(eval_one(&parse("length"), &Value::Int(-5)), Value::Int(5));
assert_eq!(eval_one(&parse("length"), &Value::Int(0)), Value::Int(0));
}
#[test]
fn eval_length_double_abs() {
assert_eq!(
eval_one(&parse("length"), &Value::Double(-3.14, None)),
Value::Double(3.14, None)
);
}
#[test]
fn eval_length_unicode_codepoints() {
assert_eq!(
eval_one(&parse("length"), &Value::String("é".into())),
Value::Int(1)
);
assert_eq!(
eval_one(&parse("length"), &Value::String("abc".into())),
Value::Int(3)
);
}
#[test]
fn eval_if_generator_cond() {
let results = eval_all(
&parse("if (1,2) > 1 then \"yes\" else \"no\" end"),
&Value::Null,
);
assert_eq!(
results,
vec![Value::String("no".into()), Value::String("yes".into()),]
);
}
#[test]
fn eval_if_no_else_passthrough() {
assert_eq!(
eval_one(&parse("if false then \"x\" end"), &Value::Int(42)),
Value::Int(42)
);
}
#[test]
fn eval_object_construct_generator() {
let results = eval_all(&parse("{x: (1,2)}"), &Value::Null);
assert_eq!(results.len(), 2);
assert_eq!(results[0], obj(&[("x", Value::Int(1))]));
assert_eq!(results[1], obj(&[("x", Value::Int(2))]));
}
#[test]
fn eval_object_construct_multi_pair_generator() {
let results = eval_all(&parse("{a: (1,2), b: (3,4)}"), &Value::Null);
assert_eq!(results.len(), 4);
}
#[test]
fn eval_split_empty() {
assert_eq!(
eval_one(&parse("split(\"\")"), &Value::String("abc".into())),
Value::Array(Arc::new(vec![
Value::String("a".into()),
Value::String("b".into()),
Value::String("c".into()),
]))
);
}
#[test]
fn eval_ascii_downcase_only_ascii() {
assert_eq!(
eval_one(&parse("ascii_downcase"), &Value::String("ABCé".into())),
Value::String("abcé".into())
);
}
#[test]
fn eval_explode() {
assert_eq!(
eval_one(&parse("explode"), &Value::String("abc".into())),
Value::Array(Arc::new(vec![
Value::Int(97),
Value::Int(98),
Value::Int(99)
]))
);
}
#[test]
fn eval_implode() {
let input = Value::Array(Arc::new(vec![
Value::Int(97),
Value::Int(98),
Value::Int(99),
]));
assert_eq!(
eval_one(&parse("implode"), &input),
Value::String("abc".into())
);
}
#[test]
fn eval_tojson() {
let input = Value::Array(Arc::new(vec![Value::Int(1), Value::Int(2)]));
assert_eq!(
eval_one(&parse("tojson"), &input),
Value::String("[1,2]".into())
);
}
#[test]
fn eval_utf8bytelength() {
assert_eq!(
eval_one(&parse("utf8bytelength"), &Value::String("é".into())),
Value::Int(2)
);
assert_eq!(
eval_one(&parse("utf8bytelength"), &Value::String("abc".into())),
Value::Int(3)
);
}
#[test]
fn eval_inside() {
let input = Value::String("foo".into());
assert_eq!(
eval_one(&parse("inside(\"foobar\")"), &input),
Value::Bool(true)
);
assert_eq!(
eval_one(&parse("inside(\"bar\")"), &input),
Value::Bool(false)
);
}
#[test]
fn eval_index_builtin() {
assert_eq!(
eval_one(&parse("index(\"b\")"), &Value::String("abc".into())),
Value::Int(1)
);
assert_eq!(
eval_one(&parse("index(\"z\")"), &Value::String("abc".into())),
Value::Null
);
}
#[test]
fn eval_rindex_builtin() {
assert_eq!(
eval_one(&parse("rindex(\"o\")"), &Value::String("fooboo".into())),
Value::Int(5)
);
}
#[test]
fn eval_indices_builtin() {
assert_eq!(
eval_one(&parse("indices(\"o\")"), &Value::String("foobar".into())),
Value::Array(Arc::new(vec![Value::Int(1), Value::Int(2)]))
);
}
#[test]
fn eval_trim() {
assert_eq!(
eval_one(&parse("trim"), &Value::String(" hi ".into())),
Value::String("hi".into())
);
assert_eq!(
eval_one(&parse("ltrim"), &Value::String(" hi ".into())),
Value::String("hi ".into())
);
assert_eq!(
eval_one(&parse("rtrim"), &Value::String(" hi ".into())),
Value::String(" hi".into())
);
}
#[test]
fn eval_string_repeat() {
assert_eq!(
eval_one(&parse("\"ab\" * 3"), &Value::Null),
Value::String("ababab".into())
);
}
#[test]
fn eval_string_repeat_zero() {
assert_eq!(
eval_one(&parse("\"ab\" * 0"), &Value::Null),
Value::String(String::new())
);
}
#[test]
fn eval_string_divide() {
assert_eq!(
eval_one(&parse("\"a,b,c\" / \",\""), &Value::Null),
Value::Array(Arc::new(vec![
Value::String("a".into()),
Value::String("b".into()),
Value::String("c".into()),
]))
);
}
#[test]
fn eval_from_entries_capitalized() {
let input = Value::Array(Arc::new(vec![Value::Object(Arc::new(vec![
("Key".into(), Value::String("x".into())),
("Value".into(), Value::Int(42)),
]))]));
assert_eq!(
eval_one(&parse("from_entries"), &input),
obj(&[("x", Value::Int(42))])
);
}
#[test]
fn eval_values_iterates() {
let input = obj(&[("a", Value::Int(1)), ("b", Value::Int(2))]);
let results = eval_all(&parse("values"), &input);
assert_eq!(results, vec![input.clone()]);
let null_results = eval_all(&parse("values"), &Value::Null);
assert_eq!(null_results, vec![] as Vec<Value>);
}
#[test]
fn eval_index_generator() {
let input = Value::Array(Arc::new(vec![
Value::Int(10),
Value::Int(20),
Value::Int(30),
]));
assert_eq!(
eval_all(&parse(".[0,2]"), &input),
vec![Value::Int(10), Value::Int(30)]
);
}
#[test]
fn eval_array_subtraction() {
let a = Value::Array(Arc::new(vec![Value::Int(1), Value::Int(2), Value::Int(3)]));
let b = Value::Array(Arc::new(vec![Value::Int(2)]));
assert_eq!(
arith_values(&a, &ArithOp::Sub, &b),
Ok(Value::Array(Arc::new(vec![Value::Int(1), Value::Int(3)])))
);
}
#[test]
fn eval_object_recursive_merge() {
let result = eval_one(
&parse("{\"a\":{\"b\":1}} * {\"a\":{\"c\":2}}"),
&Value::Null,
);
if let Value::Object(outer) = &result {
let a_val = &outer.iter().find(|(k, _)| k == "a").unwrap().1;
if let Value::Object(inner) = a_val {
assert_eq!(inner.len(), 2);
} else {
panic!("expected inner object");
}
} else {
panic!("expected outer object");
}
}
#[test]
fn eval_float_modulo() {
let result = arith_values(&Value::Double(10.5, None), &ArithOp::Mod, &Value::Int(3));
assert_eq!(result, Ok(Value::Int(1)));
}
#[test]
fn eval_int_division_float_result() {
let result = arith_values(&Value::Int(1), &ArithOp::Div, &Value::Int(3));
match result {
Ok(Value::Double(f, _)) => assert!((f - 1.0 / 3.0).abs() < 1e-10),
other => panic!("expected Double(0.333...), got {other:?}"),
}
}
#[test]
fn eval_int_division_exact() {
assert_eq!(
arith_values(&Value::Int(6), &ArithOp::Div, &Value::Int(3)),
Ok(Value::Int(2))
);
}
#[test]
fn eval_compare_generator() {
let results = eval_all(&parse("(1,2) > 1"), &Value::Null);
assert_eq!(results, vec![Value::Bool(false), Value::Bool(true)]);
}
#[test]
fn eval_arith_generator() {
let results = eval_all(&parse("(1,2) + (10,20)"), &Value::Null);
assert_eq!(
results,
vec![
Value::Int(11),
Value::Int(12),
Value::Int(21),
Value::Int(22),
]
);
}
#[test]
fn eval_transpose() {
let input = Value::Array(Arc::new(vec![
Value::Array(Arc::new(vec![Value::Int(1), Value::Int(2)])),
Value::Array(Arc::new(vec![Value::Int(3), Value::Int(4)])),
]));
assert_eq!(
eval_one(&parse("transpose"), &input),
Value::Array(Arc::new(vec![
Value::Array(Arc::new(vec![Value::Int(1), Value::Int(3)])),
Value::Array(Arc::new(vec![Value::Int(2), Value::Int(4)])),
]))
);
}
#[test]
fn eval_transpose_uneven() {
let input = Value::Array(Arc::new(vec![
Value::Array(Arc::new(vec![Value::Int(1)])),
Value::Array(Arc::new(vec![Value::Int(2), Value::Int(3)])),
]));
let result = eval_one(&parse("transpose"), &input);
assert_eq!(
result,
Value::Array(Arc::new(vec![
Value::Array(Arc::new(vec![Value::Int(1), Value::Int(2)])),
Value::Array(Arc::new(vec![Value::Null, Value::Int(3)])),
]))
);
}
#[test]
fn eval_map_values_object() {
let input = obj(&[("a", Value::Int(1)), ("b", Value::Int(2))]);
assert_eq!(
eval_one(&parse("map_values(. + 10)"), &input),
obj(&[("a", Value::Int(11)), ("b", Value::Int(12))])
);
}
#[test]
fn eval_limit() {
assert_eq!(
eval_all(&parse("limit(3; range(100))"), &Value::Null),
vec![Value::Int(0), Value::Int(1), Value::Int(2)]
);
}
#[test]
fn eval_until() {
assert_eq!(
eval_one(&parse("0 | until(. >= 5; . + 1)"), &Value::Null),
Value::Int(5)
);
}
#[test]
fn eval_while() {
let input = Value::Null;
let results = eval_all(&parse("[1 | while(. < 8; . * 2)]"), &input);
assert_eq!(
results,
vec![Value::Array(Arc::new(vec![
Value::Int(1),
Value::Int(2),
Value::Int(4),
]))]
);
}
#[test]
fn eval_isempty_true() {
assert_eq!(
eval_one(&parse("isempty(empty)"), &Value::Null),
Value::Bool(true)
);
}
#[test]
fn eval_isempty_false() {
assert_eq!(
eval_one(&parse("isempty(range(3))"), &Value::Null),
Value::Bool(false)
);
}
#[test]
fn eval_getpath() {
let input = obj(&[("a", obj(&[("b", Value::Int(42))]))]);
assert_eq!(
eval_one(&parse("getpath([\"a\",\"b\"])"), &input),
Value::Int(42)
);
}
#[test]
fn eval_getpath_missing() {
let input = obj(&[("a", Value::Int(1))]);
assert_eq!(eval_one(&parse("getpath([\"x\"])"), &input), Value::Null);
}
#[test]
fn eval_setpath() {
let input = obj(&[("a", obj(&[("b", Value::Int(1))]))]);
let result = eval_one(&parse("setpath([\"a\",\"b\"]; 99)"), &input);
assert_eq!(result, obj(&[("a", obj(&[("b", Value::Int(99))]))]));
}
#[test]
fn eval_delpaths() {
let input = obj(&[("a", Value::Int(1)), ("b", Value::Int(2))]);
assert_eq!(
eval_one(&parse("delpaths([[\"a\"]])"), &input),
obj(&[("b", Value::Int(2))])
);
}
#[test]
fn eval_paths_no_filter() {
let input = obj(&[("a", Value::Int(1)), ("b", obj(&[("c", Value::Int(2))]))]);
let results = eval_all(&parse("paths"), &input);
assert_eq!(results.len(), 3); }
#[test]
fn eval_bsearch_found() {
let input = Value::Array(Arc::new(vec![
Value::Int(1),
Value::Int(2),
Value::Int(3),
Value::Int(4),
Value::Int(5),
]));
assert_eq!(eval_one(&parse("bsearch(3)"), &input), Value::Int(2));
}
#[test]
fn eval_bsearch_not_found() {
let input = Value::Array(Arc::new(vec![Value::Int(1), Value::Int(3), Value::Int(5)]));
assert_eq!(eval_one(&parse("bsearch(2)"), &input), Value::Int(-2));
}
#[test]
fn eval_in_builtin() {
assert_eq!(
eval_one(&parse("IN(2, 3)"), &Value::Int(3)),
Value::Bool(true)
);
assert_eq!(
eval_one(&parse("IN(2, 3)"), &Value::Int(5)),
Value::Bool(false)
);
}
#[test]
fn eval_with_entries() {
let input = obj(&[("a", Value::Int(1)), ("b", Value::Int(2))]);
let result = eval_one(&parse("with_entries(select(.value > 1))"), &input);
assert_eq!(result, obj(&[("b", Value::Int(2))]));
}
#[test]
fn eval_abs() {
assert_eq!(eval_one(&parse("abs"), &Value::Int(-42)), Value::Int(42));
assert_eq!(eval_one(&parse("abs"), &Value::Int(42)), Value::Int(42));
}
#[test]
fn eval_debug_passthrough() {
assert_eq!(eval_one(&parse("debug"), &Value::Int(42)), Value::Int(42));
}
#[test]
fn eval_error_no_output() {
assert_eq!(eval_all(&parse("error"), &Value::Int(42)), vec![]);
}
#[test]
fn eval_nth() {
assert_eq!(
eval_one(&parse("nth(2; range(5))"), &Value::Null),
Value::Int(2)
);
}
#[test]
fn eval_repeat() {
let results = eval_all(&parse("limit(3; 5 | repeat(. + 1))"), &Value::Null);
assert_eq!(results, vec![Value::Int(6), Value::Int(6), Value::Int(6)]);
}
#[test]
fn eval_recurse_with_filter_and_cond() {
let results = eval_all(&parse("2 | recurse(. * .; . < 100)"), &Value::Null);
assert_eq!(results, vec![Value::Int(2), Value::Int(4), Value::Int(16)]);
}
#[test]
fn eval_string_interp_with_array() {
use crate::filter::StringPart;
let filter = Filter::StringInterp(vec![
StringPart::Lit("items: ".to_string()),
StringPart::Expr(Filter::Literal(Value::Array(Arc::new(vec![
Value::Int(1),
Value::Int(2),
])))),
]);
let result = eval_one(&filter, &Value::Null);
assert_eq!(result, Value::String("items: [1,2]".to_string()));
}
#[test]
fn eval_string_interp_with_object() {
use crate::filter::StringPart;
let filter = Filter::StringInterp(vec![
StringPart::Lit("obj: ".to_string()),
StringPart::Expr(Filter::Literal(Value::Object(Arc::new(vec![(
"a".to_string(),
Value::Int(1),
)])))),
]);
let result = eval_one(&filter, &Value::Null);
assert_eq!(result, Value::String(r#"obj: {"a":1}"#.to_string()));
}
#[test]
fn eval_tostring_array() {
let result = eval_one(&parse("[1,2,3] | tostring"), &Value::Null);
assert_eq!(result, Value::String("[1,2,3]".to_string()));
}
#[test]
fn eval_tostring_object() {
let result = eval_one(&parse(r#"{"a":1} | tostring"#), &Value::Null);
assert_eq!(result, Value::String(r#"{"a":1}"#.to_string()));
}
#[test]
fn eval_logb() {
let result = eval_one(&parse("1 | logb"), &Value::Null);
assert_eq!(result, Value::Double(0.0, None));
let result = eval_one(&parse("8 | logb"), &Value::Null);
assert_eq!(result, Value::Double(3.0, None));
}
#[test]
fn eval_scalb() {
let result = eval_one(&parse("2 | scalb(3)"), &Value::Null);
assert_eq!(result, Value::Int(16));
let result = eval_one(&parse("1 | scalb(10)"), &Value::Null);
assert_eq!(result, Value::Int(1024));
}
#[test]
fn eval_env() {
let result = eval_one(&parse("env | keys | length"), &Value::Null);
match result {
Value::Int(n) => assert!(n > 0, "env should have entries"),
_ => panic!("expected int from env | keys | length"),
}
}
#[test]
fn eval_var_basic() {
let f = parse(". as $x | $x");
assert_eq!(eval_one(&f, &Value::Int(42)), Value::Int(42));
}
#[test]
fn eval_var_shadowing() {
let f = parse("1 as $x | 2 as $x | $x");
assert_eq!(eval_one(&f, &Value::Null), Value::Int(2));
}
#[test]
fn eval_var_multiple_outputs() {
let f = parse(".[] as $x | $x * $x");
let input = Value::Array(Arc::new(vec![Value::Int(2), Value::Int(3)]));
assert_eq!(eval_all(&f, &input), vec![Value::Int(4), Value::Int(9)]);
}
#[test]
fn eval_var_in_arith() {
let f = parse(".a as $x | .b as $y | $x + $y");
let input = Value::Object(Arc::new(vec![
("a".into(), Value::Int(10)),
("b".into(), Value::Int(20)),
]));
assert_eq!(eval_one(&f, &input), Value::Int(30));
}
#[test]
fn eval_var_undefined() {
let f = parse("$nonexistent");
let results = eval_all(&f, &Value::Null);
assert!(results.is_empty());
}
#[test]
fn eval_env_var() {
let f = parse("$ENV | type");
assert_eq!(eval_one(&f, &Value::Null), Value::String("object".into()));
}
#[test]
fn eval_slice_array() {
let f = parse(".[1:3]");
let input = Value::Array(Arc::new(vec![
Value::Int(10),
Value::Int(20),
Value::Int(30),
Value::Int(40),
]));
assert_eq!(
eval_one(&f, &input),
Value::Array(Arc::new(vec![Value::Int(20), Value::Int(30)]))
);
}
#[test]
fn eval_slice_array_no_start() {
let f = parse(".[:2]");
let input = Value::Array(Arc::new(vec![Value::Int(1), Value::Int(2), Value::Int(3)]));
assert_eq!(
eval_one(&f, &input),
Value::Array(Arc::new(vec![Value::Int(1), Value::Int(2)]))
);
}
#[test]
fn eval_slice_array_no_end() {
let f = parse(".[1:]");
let input = Value::Array(Arc::new(vec![Value::Int(1), Value::Int(2), Value::Int(3)]));
assert_eq!(
eval_one(&f, &input),
Value::Array(Arc::new(vec![Value::Int(2), Value::Int(3)]))
);
}
#[test]
fn eval_slice_array_negative() {
let f = parse(".[-2:]");
let input = Value::Array(Arc::new(vec![
Value::Int(1),
Value::Int(2),
Value::Int(3),
Value::Int(4),
]));
assert_eq!(
eval_one(&f, &input),
Value::Array(Arc::new(vec![Value::Int(3), Value::Int(4)]))
);
}
#[test]
fn eval_slice_array_empty_range() {
let f = parse(".[3:1]");
let input = Value::Array(Arc::new(vec![Value::Int(1), Value::Int(2), Value::Int(3)]));
assert_eq!(eval_one(&f, &input), Value::Array(Arc::new(vec![])));
}
#[test]
fn eval_slice_array_out_of_bounds() {
let f = parse(".[0:100]");
let input = Value::Array(Arc::new(vec![Value::Int(1), Value::Int(2)]));
assert_eq!(
eval_one(&f, &input),
Value::Array(Arc::new(vec![Value::Int(1), Value::Int(2)]))
);
}
#[test]
fn eval_slice_string() {
let f = parse(".[1:4]");
assert_eq!(
eval_one(&f, &Value::String("abcdef".into())),
Value::String("bcd".into())
);
}
#[test]
fn eval_slice_string_negative() {
let f = parse(".[-3:]");
assert_eq!(
eval_one(&f, &Value::String("abcdef".into())),
Value::String("def".into())
);
}
#[test]
fn eval_slice_null_input() {
let f = parse(".[0:1]");
assert_eq!(eval_one(&f, &Value::Null), Value::Null);
}
#[test]
fn eval_elif_first_branch() {
let f = parse(r#"if . < 0 then "neg" elif . == 0 then "zero" else "pos" end"#);
assert_eq!(eval_one(&f, &Value::Int(-5)), Value::String("neg".into()));
}
#[test]
fn eval_elif_middle_branch() {
let f = parse(r#"if . < 0 then "neg" elif . == 0 then "zero" else "pos" end"#);
assert_eq!(eval_one(&f, &Value::Int(0)), Value::String("zero".into()));
}
#[test]
fn eval_elif_else_branch() {
let f = parse(r#"if . < 0 then "neg" elif . == 0 then "zero" else "pos" end"#);
assert_eq!(eval_one(&f, &Value::Int(5)), Value::String("pos".into()));
}
#[test]
fn eval_elif_three_branches() {
let f = parse(
r#"if . == 1 then "one" elif . == 2 then "two" elif . == 3 then "three" else "other" end"#,
);
assert_eq!(eval_one(&f, &Value::Int(2)), Value::String("two".into()));
assert_eq!(eval_one(&f, &Value::Int(3)), Value::String("three".into()));
assert_eq!(eval_one(&f, &Value::Int(99)), Value::String("other".into()));
}
#[test]
fn eval_try_keyword_success() {
let f = parse("try .a");
let input = Value::Object(Arc::new(vec![("a".into(), Value::Int(1))]));
assert_eq!(eval_one(&f, &input), Value::Int(1));
}
#[test]
fn eval_try_keyword_error() {
let f = parse("try error");
let results = eval_all(&f, &Value::Null);
assert!(results.is_empty(), "try should suppress error");
}
#[test]
fn eval_try_catch_no_error() {
let f = parse("try . catch .");
assert_eq!(eval_one(&f, &Value::Int(42)), Value::Int(42));
}
#[test]
fn eval_try_catch_with_error() {
let f = parse(r#"try error("boom") catch ."#);
assert_eq!(eval_one(&f, &Value::Null), Value::String("boom".into()));
}
#[test]
fn eval_try_catch_error_no_arg() {
let f = parse("try error catch .");
assert_eq!(eval_one(&f, &Value::Int(99)), Value::Int(99));
}
#[test]
fn eval_reduce_sum() {
let f = parse("reduce .[] as $x (0; . + $x)");
let input = Value::Array(Arc::new(vec![Value::Int(1), Value::Int(2), Value::Int(3)]));
assert_eq!(eval_one(&f, &input), Value::Int(6));
}
#[test]
fn eval_reduce_empty() {
let f = parse("reduce .[] as $x (0; . + $x)");
let input = Value::Array(Arc::new(vec![]));
assert_eq!(eval_one(&f, &input), Value::Int(0));
}
#[test]
fn eval_reduce_build_array() {
let f = parse("reduce .[] as $x ([]; . + [$x * 2])");
let input = Value::Array(Arc::new(vec![Value::Int(1), Value::Int(2), Value::Int(3)]));
assert_eq!(
eval_one(&f, &input),
Value::Array(Arc::new(vec![Value::Int(2), Value::Int(4), Value::Int(6)]))
);
}
#[test]
fn eval_foreach_running_sum() {
let f = parse("[foreach .[] as $x (0; . + $x)]");
let input = Value::Array(Arc::new(vec![Value::Int(1), Value::Int(2), Value::Int(3)]));
assert_eq!(
eval_one(&f, &input),
Value::Array(Arc::new(vec![Value::Int(1), Value::Int(3), Value::Int(6)]))
);
}
#[test]
fn eval_foreach_with_extract() {
let f = parse("[foreach .[] as $x (0; . + $x; . * 10)]");
let input = Value::Array(Arc::new(vec![Value::Int(1), Value::Int(2), Value::Int(3)]));
assert_eq!(
eval_one(&f, &input),
Value::Array(Arc::new(vec![
Value::Int(10),
Value::Int(30),
Value::Int(60)
]))
);
}
#[test]
fn eval_foreach_empty() {
let f = parse("[foreach .[] as $x (0; . + $x)]");
let input = Value::Array(Arc::new(vec![]));
assert_eq!(eval_one(&f, &input), Value::Array(Arc::new(vec![])));
}
#[test]
fn eval_walk_numbers() {
let f = parse("walk(if type == \"number\" then . + 1 else . end)");
let input = Value::Array(Arc::new(vec![
Value::Int(1),
Value::Array(Arc::new(vec![Value::Int(2)])),
]));
assert_eq!(
eval_one(&f, &input),
Value::Array(Arc::new(vec![
Value::Int(2),
Value::Array(Arc::new(vec![Value::Int(3)])),
]))
);
}
#[test]
fn eval_walk_scalar() {
let f = parse("walk(. + 1)");
assert_eq!(eval_one(&f, &Value::Int(5)), Value::Int(6));
}
#[test]
fn eval_walk_object() {
let f = parse(r#"walk(if type == "string" then "X" else . end)"#);
let input = Value::Object(Arc::new(vec![("a".into(), Value::String("hello".into()))]));
assert_eq!(
eval_one(&f, &input),
Value::Object(Arc::new(vec![("a".into(), Value::String("X".into()))]))
);
}
#[test]
fn overflow_add_promotes_to_double() {
let f = parse(". + 1");
let result = eval_one(&f, &Value::Int(i64::MAX));
match result {
Value::Double(v, _) => {
assert!((v - 9.223372036854776e18).abs() < 1e4);
}
other => panic!("expected Double, got {other:?}"),
}
}
#[test]
fn overflow_sub_promotes_to_double() {
let f = parse(". - 1");
let result = eval_one(&f, &Value::Int(i64::MIN));
match result {
Value::Double(v, _) => assert!((v - (-9.223372036854776e18)).abs() < 1e4),
Value::Int(n) => assert_eq!(n, i64::MIN),
other => panic!("expected Double or Int, got {other:?}"),
}
}
#[test]
fn overflow_mul_promotes_to_double() {
let f = parse(". * 2");
let result = eval_one(&f, &Value::Int(i64::MAX));
match result {
Value::Double(v, _) => assert!(v > i64::MAX as f64),
other => panic!("expected Double, got {other:?}"),
}
}
#[test]
fn overflow_neg_promotes_to_double() {
let f = parse("-.");
let result = eval_one(&f, &Value::Int(i64::MIN));
match result {
Value::Double(v, _) => assert!(v > 0.0),
other => panic!("expected Double, got {other:?}"),
}
}
#[test]
fn no_overflow_stays_int() {
let f = parse(". + 1");
assert_eq!(eval_one(&f, &Value::Int(100)), Value::Int(101));
let f = parse(". - 1");
assert_eq!(eval_one(&f, &Value::Int(-100)), Value::Int(-101));
let f = parse(". * 2");
assert_eq!(eval_one(&f, &Value::Int(50)), Value::Int(100));
let f = parse("-.");
assert_eq!(eval_one(&f, &Value::Int(-42)), Value::Int(42));
}
#[test]
fn overflow_div_i64_min_by_neg1() {
let f = parse(". / -1");
let result = eval_one(&f, &Value::Int(i64::MIN));
match result {
Value::Double(v, _) => assert!(v > 0.0, "should be positive: {v}"),
other => panic!("expected Double, got {other:?}"),
}
}
#[test]
fn overflow_mod_i64_min_by_neg1() {
let f = parse(". % -1");
assert_eq!(eval_one(&f, &Value::Int(i64::MIN)), Value::Int(0));
}
#[test]
fn abs_i64_min_promotes_to_double() {
let f = parse("abs");
let result = eval_one(&f, &Value::Int(i64::MIN));
match result {
Value::Double(v, _) => assert!(v > 0.0),
other => panic!("expected Double, got {other:?}"),
}
}
#[test]
fn length_i64_min_promotes_to_double() {
let f = parse("length");
let result = eval_one(&f, &Value::Int(i64::MIN));
match result {
Value::Double(v, _) => assert!(v > 0.0),
other => panic!("expected Double, got {other:?}"),
}
}
#[test]
fn floor_large_double_stays_double() {
let f = parse("floor");
let result = eval_one(&f, &Value::Double(9223372036854775808.0, None));
match result {
Value::Double(v, _) => assert_eq!(v, 9223372036854775808.0),
other => panic!("expected Double for 2^63, got {other:?}"),
}
}
#[test]
fn div_normal_stays_int() {
assert_eq!(eval_one(&parse(". / 2"), &Value::Int(10)), Value::Int(5));
}
#[test]
fn mod_normal() {
assert_eq!(eval_one(&parse(". % 3"), &Value::Int(10)), Value::Int(1));
}
#[test]
fn int_div_by_zero_error() {
assert!(eval_all(&parse("1 / 0"), &Value::Null).is_empty());
}
#[test]
fn int_mod_by_zero_error() {
assert!(eval_all(&parse("1 % 0"), &Value::Null).is_empty());
}
#[test]
fn float_div_by_zero_error() {
assert!(eval_all(&parse("1.0 / 0.0"), &Value::Null).is_empty());
}
#[test]
fn float_zero_div_zero_error() {
assert!(eval_all(&parse("0.0 / 0.0"), &Value::Null).is_empty());
}
#[test]
fn div_by_zero_catchable() {
let result = eval_all(&parse("try (1/0) catch ."), &Value::Null);
assert_eq!(result.len(), 1);
match &result[0] {
Value::String(s) => assert!(s.contains("cannot be divided"), "got: {s}"),
other => panic!("expected error string, got {other:?}"),
}
}
#[test]
fn mod_by_zero_catchable() {
let result = eval_all(&parse("try (1%0) catch ."), &Value::Null);
assert_eq!(result.len(), 1);
match &result[0] {
Value::String(s) => assert!(s.contains("remainder"), "got: {s}"),
other => panic!("expected error string, got {other:?}"),
}
}
#[test]
fn range_step_zero_no_output() {
assert!(eval_all(&parse("range(0; 10; 0)"), &Value::Null).is_empty());
}
#[test]
fn range_nan_no_output() {
assert!(eval_all(&parse("range(nan)"), &Value::Null).is_empty());
}
#[test]
fn range_negative_step() {
let results = eval_all(&parse("range(5; 0; -2)"), &Value::Null);
assert_eq!(results, vec![Value::Int(5), Value::Int(3), Value::Int(1)]);
}
#[test]
fn implode_negative_codepoint_replaced() {
let input = Value::Array(Arc::new(vec![Value::Int(-1)]));
assert_eq!(
eval_one(&parse("implode"), &input),
Value::String("\u{FFFD}".into())
);
}
#[test]
fn implode_valid_codepoints() {
let input = Value::Array(Arc::new(vec![Value::Int(65), Value::Int(66)]));
assert_eq!(
eval_one(&parse("implode"), &input),
Value::String("AB".into())
);
}
#[test]
fn tonumber_overflow_i64_falls_to_f64() {
let input = Value::String("99999999999999999999".into());
let result = eval_one(&parse("tonumber"), &input);
match result {
Value::Double(v, _) => assert!(v > 1e19),
Value::Int(_) => panic!("should be f64 for overflowing string"),
other => panic!("expected number, got {other:?}"),
}
}
#[test]
fn tonumber_already_int() {
assert_eq!(
eval_one(&parse("tonumber"), &Value::Int(42)),
Value::Int(42)
);
}
#[test]
fn ceil_large_double_stays_double() {
let f = parse("ceil");
let result = eval_one(&f, &Value::Double(9223372036854775808.0, None));
match result {
Value::Double(v, _) => assert_eq!(v, 9223372036854775808.0),
other => panic!("expected Double for 2^63, got {other:?}"),
}
}
#[test]
fn round_large_double_stays_double() {
let f = parse("round");
let result = eval_one(&f, &Value::Double(9223372036854775808.0, None));
match result {
Value::Double(v, _) => assert_eq!(v, 9223372036854775808.0),
other => panic!("expected Double for 2^63, got {other:?}"),
}
}
#[test]
fn limit_zero_no_output() {
assert!(eval_all(&parse("limit(0; range(10))"), &Value::Null).is_empty());
}
#[test]
fn limit_normal() {
let results = eval_all(&parse("limit(3; range(10))"), &Value::Null);
assert_eq!(results, vec![Value::Int(0), Value::Int(1), Value::Int(2)]);
}
#[test]
fn split_empty_string() {
let input = Value::String("abc".into());
let result = eval_one(&parse(r#"split("")"#), &input);
assert_eq!(
result,
Value::Array(Arc::new(vec![
Value::String("a".into()),
Value::String("b".into()),
Value::String("c".into()),
]))
);
}
#[test]
fn split_no_match() {
let input = Value::String("hello".into());
let result = eval_one(&parse(r#"split("xyz")"#), &input);
assert_eq!(
result,
Value::Array(Arc::new(vec![Value::String("hello".into())]))
);
}
#[test]
fn join_empty_array() {
let input = Value::Array(Arc::new(vec![]));
let result = eval_one(&parse(r#"join(",")"#), &input);
assert_eq!(result, Value::String("".into()));
}
#[test]
fn ltrimstr_no_match() {
let input = Value::String("hello".into());
let result = eval_one(&parse(r#"ltrimstr("xyz")"#), &input);
assert_eq!(result, Value::String("hello".into()));
}
#[test]
fn rtrimstr_no_match() {
let input = Value::String("hello".into());
let result = eval_one(&parse(r#"rtrimstr("xyz")"#), &input);
assert_eq!(result, Value::String("hello".into()));
}
#[test]
fn ascii_downcase_non_ascii() {
let input = Value::String("Héllo".into());
let result = eval_one(&parse("ascii_downcase"), &input);
assert_eq!(result, Value::String("héllo".into()));
}
#[test]
fn tostring_types() {
assert_eq!(
eval_one(&parse("tostring"), &Value::Int(42)),
Value::String("42".into())
);
assert_eq!(
eval_one(&parse("tostring"), &Value::Null),
Value::String("null".into())
);
assert_eq!(
eval_one(&parse("tostring"), &Value::Bool(true)),
Value::String("true".into())
);
assert_eq!(
eval_one(&parse("tostring"), &Value::String("hi".into())),
Value::String("hi".into())
);
}
#[test]
fn add_empty_array() {
let input = Value::Array(Arc::new(vec![]));
assert_eq!(eval_one(&parse("add"), &input), Value::Null);
}
#[test]
fn add_single_element() {
let input = Value::Array(Arc::new(vec![Value::Int(5)]));
assert_eq!(eval_one(&parse("add"), &input), Value::Int(5));
}
#[test]
fn add_strings() {
let input = Value::Array(Arc::new(vec![
Value::String("a".into()),
Value::String("b".into()),
]));
assert_eq!(eval_one(&parse("add"), &input), Value::String("ab".into()));
}
#[test]
fn flatten_already_flat() {
let input = Value::Array(Arc::new(vec![Value::Int(1), Value::Int(2)]));
assert_eq!(
eval_one(&parse("flatten"), &input),
Value::Array(Arc::new(vec![Value::Int(1), Value::Int(2)]))
);
}
#[test]
fn flatten_empty() {
let input = Value::Array(Arc::new(vec![]));
assert_eq!(
eval_one(&parse("flatten"), &input),
Value::Array(Arc::new(vec![]))
);
}
#[test]
fn flatten_depth_zero() {
let inner = Value::Array(Arc::new(vec![Value::Int(1)]));
let input = Value::Array(Arc::new(vec![inner.clone()]));
assert_eq!(
eval_one(&parse("flatten(0)"), &input),
Value::Array(Arc::new(vec![inner]))
);
}
#[test]
fn first_empty_array() {
let input = Value::Array(Arc::new(vec![]));
assert_eq!(eval_one(&parse("first"), &input), Value::Null);
}
#[test]
fn last_empty_array() {
let input = Value::Array(Arc::new(vec![]));
assert_eq!(eval_one(&parse("last"), &input), Value::Null);
}
#[test]
fn reverse_empty() {
let input = Value::Array(Arc::new(vec![]));
assert_eq!(
eval_one(&parse("reverse"), &input),
Value::Array(Arc::new(vec![]))
);
}
#[test]
fn reverse_string() {
let input = Value::String("abc".into());
assert_eq!(
eval_one(&parse("reverse"), &input),
Value::String("cba".into())
);
}
#[test]
fn sort_empty() {
let input = Value::Array(Arc::new(vec![]));
assert_eq!(
eval_one(&parse("sort"), &input),
Value::Array(Arc::new(vec![]))
);
}
#[test]
fn unique_empty() {
let input = Value::Array(Arc::new(vec![]));
assert_eq!(
eval_one(&parse("unique"), &input),
Value::Array(Arc::new(vec![]))
);
}
#[test]
fn group_by_empty() {
let input = Value::Array(Arc::new(vec![]));
assert_eq!(
eval_one(&parse("group_by(.)"), &input),
Value::Array(Arc::new(vec![]))
);
}
#[test]
fn min_by_empty() {
let input = Value::Array(Arc::new(vec![]));
assert_eq!(eval_one(&parse("min_by(.)"), &input), Value::Null);
}
#[test]
fn max_by_empty() {
let input = Value::Array(Arc::new(vec![]));
assert_eq!(eval_one(&parse("max_by(.)"), &input), Value::Null);
}
#[test]
fn transpose_ragged() {
let input = Value::Array(Arc::new(vec![
Value::Array(Arc::new(vec![Value::Int(1), Value::Int(2)])),
Value::Array(Arc::new(vec![Value::Int(3)])),
]));
let result = eval_one(&parse("transpose"), &input);
assert_eq!(
result,
Value::Array(Arc::new(vec![
Value::Array(Arc::new(vec![Value::Int(1), Value::Int(3)])),
Value::Array(Arc::new(vec![Value::Int(2), Value::Null])),
]))
);
}
#[test]
fn transpose_empty() {
let input = Value::Array(Arc::new(vec![]));
assert_eq!(
eval_one(&parse("transpose"), &input),
Value::Array(Arc::new(vec![]))
);
}
#[test]
fn getpath_missing() {
let input = Value::Object(Arc::new(vec![("a".into(), Value::Int(1))]));
assert_eq!(eval_one(&parse(r#"getpath(["b"])"#), &input), Value::Null);
}
#[test]
fn getpath_deep_missing() {
assert_eq!(
eval_one(&parse(r#"getpath(["a","b","c"])"#), &Value::Null),
Value::Null
);
}
#[test]
fn getpath_empty_path() {
let input = Value::Int(42);
assert_eq!(eval_one(&parse("getpath([])"), &input), Value::Int(42));
}
#[test]
fn setpath_creates_nested() {
let result = eval_one(&parse(r#"setpath(["a","b"]; 1)"#), &Value::Null);
if let Value::Object(obj) = &result {
if let Some((_, Value::Object(inner))) = obj.iter().find(|(k, _)| k == "a") {
assert_eq!(
inner.iter().find(|(k, _)| k == "b").map(|(_, v)| v),
Some(&Value::Int(1))
);
return;
}
}
panic!("expected {{\"a\":{{\"b\":1}}}}, got {result:?}");
}
#[test]
fn fromjson_invalid() {
let input = Value::String("not json".into());
assert!(eval_all(&parse("fromjson"), &input).is_empty());
}
#[test]
fn fromjson_valid() {
let input = Value::String(r#"{"a":1}"#.into());
let result = eval_one(&parse("fromjson"), &input);
assert_eq!(
result,
Value::Object(Arc::new(vec![("a".into(), Value::Int(1))]))
);
}
#[test]
fn tojson_roundtrip() {
let input = Value::Array(Arc::new(vec![Value::Int(1), Value::Bool(true)]));
let json_str = eval_one(&parse("tojson"), &input);
let roundtrip = eval_one(&parse("fromjson"), &json_str);
assert_eq!(roundtrip, input);
}
#[test]
fn contains_partial_object() {
let input = Value::Object(Arc::new(vec![
("a".into(), Value::Int(1)),
("b".into(), Value::Int(2)),
]));
assert_eq!(
eval_one(&parse(r#"contains({"a":1})"#), &input),
Value::Bool(true)
);
}
#[test]
fn contains_missing_key() {
let input = Value::Object(Arc::new(vec![("a".into(), Value::Int(1))]));
assert_eq!(
eval_one(&parse(r#"contains({"x":1})"#), &input),
Value::Bool(false)
);
}
#[test]
fn select_false_no_output() {
assert!(eval_all(&parse("select(false)"), &Value::Int(1)).is_empty());
}
#[test]
fn select_null_no_output() {
assert!(eval_all(&parse("select(null)"), &Value::Int(1)).is_empty());
}
#[test]
fn empty_no_output() {
assert!(eval_all(&parse("empty"), &Value::Int(1)).is_empty());
}
#[test]
fn type_of_all_types() {
assert_eq!(
eval_one(&parse("type"), &Value::Null),
Value::String("null".into())
);
assert_eq!(
eval_one(&parse("type"), &Value::Bool(true)),
Value::String("boolean".into())
);
assert_eq!(
eval_one(&parse("type"), &Value::Int(1)),
Value::String("number".into())
);
assert_eq!(
eval_one(&parse("type"), &Value::Double(1.5, None)),
Value::String("number".into())
);
assert_eq!(
eval_one(&parse("type"), &Value::String("x".into())),
Value::String("string".into())
);
assert_eq!(
eval_one(&parse("type"), &Value::Array(Arc::new(vec![]))),
Value::String("array".into())
);
assert_eq!(
eval_one(&parse("type"), &Value::Object(Arc::new(vec![]))),
Value::String("object".into())
);
}
#[test]
fn length_all_types() {
assert_eq!(eval_one(&parse("length"), &Value::Null), Value::Int(0));
assert_eq!(
eval_one(&parse("length"), &Value::String("hello".into())),
Value::Int(5)
);
assert_eq!(
eval_one(
&parse("length"),
&Value::Array(Arc::new(vec![Value::Int(1)]))
),
Value::Int(1)
);
assert_eq!(
eval_one(&parse("length"), &Value::Object(Arc::new(vec![]))),
Value::Int(0)
);
assert_eq!(eval_one(&parse("length"), &Value::Int(-5)), Value::Int(5));
}
#[test]
fn keys_empty_object() {
let input = Value::Object(Arc::new(vec![]));
assert_eq!(
eval_one(&parse("keys"), &input),
Value::Array(Arc::new(vec![]))
);
}
#[test]
fn values_empty_object() {
let input = Value::Object(Arc::new(vec![]));
assert_eq!(
eval_one(&parse("[.[]]"), &input),
Value::Array(Arc::new(vec![]))
);
}
#[test]
fn keys_array() {
let input = Value::Array(Arc::new(vec![
Value::String("a".into()),
Value::String("b".into()),
]));
assert_eq!(
eval_one(&parse("keys"), &input),
Value::Array(Arc::new(vec![Value::Int(0), Value::Int(1)]))
);
}
#[test]
fn array_index_negative() {
let input = Value::Array(Arc::new(vec![
Value::Int(10),
Value::Int(20),
Value::Int(30),
]));
assert_eq!(eval_one(&parse(".[-1]"), &input), Value::Int(30));
assert_eq!(eval_one(&parse(".[-2]"), &input), Value::Int(20));
}
#[test]
fn array_index_out_of_bounds() {
let input = Value::Array(Arc::new(vec![Value::Int(1)]));
assert_eq!(eval_one(&parse(".[99]"), &input), Value::Null);
}
#[test]
fn field_on_null() {
assert_eq!(eval_one(&parse(".x"), &Value::Null), Value::Null);
}
#[test]
fn field_on_wrong_type() {
let mut results = Vec::new();
eval_filter(&parse(".x"), &Value::Int(5), &mut |v| results.push(v));
assert!(results.is_empty(), "expected no output, got {:?}", results);
}
#[test]
fn explode_implode_roundtrip() {
let input = Value::String("Hello".into());
let result = eval_one(&parse("explode | implode"), &input);
assert_eq!(result, input);
}
#[test]
fn until_terminates() {
assert_eq!(
eval_one(&parse("0 | until(. >= 5; . + 1)"), &Value::Null),
Value::Int(5)
);
}
#[test]
fn alternative_null() {
assert_eq!(eval_one(&parse("null // 42"), &Value::Null), Value::Int(42));
}
#[test]
fn alternative_false() {
assert_eq!(
eval_one(&parse("false // 42"), &Value::Null),
Value::Int(42)
);
}
#[test]
fn alternative_non_null() {
assert_eq!(eval_one(&parse("1 // 42"), &Value::Null), Value::Int(1));
}
#[test]
fn eval_filter_clears_stale_last_error() {
LAST_ERROR.with(|e| *e.borrow_mut() = Some(Value::String("stale".into())));
eval_filter(&parse("."), &Value::Int(1), &mut |_| {});
let has_error = LAST_ERROR.with(|e| e.borrow().is_some());
assert!(!has_error, "LAST_ERROR should be cleared after eval_filter");
}
#[test]
fn eval_filter_with_env_clears_stale_error() {
LAST_ERROR.with(|e| *e.borrow_mut() = Some(Value::String("stale".into())));
eval_filter_with_env(&parse("."), &Value::Int(1), &Env::empty(), &mut |_| {});
let has_error = LAST_ERROR.with(|e| e.borrow().is_some());
assert!(
!has_error,
"LAST_ERROR should be cleared after eval_filter_with_env"
);
}
}