mod builtins;
mod eval;
mod parse;
use std::collections::BTreeMap;
use std::sync::{Arc, OnceLock};
use crate::error::{CamelError, Result};
use crate::message::Exchange;
use crate::spi::{Expression, Language, Predicate};
use crate::value::Value;
use eval::{Bind, Ctl, Env, Interp, Pv};
use parse::{Ast, FuncDef};
pub(crate) use builtins::normalize;
fn prelude_defs() -> &'static [FuncDef] {
static DEFS: OnceLock<Vec<FuncDef>> = OnceLock::new();
DEFS.get_or_init(|| {
let toks = parse::lex(builtins::PRELUDE).expect("jq prelude lexes");
let mut p = parse::Parser::new(builtins::PRELUDE, toks);
match p.program().expect("jq prelude parses") {
Ast::Defs(defs, _) => defs,
_ => Vec::new(),
}
})
}
thread_local! {
static PRELUDE_ENV: Env<'static> = {
let mut env = Env::default();
for d in prelude_defs() {
env = env.push(Bind::Func(d));
}
env
};
}
pub(crate) fn prelude_lookup(name: &str, arity: usize) -> Option<(&'static FuncDef, Env<'static>)> {
let d = prelude_defs().iter().rev().find(|d| d.name == name && d.params.len() == arity)?;
Some((d, PRELUDE_ENV.with(|e| e.clone())))
}
fn is_builtin(name: &str, arity: usize) -> bool {
builtins::is_native(name, arity) || prelude_defs().iter().any(|d| d.name == name && d.params.len() == arity)
}
fn to_camel_error(c: Ctl) -> CamelError {
match c {
Ctl::Err(Value::String(s)) => CamelError::new("JqException", s),
Ctl::Err(Value::Null) => CamelError::new("JqException", "null (null) not a string"),
Ctl::Err(v) => CamelError::new("JqException", format!("{} (not a string)", eval::to_json(&v))),
Ctl::Break(_) => CamelError::new("JqException", "break"),
Ctl::Through(_, inner) => to_camel_error(*inner),
}
}
#[derive(Debug, Clone)]
pub struct Jq {
text: String,
expr: Ast,
}
impl Jq {
pub fn compile(q: &str) -> Result<Jq> {
let toks = parse::lex(q)?;
let mut p = parse::Parser::new(q, toks);
let expr = p.program()?;
let native = |n: &str, a: usize| is_builtin(n, a);
let mut scope = parse::Scope { funcs: Vec::new(), vars: Vec::new(), labels: Vec::new(), native: &native };
parse::resolve(q, &expr, &mut scope)?;
Ok(Jq { text: q.to_string(), expr })
}
pub fn as_str(&self) -> &str {
&self.text
}
fn run(&self, input: &Value, ex: Option<&Exchange>, f: &mut dyn FnMut(Value) -> std::result::Result<(), Ctl>) -> Result<()> {
let v = normalize(super::jsonpath::to_json_value(input)?);
let interp = Interp { ex };
let env = PRELUDE_ENV.with(|e| e.clone());
interp.eval(&self.expr, &env, Pv::val(v), &mut |x| f(x.v)).map_err(to_camel_error)
}
pub fn outputs(&self, input: &Value) -> Result<Vec<Value>> {
self.outputs_with(input, None)
}
pub fn outputs_with(&self, input: &Value, ex: Option<&Exchange>) -> Result<Vec<Value>> {
let mut out = Vec::new();
self.run(input, ex, &mut |v| {
out.push(v);
Ok(())
})?;
Ok(out)
}
pub fn evaluate(&self, input: &Value) -> Result<Value> {
self.evaluate_with(input, None)
}
pub fn evaluate_with(&self, input: &Value, ex: Option<&Exchange>) -> Result<Value> {
let mut outs = self.outputs_with(input, ex)?;
Ok(match outs.len() {
0 => Value::Null,
1 => outs.pop().unwrap(),
_ => Value::Array(outs),
})
}
}
pub fn query(input: &Value, q: &str) -> Result<Value> {
Jq::compile(q)?.evaluate(input)
}
struct JqExpression {
jq: Jq,
options: BTreeMap<String, String>,
}
impl Expression for JqExpression {
fn evaluate(&self, ex: &Exchange) -> Result<Value> {
let input = super::jsonpath::source_value(ex, &self.options);
let v = self.jq.evaluate_with(&input, Some(ex))?;
match self.options.get("resultType").filter(|t| !t.is_empty()) {
Some(t) => super::support::convert_to_type_ex(Some(ex), &v, t),
None => Ok(v),
}
}
}
impl Predicate for JqExpression {
fn matches(&self, ex: &Exchange) -> Result<bool> {
let input = super::jsonpath::source_value(ex, &self.options);
Ok(self.jq.outputs_with(&input, Some(ex))?.iter().any(eval::truthy))
}
}
#[derive(Default)]
pub struct JqLanguage;
impl Language for JqLanguage {
fn name(&self) -> &str {
"jq"
}
fn create_expression(&self, text: &str, options: &BTreeMap<String, String>) -> Result<Arc<dyn Expression>> {
Ok(Arc::new(JqExpression { jq: Jq::compile(text)?, options: options.clone() }))
}
fn create_predicate(&self, text: &str, options: &BTreeMap<String, String>) -> Result<Arc<dyn Predicate>> {
Ok(Arc::new(JqExpression { jq: Jq::compile(text)?, options: options.clone() }))
}
}
#[cfg(test)]
mod tests;