use super::*;
use crate::adapter::Provenance;
use crate::lexer::lex;
use crate::parser::parse;
use std::collections::HashMap;
#[test]
fn glob_match_anchors_and_stars() {
use PatSeg::{Lit, Star};
let lit = |t: &str| Lit(t.to_string());
assert!(glob_match(&[Star, lit("web"), Star], "app-web-2"));
assert!(!glob_match(&[Star, lit("web"), Star], "app"));
assert!(glob_match(&[lit("app"), Star], "app-web"));
assert!(!glob_match(&[lit("app"), Star], "the-app"));
assert!(glob_match(&[Star, lit(".gz")], "data.gz"));
assert!(!glob_match(&[Star, lit(".gz")], "data.gz.bak"));
assert!(glob_match(&[lit("a"), Star, lit("z")], "abcz"));
assert!(glob_match(&[lit("a"), Star, lit("z")], "az"));
assert!(!glob_match(&[lit("a"), Star, lit("z")], "abc"));
assert!(glob_match(&[Star, lit("x"), Star], "x"));
assert!(glob_match(&[Star, lit("ab"), Star, lit("b")], "ab-ab-b"));
assert!(glob_match(&[] as &[PatSeg], ""));
assert!(!glob_match(&[] as &[PatSeg], "x"));
assert!(glob_match(&[Star, lit("gun")], "льюис gun"));
}
#[test]
fn pattern_test_null_semantics() {
use PatSeg::{Lit, Star};
let p = [Star, Lit("x".into()), Star];
assert!(!pattern_test(&p, &Value::Null));
assert!(pattern_test(&p, &Value::Str("axb".into())));
assert!(pattern_test(
&[Star, Lit("42".into()), Star],
&Value::Int(142)
));
}
#[test]
fn quantital_coercion_and_arith() {
let sc: &dyn Fn(&str) -> Option<(f64, String)> = &crate::quantity::scale_expr;
let q = |value: f64, base: &str, written: Option<(f64, &str)>| Value::Quantity {
value,
base: base.into(),
written: written.map(|(v, u)| (v, u.to_string())),
};
let len = q(42000.0, "m", Some((42.0, "km")));
assert_eq!(
value_cmp(&len, &Value::Str("5km".into()), sc),
Some(Ordering::Greater)
);
assert!(value_eq(&len, &Value::Str("42km".into()), sc));
assert!(value_eq(&len, &Value::Float(42000.0), sc));
assert_eq!(
value_cmp(&len, &Value::Str("30mi".into()), sc),
Some(Ordering::Less)
);
let power = q(290.0, "kg*m^2/s^3", Some((290.0, "W")));
assert_eq!(value_cmp(&power, &Value::Str("5km".into()), sc), None);
assert!(!value_eq(&power, &Value::Str("5km".into()), sc));
assert_eq!(
value_cmp(&power, &Value::Str("0.2kW".into()), sc),
Some(Ordering::Greater)
);
assert_eq!(len.to_string(), "42 km");
let sum = arith(ArithOp::Add, &len, &Value::Str("500 m".into()), sc);
assert_eq!(sum.to_string(), "42500 m");
let doubled = arith(ArithOp::Mul, &len, &Value::Int(2), sc);
assert_eq!(doubled.to_string(), "84 km");
assert!(matches!(
arith(ArithOp::Add, &len, &Value::Int(5), sc),
Value::Null
));
}
#[test]
fn durational_coercion_and_lift() {
let sc: &dyn Fn(&str) -> Option<(f64, String)> = &crate::quantity::scale_expr;
let two_h = Value::Duration {
secs: 7200,
nanos: 0,
};
assert_eq!(
value_cmp(&two_h, &Value::Str("90min".into()), sc),
Some(Ordering::Greater)
);
assert!(value_eq(&two_h, &Value::Str("PT2H".into()), sc));
assert!(value_eq(&two_h, &Value::Int(7200), sc));
assert_eq!(value_cmp(&two_h, &Value::Str("hello".into()), sc), None);
assert!(!value_eq(&two_h, &Value::Str("hello".into()), sc));
let (s, n, _) = crate::temporal::parse_iso("2026-07-12T09:00:00Z").unwrap();
let now = Value::Instant {
secs: s,
nanos: n,
offset_min: Some(0),
};
let earlier = arith(ArithOp::Sub, &now, &Value::Str("12h".into()), sc);
assert_eq!(earlier.to_string(), "2026-07-11T21:00:00Z");
let span = arith(ArithOp::Sub, &now, &Value::Str("2026-07-12".into()), sc);
assert_eq!(span.to_string(), "PT9H");
assert!(matches!(
arith(ArithOp::Sub, &now, &Value::Int(300), sc),
Value::Null
));
let sum = arith(ArithOp::Add, &two_h, &Value::Str("30min".into()), sc);
assert_eq!(sum.to_string(), "PT2H30M");
let five_kg = Value::Quantity {
value: 5.0,
base: "kg".into(),
written: Some((5.0, "kg".into())),
};
assert!(matches!(
arith(ArithOp::Mul, &two_h, &five_kg, sc),
Value::Null
));
assert!(matches!(
arith(ArithOp::Mul, &five_kg, &two_h, sc),
Value::Null
));
assert!(matches!(
arith(ArithOp::Div, &two_h, &five_kg, sc),
Value::Null
));
}
struct MockTree {
names: Vec<Option<String>>,
kids: HashMap<u64, Vec<u64>>,
loose: Vec<u64>,
links: HashMap<u64, Vec<(String, u64)>>,
edge_props: HashMap<(u64, String, u64), Vec<(String, Value)>>,
prov: HashMap<u64, Provenance>,
}
impl MockTree {
fn sample() -> Self {
let names = vec![
None, Some("a".into()), Some("x.rs".into()), Some("y.txt".into()), Some("b".into()), Some("z.rs".into()), Some("deep".into()), Some("w.rs".into()), ];
let mut kids = HashMap::new();
kids.insert(0, vec![1, 4]);
kids.insert(1, vec![2, 3]);
kids.insert(4, vec![5, 6]);
kids.insert(6, vec![7]);
let mut links = HashMap::new();
links.insert(2, vec![("ref".to_string(), 7)]);
MockTree {
names,
kids,
loose: Vec::new(),
links,
edge_props: HashMap::new(),
prov: HashMap::new(),
}
}
fn cyclic() -> Self {
let mut t = MockTree::sample();
t.links = HashMap::new();
t.links.insert(2, vec![("mgr".to_string(), 5)]);
t.links.insert(5, vec![("mgr".to_string(), 7)]);
t.links.insert(7, vec![("mgr".to_string(), 2)]);
t
}
fn lists() -> Self {
let names = vec![
None,
Some("div".into()), Some("ul".into()), Some("li".into()), Some("ul".into()), Some("li".into()), Some("ul".into()), Some("li".into()), ];
let mut kids = HashMap::new();
for (parent, child) in [(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7)] {
kids.insert(parent, vec![child]);
}
MockTree {
names,
kids,
loose: Vec::new(),
links: HashMap::new(),
edge_props: HashMap::new(),
prov: HashMap::new(),
}
}
fn converging() -> Self {
let mut t = MockTree::weighted();
t.links.get_mut(&3).map(|v| v.push(("e".to_string(), 6)));
t.links.insert(3, vec![("e".to_string(), 6)]);
t.edge_props.insert(
(3, "e".to_string(), 6),
vec![("qty".to_string(), Value::Int(5))],
);
t
}
fn diamond() -> Self {
let mut t = MockTree::sample();
t.links = HashMap::new();
t.links
.insert(1, vec![("e".to_string(), 2), ("e".to_string(), 3)]);
t.links.insert(2, vec![("e".to_string(), 6)]);
t.links.insert(3, vec![("e".to_string(), 6)]);
t.links.insert(6, vec![("e".to_string(), 2)]);
t
}
}
impl MockTree {
fn weighted() -> Self {
let mut t = MockTree::sample();
t.links = HashMap::new();
t.links
.insert(1, vec![("e".to_string(), 2), ("e".to_string(), 3)]);
t.links.insert(2, vec![("e".to_string(), 6)]);
let mut w = |s: u64, t_: u64, q: i64| {
t.edge_props.insert(
(s, "e".to_string(), t_),
vec![("qty".to_string(), Value::Int(q))],
);
};
w(1, 2, 4);
w(1, 3, 1);
w(2, 6, 12);
t
}
}
impl AstAdapter for MockTree {
fn root(&self) -> NodeId {
NodeId(0)
}
fn children(&self, node: NodeId) -> Vec<NodeId> {
self.kids
.get(&node.0)
.map(|v| v.iter().map(|&i| NodeId(i)).collect())
.unwrap_or_default()
}
fn name(&self, node: NodeId) -> Option<String> {
self.names[node.0 as usize].clone()
}
fn parent(&self, node: NodeId) -> Option<NodeId> {
self.kids
.iter()
.find(|(_, v)| v.contains(&node.0))
.map(|(&p, _)| NodeId(p))
}
fn non_tree_nodes(&self) -> Vec<NodeId> {
self.loose.iter().map(|&i| NodeId(i)).collect()
}
fn links(&self, node: NodeId) -> Vec<(String, NodeId)> {
self.links
.get(&node.0)
.map(|v| v.iter().map(|(l, t)| (l.clone(), NodeId(*t))).collect())
.unwrap_or_default()
}
fn traits(&self, node: NodeId) -> Vec<String> {
match self.names[node.0 as usize].as_deref() {
Some(n) if n.ends_with(".rs") => vec!["code".into(), "file".into()],
Some(n) if n.contains('.') => vec!["text".into(), "file".into()],
_ => Vec::new(),
}
}
fn backlinks(&self, node: NodeId) -> Vec<(String, NodeId)> {
self.links
.iter()
.flat_map(|(&src, v)| {
v.iter()
.filter(|(_, t)| *t == node.0)
.map(move |(l, _)| (l.clone(), NodeId(src)))
})
.collect()
}
fn link_property(
&self,
source: NodeId,
label: &str,
target: NodeId,
name: &str,
) -> Option<Value> {
self.edge_props
.get(&(source.0, label.to_string(), target.0))?
.iter()
.find(|(k, _)| k == name)
.map(|(_, v)| v.clone())
}
fn provenance(&self, node: NodeId) -> Provenance {
self.prov.get(&node.0).cloned().unwrap_or_default()
}
}
fn run(q: &str, t: &impl AstAdapter) -> Vec<u64> {
match eval(&parse(&lex(q).unwrap()).unwrap(), t) {
QueryResult::Nodes(ns) => ns.into_iter().map(|n| n.0).collect(),
QueryResult::Values(_) => panic!("expected nodes, got values"),
}
}
fn vals(q: &str, t: &MockTree) -> Vec<Value> {
match eval(&parse(&lex(q).unwrap()).unwrap(), t) {
QueryResult::Values(vs) => vs,
QueryResult::Nodes(_) => panic!("expected values, got nodes"),
}
}
#[test]
fn universal_search_reaches_the_whole_arbor() {
let mut t = MockTree::sample();
t.names.push(Some("x.rs".into())); t.loose.push(8);
t.links.insert(3, vec![("see".to_string(), 8)]);
assert_eq!(run("*//x.rs", &t), vec![2, 8]);
assert_eq!(run("//x.rs", &t), vec![2]);
assert_eq!(run("^//x.rs", &t), vec![2]);
assert_eq!(run("*//*", &t), vec![0, 1, 2, 3, 4, 5, 6, 7, 8]);
assert_eq!(run("//*", &t), vec![1, 2, 3, 4, 5, 6, 7]);
assert_eq!(run("*//*[:::is-non-tree]", &t), vec![8]);
assert_eq!(run("*//y.txt->see", &t), vec![8]);
assert!(parse(&lex("*//?x.rs").unwrap()).is_err());
assert!(parse(&lex("/a/*//x.rs").unwrap()).is_ok());
assert_eq!(run("/a/*//x.rs", &t), Vec::<u64>::new());
assert_eq!(
crate::unparse::unparse(&parse(&lex("*//x.rs[1]").unwrap()).unwrap()),
"*//x.rs[1]"
);
assert_eq!(
vals("/a | %(n = (*//x.rs @| count); d = (//x.rs @| count))", &t),
vec![Value::Record(vec![
("n".to_string(), Value::Int(2)),
("d".to_string(), Value::Int(1)),
])]
);
}
#[test]
fn child_and_descendant() {
let t = MockTree::sample();
assert_eq!(run("/a", &t), vec![1]);
assert_eq!(run("/a/x.rs", &t), vec![2]);
assert_eq!(run("//*.rs", &t), vec![2, 5, 7]);
}
#[test]
fn parent_and_ancestor() {
let t = MockTree::sample();
assert_eq!(run("//w.rs\\deep", &t), vec![6]);
assert_eq!(run("//w.rs\\\\b", &t), vec![4]);
}
#[test]
fn proximal_and_distal_descendant() {
let t = MockTree::sample();
assert_eq!(run("/b//*.rs", &t), vec![5, 7]);
assert_eq!(run("/b//?*.rs", &t), vec![5]); assert_eq!(run("/b//!*.rs", &t), vec![7]); }
#[test]
fn siblings() {
let t = MockTree::sample();
assert_eq!(run("/a/x.rs>y.txt", &t), vec![3]);
assert_eq!(run("/a/y.txt<x.rs", &t), vec![2]);
assert!(run("/a/y.txt>*", &t).is_empty()); }
#[test]
fn leaf_anchor() {
let t = MockTree::sample();
assert!(run("/b/deep$", &t).is_empty());
assert_eq!(run("/b/z.rs$", &t), vec![5]);
}
#[test]
fn regex_name() {
let t = MockTree::sample();
assert_eq!(run("//(/.*\\.rs/)", &t), vec![2, 5, 7]);
}
#[test]
fn correlation() {
let t = MockTree::sample();
assert_eq!(run("//* <=> //a[:::depth = _:::depth]", &t), vec![1, 4]);
assert_eq!(
run("//*.rs <=> //x.rs[:::name != _:::name]", &t),
vec![5, 7]
);
assert_eq!(
run("//* <=> //a[:::depth = _:::depth] <=> //x.rs", &t),
vec![1, 4]
);
assert!(
run(
"//* <=> //a[:::depth = _:::depth] <=> //x.rs[:::depth = $$1:::depth]",
&t
)
.is_empty()
);
}
#[test]
fn nested_predicates_read_the_thread_witness() {
let t = MockTree::sample();
assert_eq!(
run(
"//*.rs <=> //*[:::name = _:::name] \
| [(^//*[:::name = $$1:::name && :::depth = 3])]",
&t
),
vec![7]
);
assert_eq!(
run(
"//*.rs <=> //*[:::name = _:::name] \
| [(^//*[:::name = $$1:::name && :::depth = 2])]",
&t
),
vec![2, 5]
);
assert_eq!(
run(
"//*.rs <=> //*[:::name = _:::name] \
| \\\\*/*.rs[:::name != $$1:::name && :::depth = 2]",
&t
),
vec![5]
);
}
#[test]
fn crosslinks() {
let t = MockTree::sample();
assert_eq!(run("//x.rs->ref", &t), vec![7]);
assert_eq!(run("//x.rs->*", &t), vec![7]);
assert!(run("//x.rs->other", &t).is_empty());
assert_eq!(run("//w.rs<-ref", &t), vec![2]);
assert_eq!(run("//x.rs->ref\\deep", &t), vec![6]);
}
#[test]
fn quantified_child_group() {
let t = MockTree::lists();
assert_eq!(run("/div(/ul/li)+", &t), vec![3, 5, 7]);
assert_eq!(run("/div(/ul/li)+?", &t), vec![3]); assert_eq!(run("/div(/ul/li)+!", &t), vec![7]); assert_eq!(run("/div(/ul/li){2}", &t), vec![5]);
assert_eq!(run("/div(/ul/li){1;2}", &t), vec![3, 5]);
assert_eq!(run("/div(/ul/li){1;2}!", &t), vec![5]);
assert!(run("/div(/ul/li){4;}", &t).is_empty());
}
#[test]
fn quantified_crosslinks_on_a_cycle() {
let t = MockTree::cyclic();
assert_eq!(run("//x.rs(->mgr)+", &t), vec![5, 7]);
assert_eq!(run("//x.rs(->mgr)+?", &t), vec![5]);
assert_eq!(run("//x.rs(->mgr)+!", &t), vec![7]);
assert_eq!(run("//x.rs(->mgr){2}", &t), vec![7]);
assert_eq!(run("//x.rs(->mgr)*", &t), vec![2, 5, 7]);
assert_eq!(run("//x.rs(->mgr)*?", &t), vec![2]);
assert_eq!(run("//x.rs(<-mgr)+", &t), vec![7, 5]);
}
#[test]
fn group_alternation() {
let t = MockTree::sample();
assert_eq!(run("(/a|/b)", &t), vec![1, 4]);
assert_eq!(run("/a/(x.rs|y.txt)", &t), vec![2, 3]);
assert_eq!(run("/b/(z.rs|deep/w.rs)", &t), vec![5, 7]);
assert_eq!(run("(/a/(x.rs|y.txt)|/b/z.rs)", &t), vec![2, 3, 5]);
let toks = lex("/a/(x.rs|/y.txt)").unwrap();
assert!(parse(&toks).is_err());
let toks = lex("(/a|)").unwrap();
assert!(parse(&toks).is_err());
}
#[test]
fn dot_wildcard_and_sugar() {
let t = MockTree::sample();
assert_eq!(run("/b(/.)*/w.rs", &t), run("/b//w.rs", &t));
assert_eq!(run("(/.){2}", &t), vec![2, 3, 5, 6]);
assert_eq!(run("/{2}", &t), run("(/.){2}", &t));
let lists = MockTree::lists();
assert_eq!(run("/div/ul/li(/ul/li){1}", &lists), vec![5]);
assert!(run("/.", &t).is_empty());
}
#[test]
fn zero_repetitions_keep_the_anchor() {
let t = MockTree::sample();
assert_eq!(run("/b(/deep)*", &t), vec![4, 6]);
assert_eq!(run("/b(/deep){0;1}", &t), vec![4, 6]);
}
#[test]
fn quantifier_bound_refuses_out_loud() {
struct Bounded(MockTree);
impl AstAdapter for Bounded {
fn root(&self) -> NodeId {
self.0.root()
}
fn children(&self, node: NodeId) -> Vec<NodeId> {
self.0.children(node)
}
fn name(&self, node: NodeId) -> Option<String> {
self.0.name(node)
}
fn parent(&self, node: NodeId) -> Option<NodeId> {
self.0.parent(node)
}
fn links(&self, node: NodeId) -> Vec<(String, NodeId)> {
self.0.links(node)
}
fn quantifier_bound(&self) -> usize {
2
}
}
let t = Bounded(MockTree::lists());
let refusal = |q: &str| {
let out = run(q, &t);
let msg = take_refusal();
(out, msg)
};
let (out, msg) = refusal("/div(/ul/li)+");
assert!(out.is_empty());
assert!(msg.unwrap().contains("quantifier bound reached"));
let (out, msg) = refusal("/div(/ul/li){1;3}");
assert!(out.is_empty());
assert!(msg.unwrap().contains("explicit ceiling"));
let (out, msg) = refusal("/div(/ul/li){3;}");
assert!(out.is_empty());
assert!(msg.unwrap().contains("minimum"));
let (out, msg) = refusal("/div(/ul/li){1;2}");
assert_eq!(out, vec![3, 5]);
assert!(msg.is_none());
let looped = |q: &str| match crate::run(q, &t) {
Ok(QueryResult::Values(vs)) => (vs, take_refusal()),
Ok(QueryResult::Nodes(ns)) => (
ns.into_iter().map(|n| Value::Int(n.0 as i64)).collect(),
take_refusal(),
),
Err(e) => {
let _ = take_refusal();
(Vec::new(), Some(e.to_string()))
}
};
let (out, msg) = looped("/div | .n(1){5} | $.n");
assert!(out.is_empty());
assert!(msg.unwrap().contains("loop's explicit ceiling"));
let (out, msg) = looped("/div | .n(1){1;3} | $.n");
assert!(out.is_empty());
assert!(msg.unwrap().contains("loop's explicit ceiling"));
let (out, msg) = looped("/div | .n(1){3;} | $.n");
assert!(out.is_empty());
assert!(msg.unwrap().contains("loop's minimum"));
let (out, msg) = looped("/div | .n(1){1;2} | $.n");
assert_eq!(out, vec![Value::Int(1)]);
assert!(msg.is_none());
}
#[test]
fn bound_hit_with_drained_frontier_is_complete() {
struct Exact(MockTree);
impl AstAdapter for Exact {
fn root(&self) -> NodeId {
self.0.root()
}
fn children(&self, node: NodeId) -> Vec<NodeId> {
self.0.children(node)
}
fn name(&self, node: NodeId) -> Option<String> {
self.0.name(node)
}
fn parent(&self, node: NodeId) -> Option<NodeId> {
self.0.parent(node)
}
fn links(&self, node: NodeId) -> Vec<(String, NodeId)> {
self.0.links(node)
}
fn quantifier_bound(&self) -> usize {
3
}
}
let t = Exact(MockTree::lists());
assert_eq!(run("/div(/ul/li)+", &t), vec![3, 5, 7]);
assert!(take_refusal().is_none());
}
#[test]
fn proximal_answer_within_bound_is_complete() {
struct Tight(MockTree);
impl AstAdapter for Tight {
fn root(&self) -> NodeId {
self.0.root()
}
fn children(&self, node: NodeId) -> Vec<NodeId> {
self.0.children(node)
}
fn name(&self, node: NodeId) -> Option<String> {
self.0.name(node)
}
fn parent(&self, node: NodeId) -> Option<NodeId> {
self.0.parent(node)
}
fn links(&self, node: NodeId) -> Vec<(String, NodeId)> {
self.0.links(node)
}
fn quantifier_bound(&self) -> usize {
2
}
}
let t = Tight(MockTree::cyclic());
assert_eq!(run("//x.rs(->mgr)+?", &t), vec![5]);
assert!(take_refusal().is_none());
}
#[test]
fn group_in_a_predicate_path() {
let t = MockTree::sample();
assert_eq!(run("/a/*[(->ref)+]", &t), vec![2]);
let cyc = MockTree::cyclic();
assert_eq!(run("//*[(->mgr){2}]", &cyc), vec![2, 5, 7]);
}
#[test]
fn group_predicates_filter_before_reach() {
let t = MockTree::cyclic();
assert_eq!(run("//x.rs(->mgr)+[:::name = w.rs]?", &t), vec![7]);
assert_eq!(run("//x.rs(->mgr)+?", &t), vec![5]);
assert_eq!(run("//x.rs(->mgr)+[:::name == (/rs/)]!", &t), vec![7]);
assert!(run("//x.rs(->mgr)+[:::name = \"nope\"]?", &t).is_empty());
let s = MockTree::sample();
assert_eq!(run("/b/(z.rs|deep/w.rs)[:::name = w.rs]", &s), vec![7]);
assert_eq!(run("/a(/x.rs)*[:::name = \"a\"]", &s), vec![1]);
let w = MockTree::weighted();
assert_eq!(run("//a(->e)+[$-::qty = 12]", &w), vec![6]);
let toks = lex("/a(/b)+[1]").unwrap();
assert!(parse(&toks).is_err());
}
#[test]
fn arrived_edges_aggregate() {
let t = MockTree::converging();
assert_eq!(
vals("//a->e->e | .(@-::qty)", &t),
vec![Value::list(vec![Value::Int(12), Value::Int(5)])]
);
assert_eq!(
vals("//a->e->e | .(@-)", &t),
vec![Value::list(vec![
Value::Str("e".into()),
Value::Str("e".into())
])]
);
assert_eq!(
vals("/a | .(@-)", &t),
vec![Value::list(vec![Value::Str("[child]".into())])]
);
assert_eq!(
vals("//a(->e .($-::qty))->e | .(@-) @| count", &t),
vec![Value::Int(2)]
);
assert_eq!(
vals("//a(->e .(1))->e | .(@-::qty)", &t),
vec![Value::list(vec![Value::Int(12), Value::Int(5)])]
);
assert_eq!(
vals("//a->e->e | @-::qty | ... @| sum", &t),
vec![Value::Int(17)]
);
}
#[test]
fn context_accessor_and_inline_pipes() {
let t = MockTree::sample();
assert_eq!(
vals("//*.rs:::name | .all((@* @| join(\", \")))", &t),
vec![
Value::Str("x.rs, z.rs, w.rs".into()),
Value::Str("x.rs, z.rs, w.rs".into()),
Value::Str("x.rs, z.rs, w.rs".into()),
]
);
assert_eq!(
vals("//*.rs | [(@*:::name @| count) = 3] @| count", &t),
vec![Value::Int(3)]
);
assert_eq!(
vals("/a/x.rs | .n(((@*:::name | count)))", &t),
vec![Value::Int(1)]
);
assert_eq!(
vals(
"//*.rs | .top((@*:::name @| sort @| [1..2] @| join(\"-\")))",
&t
)[0],
Value::Str("w.rs-x.rs".into())
);
assert_eq!(run("/*[@* = null]", &t).len(), 2);
let toks = lex("/a | .x((@* | .bad))").unwrap();
assert!(parse(&toks).is_err());
}
#[test]
fn conditionals() {
let t = MockTree::sample();
assert_eq!(
vals(
"/a/* | %(:::name; kind = (:::name == (/rs/) ? \"code\" : \"text\"))",
&t
),
vec![
Value::Record(vec![
("name".into(), Value::Str("x.rs".into())),
("kind".into(), Value::Str("code".into())),
]),
Value::Record(vec![
("name".into(), Value::Str("y.txt".into())),
("kind".into(), Value::Str("text".into())),
]),
]
);
assert_eq!(
vals(
"//w.rs | ((:::depth = 1 ? \"top\" : :::depth = 2 ? \"mid\" : \"deep\"))",
&t
),
vec![Value::Str("deep".into())]
);
assert_eq!(
vals("/b | ((/deep ? \"has-deep\" : \"flat\"))", &t),
vec![Value::Str("has-deep".into())]
);
assert_eq!(
vals("/a | (((1 = 1 ? \"yes\" : \"no\") | upper))", &t),
vec![Value::Str("YES".into())]
);
assert_eq!(
vals("/a | (($_ ? \"topic\" : \"none\"))", &t),
vec![Value::Str("none".into())]
);
}
#[test]
fn trait_negation() {
let t = MockTree::sample();
assert_eq!(run("//*<file && !code>", &t), vec![3]);
assert_eq!(run("/*<!*>", &t), vec![1, 4]);
assert_eq!(run("/a/*<text || !file>", &t), vec![3]);
assert_eq!(run("//*<(code || text) && !text>", &t), vec![2, 5, 7]);
}
#[test]
fn map_pipe() {
let t = MockTree::converging();
assert_eq!(
vals("//a->e->e | .(@-::qty) $| ($_ * 10) | json", &t),
vec![Value::Str("[120, 50]".into())]
);
assert_eq!(
vals("//a->e->e | .(@-::qty) $| [$_ > 6] | json", &t),
vec![Value::Str("[12]".into())]
);
assert_eq!(
vals("//a->e->e | .(@-::qty) $| [1..1] | json", &t),
vec![Value::Str("[12]".into())]
);
assert_eq!(
vals(
"//a->e->e | .(@-::qty) $| (($_ * 100 div (@* @| sum) | round)) | json",
&t
),
vec![Value::Str("[71, 29]".into())]
);
assert_eq!(vals("/a | $_ $| upper", &t), vec![Value::Null]);
assert_eq!(
vals("/a | :::name $| upper", &t),
vec![Value::Str("A".into())]
);
let toks = lex("/a | @. $| .bad").unwrap();
assert!(parse(&toks).is_err());
}
#[test]
fn simple_path_dedup_is_by_visited_set() {
let t = MockTree::diamond();
assert_eq!(run("//a(->e)+", &t), vec![2, 3, 6]);
assert_eq!(run("//a(->e)+!", &t), vec![2]);
}
#[test]
fn edge_accessor() {
let t = MockTree::weighted();
assert_eq!(run("//a->e[$-::qty > 1]", &t), vec![2]);
assert_eq!(run("//a->*[$- = \"e\"]", &t), vec![2, 3]);
assert_eq!(run("//deep<-e[$-::qty = 12]", &t), vec![2]);
assert!(run("/a[$-::qty = 4]", &t).is_empty());
assert_eq!(
vals("//a->e[$-::qty > 1]:::name", &t),
vec![Value::Str("x.rs".into())]
);
}
#[test]
fn pattern_breadcrumbs() {
let t = MockTree::weighted();
assert_eq!(
vals(
"//a(->e .($-::qty))+ | [:::name = \"deep\"] | @. | product",
&t
),
vec![Value::Int(48)]
);
assert_eq!(
vals("(/a .($-)){1} | $.", &t),
vec![Value::Str("[child]".into())]
);
assert_eq!(
vals("//a(->e .(1))+! | @. | count", &t),
vec![Value::Int(2)]
);
assert_eq!(
vals("//a(->e .q($-::qty)){1;2}! | $.q", &t),
vec![Value::Int(12)]
);
}
#[test]
fn per_path_results() {
let t = MockTree::diamond();
assert_eq!(
vals(
"//a(->e .(:::name))+ | [:::name = \"deep\"] | @. | join(\"-\")",
&t
),
vec![
Value::Str("x.rs-deep".into()),
Value::Str("y.txt-deep".into()),
]
);
assert_eq!(run("//a(->e)+ | [:::name = \"deep\"]", &t), vec![6]);
}
#[test]
fn structural_edge_labels() {
let t = MockTree::sample();
assert_eq!(run("/a[$- = \"[child]\"]", &t), vec![1]);
assert_eq!(run("//w.rs\\deep[$- = \"[parent]\"]", &t), vec![6]);
assert_eq!(run("/a/x.rs>y.txt[$- = \"[next]\"]", &t), vec![3]);
assert_eq!(run("/a/y.txt<x.rs[$- = \"[prev]\"]", &t), vec![2]);
assert!(run("/a[$- = \"[parent]\"]", &t).is_empty());
}
#[test]
fn pipelines_and_union() {
let t = MockTree::sample();
assert_eq!(vals("//*.rs @| count", &t), vec![Value::Int(3)]);
assert_eq!(
vals("//*.rs:::name @| sort @| join(\", \")", &t),
vec![Value::Str("w.rs, x.rs, z.rs".into())]
);
assert_eq!(
vals("//x.rs:::name | upper", &t),
vec![Value::Str("X.RS".into())]
);
assert_eq!(run("//x.rs || //z.rs", &t), vec![2, 5]);
assert_eq!(vals("//x.rs || //z.rs @| count", &t), vec![Value::Int(2)]);
}
#[test]
fn registers_and_subcontexts() {
let t = MockTree::sample();
assert_eq!(
vals("/*[:::name = \"a\"] | .(//*.rs @| count)", &t),
vec![Value::Int(1)]
);
assert_eq!(
vals("/*[:::name = \"b\"] | .(//*.rs @| count)", &t),
vec![Value::Int(2)]
);
assert_eq!(
vals("//x.rs:::name | .saved | upper | $.saved", &t),
vec![Value::Str("x.rs".into())]
);
}
#[test]
fn predicates() {
let t = MockTree::sample();
assert_eq!(run("/*[:::n-children = 2]", &t), vec![1, 4]);
assert_eq!(run("/*[/z.rs]", &t), vec![4]);
assert_eq!(run("/a/*[1]", &t), vec![2]);
assert_eq!(run("/*[!:::is-leaf]", &t), vec![1, 4]);
assert_eq!(
run("//*[:::is-leaf && :::name == (/rs/)]", &t),
vec![2, 5, 7]
);
assert_eq!(run("//*[:::depth > 2]", &t), vec![7]);
}
#[test]
fn index_predicate_positions_among_hop_results() {
let names = vec![
None, Some("h".into()), Some("p".into()), Some("p".into()), Some("sect".into()), Some("title".into()), Some("p".into()), Some("p".into()), ];
let mut kids = HashMap::new();
kids.insert(0, vec![1, 2, 3, 4]);
kids.insert(4, vec![5, 6, 7]);
let t = MockTree {
names,
kids,
loose: Vec::new(),
links: HashMap::new(),
edge_props: HashMap::new(),
prov: HashMap::new(),
};
assert_eq!(run("/p[1]", &t), vec![2]);
assert_eq!(run("/p[2]", &t), vec![3]);
assert_eq!(run("/sect/p[2]", &t), vec![7]);
assert_eq!(run("//p[1]", &t), vec![2]);
assert_eq!(run("//p[4]", &t), vec![7]);
assert_eq!(run("/p[:::index = 2]", &t), vec![2]);
assert_eq!(run("/*[:::index = 3]", &t), vec![3]);
assert_eq!(run("/p[:::index = 1]", &t), Vec::<u64>::new());
assert_eq!(run("/*[:::index > 1][1]", &t), vec![2]);
assert_eq!(run("/*[1][:::index > 1]", &t), Vec::<u64>::new());
assert_eq!(run("/p[-1]", &t), vec![3]);
assert_eq!(run("//p[-1]", &t), vec![7]);
assert_eq!(run("//p[-4]", &t), vec![2]);
assert_eq!(run("/*[:::index > 1][-1]", &t), vec![4]);
assert_eq!(run("//p[0]", &t), Vec::<u64>::new());
assert_eq!(run("//p[5]", &t), Vec::<u64>::new());
assert_eq!(run("//p[-5]", &t), Vec::<u64>::new());
}
#[test]
fn range_predicates() {
let names = vec![
None, Some("h".into()), Some("p".into()), Some("p".into()), Some("sect".into()), Some("title".into()), Some("p".into()), Some("p".into()), ];
let mut kids = HashMap::new();
kids.insert(0, vec![1, 2, 3, 4]);
kids.insert(4, vec![5, 6, 7]);
let t = MockTree {
names,
kids,
loose: Vec::new(),
links: HashMap::new(),
edge_props: HashMap::new(),
prov: HashMap::new(),
};
assert_eq!(run("//p[2..3]", &t), vec![3, 6]);
assert_eq!(run("//p[2..]", &t), vec![3, 6, 7]);
assert_eq!(run("//p[..2]", &t), vec![2, 3]);
assert_eq!(run("//p[2..-1]", &t), vec![3, 6, 7]);
assert_eq!(run("//p[..-2]", &t), vec![2, 3, 6]);
assert_eq!(run("//p[..9]", &t), vec![2, 3, 6, 7]);
assert_eq!(run("//p[5..]", &t), Vec::<u64>::new());
assert_eq!(run("//p[3..2]", &t), Vec::<u64>::new());
assert_eq!(run("/*[..2][:::index > 1]", &t), vec![2]);
assert_eq!(run("/*[:::index > 1][..2]", &t), vec![2, 3]);
}
#[test]
fn value_expressions() {
let t = MockTree::sample();
assert_eq!(run("/*[1 + 2 * 3 = 7]", &t), vec![1, 4]);
assert_eq!(run("/*[(1 + 2) * 3 = 9]", &t), vec![1, 4]);
assert_eq!(run("/*[:::index + 1 = 2]", &t), vec![1]);
assert_eq!(run("/*[:::index * 2 = 4]", &t), vec![4]);
assert_eq!(run("/*[:::index - 2 = -1]", &t), vec![1]);
assert_eq!(run("/*[- :::index = -2]", &t), vec![4]);
assert_eq!(run("/*[7 div 2 = 3.5]", &t), vec![1, 4]);
assert_eq!(run("/*[7 idiv 2 = 3]", &t), vec![1, 4]);
assert_eq!(run("/*[7 mod 2 = 1]", &t), vec![1, 4]);
assert_eq!(run("/*[0 - 7 mod 2 = -1]", &t), vec![1, 4]);
assert_eq!(
run("/*[9007199254740993 + 0 = 9007199254740993]", &t),
vec![1, 4]
);
assert_eq!(run("/*[1 div 0]", &t), run("/*", &t));
assert_eq!(run("/*[1 idiv 0]", &t), run("/*", &t));
assert_eq!(run("/*[:::name * 2]", &t), Vec::<u64>::new());
assert_eq!(run("/*[::a-b = 1]", &t), Vec::<u64>::new());
assert_eq!(
run("/*[(:::index = 1 || :::index = 2) && :::is-leaf]", &t),
Vec::<u64>::new()
);
}
#[test]
fn value_expression_commands() {
let t = MockTree::sample();
assert_eq!(vals("/a/x.rs | :::index * 2", &t), vec![Value::Int(2)]);
assert_eq!(
vals("/* | .double(:::index * 2) @| sum", &t),
vec![Value::Int(6)]
);
assert_eq!(
vals("/a/x.rs | (:::index + 1) * 3", &t),
vec![Value::Int(6)]
);
}
#[test]
fn fused_group_count_matches_the_two_commands() {
let t = MockTree::sample();
let fused = vals("//* @| group(d = :::depth) | count", &t);
let unfused = vals("//* @| group(d = :::depth) | %(n = ($_ @| count)) | :n", &t);
assert_eq!(fused, unfused);
assert!(fused.len() > 1);
assert_eq!(
fused
.iter()
.map(|v| v.to_string().parse::<i64>().unwrap())
.sum::<i64>(),
vals("//* @| count", &t)[0]
.to_string()
.parse::<i64>()
.unwrap()
);
let recs = vals("//* @| group(d = :::depth) | count | %($.d; n = $_)", &t);
let refs = vals("//* @| group(d = :::depth) | %($.d; n = ($_ @| count))", &t);
assert_eq!(recs, refs);
assert_eq!(
vals("//* @| group(d = :::depth) | count | [$_ > 1] | $.d", &t),
vals(
"//* @| group(d = :::depth) | %(d = $.d; n = ($_ @| count)) | [:n > 1] | :d",
&t
)
);
}
#[test]
fn keyed_aggregates() {
let t = MockTree::sample();
assert_eq!(
vals("//*[:::is-leaf] @| sort_by(:::name) | :::name", &t),
vec![
Value::Str("w.rs".into()),
Value::Str("x.rs".into()),
Value::Str("y.txt".into()),
Value::Str("z.rs".into()),
]
);
assert_eq!(
vals(
"//*[:::is-leaf] @| sort_by(:::name) @| reverse @| [..2] | :::name",
&t
),
vec![Value::Str("z.rs".into()), Value::Str("y.txt".into())]
);
assert_eq!(
vals("//*[:::is-leaf] @| top(2, :::name) | :::name", &t),
vec![Value::Str("z.rs".into()), Value::Str("y.txt".into())]
);
assert_eq!(
vals("//*[:::is-leaf] @| bottom(1, :::name) | :::name", &t),
vec![Value::Str("w.rs".into())]
);
assert_eq!(run("//*[:::is-leaf] @| max_by(:::depth)", &t), vec![7]);
assert_eq!(run("//* @| min_by(:::depth)", &t), vec![1, 4]);
assert_eq!(run("//* @| unique_by(:::depth)", &t), vec![1, 2, 7]);
assert_eq!(
vals(
"//*[:::is-leaf] @| sort_by(:::depth, :::name) | :::name",
&t
)[0],
Value::Str("x.rs".into())
);
}
#[test]
fn positional_and_filter_commands() {
let t = MockTree::sample();
assert_eq!(run("//*.rs @| [1]", &t), vec![2]);
assert_eq!(run("//*.rs @| [-1]", &t), vec![7]);
assert_eq!(run("//*.rs @| [2..]", &t), vec![5, 7]);
assert_eq!(
vals("//*.rs @| [..2] | :::name", &t),
vec![Value::Str("x.rs".into()), Value::Str("z.rs".into())]
);
assert_eq!(run("//* | [:::is-leaf]", &t), vec![2, 3, 5, 7]);
assert_eq!(
run("//*.rs @| sort_by(:::name) | [:::depth > 2]", &t),
vec![7]
);
assert!(parse(&lex("//* | [1]").unwrap()).is_ok());
assert!(parse(&lex("//* @| [:::is-leaf]").unwrap()).is_err());
assert!(parse(&lex("//a | ((::x @| [:::is-leaf] @| count))").unwrap()).is_err());
assert!(parse(&lex("//a | ((::x @| bogus))").unwrap()).is_err());
}
#[test]
fn context_snapshot_in_cond_and_agg_keys() {
let t = MockTree::sample();
assert_eq!(
vals("//*.rs | ((@* @| count) > 2 ? \"many\" : \"few\")", &t),
vec![
Value::Str("many".into()),
Value::Str("many".into()),
Value::Str("many".into()),
]
);
assert_eq!(run("//*.rs @| min_by((@* @| count) - $ord)", &t), vec![7]);
}
#[test]
fn core_metadata_projection() {
let t = MockTree::sample();
assert_eq!(
vals("/a/*:::name", &t),
vec![Value::Str("x.rs".into()), Value::Str("y.txt".into())]
);
assert_eq!(vals("/a:::n-children", &t), vec![Value::Int(2)]);
assert_eq!(vals("/b/deep:::depth", &t), vec![Value::Int(2)]);
assert_eq!(vals("/a/x.rs:::is-leaf", &t), vec![Value::Bool(true)]);
assert_eq!(vals("/b:::is-leaf", &t), vec![Value::Bool(false)]);
assert_eq!(vals("/a/y.txt:::index", &t), vec![Value::Int(2)]);
assert_eq!(vals("/a/x.rs:::parent-id", &t), vec![Value::Int(1)]);
assert_eq!(vals("/a/x.rs:::parent-index", &t), vec![Value::Int(1)]);
assert_eq!(vals("/a:::is-top-level", &t), vec![Value::Bool(true)]);
assert_eq!(vals("/a/x.rs:::is-top-level", &t), vec![Value::Bool(false)]);
assert_eq!(
vals("//w.rs:::name-path", &t),
vec![Value::Str("/b/deep/w.rs".into())]
);
assert_eq!(
vals("//w.rs:::index-path", &t),
vec![Value::Str("/2/2/1".into())]
);
assert_eq!(
vals("//w.rs:::id-path", &t),
vec![Value::Str("/4/6/7".into())]
);
}
#[test]
fn provenance_core_metadata() {
let mut t = MockTree::sample();
let (secs, nanos, off) = crate::temporal::parse_iso("2026-04-07T02:00:01Z").unwrap();
t.prov.insert(
2,
Provenance {
source: Some("api-gateway".into()),
instant: Some((secs, nanos, off)),
dpid: Some("request-42".into()),
..Default::default()
},
);
t.prov.insert(
3,
Provenance {
source: Some("api-gateway".into()),
..Default::default()
},
);
t.prov.insert(
5,
Provenance {
dpid: Some("row-7".into()),
..Default::default()
},
);
assert_eq!(
vals("/a/x.rs:::source", &t),
vec![Value::Str("api-gateway".into())]
);
assert_eq!(
vals("/a/x.rs:::instant", &t),
vec![Value::Instant {
secs,
nanos,
offset_min: off
}]
);
assert_eq!(
vals("/a/x.rs:::dpid", &t),
vec![Value::Str("request-42".into())]
);
assert_eq!(
vals("/a/x.rs:::provenance", &t),
vec![Value::Str(
"?api-gateway@2026-04-07T02:00:01Z#request-42".into()
)]
);
assert_eq!(
vals("/a/y.txt:::provenance", &t),
vec![Value::Str("?api-gateway".into())]
);
assert_eq!(
vals("/b/z.rs:::provenance", &t),
vec![Value::Str("?#row-7".into())]
);
assert_eq!(vals("/b/deep:::source", &t), vec![Value::Null]);
assert_eq!(vals("/b/deep:::instant", &t), vec![Value::Null]);
assert_eq!(vals("/b/deep:::provenance", &t), vec![Value::Null]);
assert_eq!(run("//*[:::instant > 2020-01-01]", &t), vec![2]);
assert_eq!(run("//*[:::instant > 2030-01-01]", &t), Vec::<u64>::new());
let inner = Provenance {
source: Some("leaf".into()),
..Default::default()
};
let outer = Provenance {
source: Some("mount".into()),
dpid: Some("d1".into()),
..Default::default()
};
let merged = inner.or(outer);
assert_eq!(merged.source.as_deref(), Some("leaf"));
assert_eq!(merged.dpid.as_deref(), Some("d1"));
assert!(merged.instant.is_none());
assert_eq!(Provenance::default().canonical(), None);
}
#[test]
fn ancestor_reach() {
let t = MockTree::sample();
assert_eq!(run("//w.rs\\\\*", &t), vec![6, 4, 0]);
assert_eq!(run("//w.rs\\\\?*", &t), vec![6]); assert_eq!(run("//w.rs\\\\!*", &t), vec![0]); assert_eq!(run("//w.rs\\\\", &t), run("//w.rs\\\\*", &t));
assert_eq!(run("//w.rs\\\\?", &t), run("//w.rs\\", &t));
assert_eq!(run("//w.rs\\\\!", &t), run("^", &t));
assert_eq!(run("//w.rs/..", &t), run("//w.rs\\", &t));
assert_eq!(run("//w.rs/...", &t), run("//w.rs\\\\", &t));
}
#[test]
fn marks() {
let t = MockTree::sample();
assert_eq!(
run("/a .a /*[:::name = $$.a:::name]", &t),
Vec::<u64>::new()
);
assert_eq!(run("//deep .d /w.rs[$$.d:::name = \"deep\"]", &t), vec![7]);
assert_eq!(
vals("//w.rs .w \\\\!* | $$.w:::name", &t),
vec![Value::Str("w.rs".into())]
);
assert_eq!(
run("/a/x.rs .x (->ref)[$$.x:::name = \"x.rs\"]", &t),
vec![7]
);
assert_eq!(vals("//*.rs @| count | .n | $.n", &t), vec![Value::Int(3)]);
assert_eq!(run("/a/*[$$.nope:::name = \"a\"]", &t), Vec::<u64>::new());
assert_eq!(
run("/a/x.rs .x | [^//w.rs[$$.x:::name = \"x.rs\"]]", &t),
vec![2]
);
assert_eq!(
vals("/a/x.rs .x | .n($$.x:::name) | $.n", &t),
vec![Value::Str("x.rs".into())]
);
}
#[test]
fn shell_command() {
let t = MockTree::sample();
let q = "/a/x.rs | :::name | `tr a-z A-Z`";
assert!(crate::run(q, &t).is_err());
struct Shelly(MockTree);
impl AstAdapter for Shelly {
fn root(&self) -> NodeId {
self.0.root()
}
fn children(&self, n: NodeId) -> Vec<NodeId> {
self.0.children(n)
}
fn name(&self, n: NodeId) -> Option<String> {
self.0.name(n)
}
fn parent(&self, n: NodeId) -> Option<NodeId> {
self.0.parent(n)
}
fn links(&self, n: NodeId) -> Vec<(String, NodeId)> {
self.0.links(n)
}
}
let t = Shelly(MockTree::sample());
let shell = crate::Run {
allow_shell: true,
..Default::default()
};
let run_shell = |q: &str| {
crate::Query::parse(q, &t)
.unwrap()
.run(&t, &shell)
.map(|o| o.result)
};
let got = match run_shell(q).unwrap() {
QueryResult::Values(vs) => vs,
_ => panic!("values"),
};
assert_eq!(got, vec![Value::Str("X.RS".into())]);
let got = match run_shell("/a/x.rs | :::name | `false`").unwrap() {
QueryResult::Values(vs) => vs,
_ => panic!("values"),
};
assert_eq!(got, vec![Value::Null]);
}
#[test]
fn node_mode_json() {
let t = MockTree::sample();
let out = match crate::run("/a | json", &t).unwrap() {
QueryResult::Values(vs) => vs,
_ => panic!("values"),
};
assert_eq!(out.len(), 1);
assert!(out[0].to_string().contains("x.rs"));
let out = match crate::run("^ | \"[1,2,3]\" | decode(json) | ...", &t).unwrap() {
QueryResult::Values(vs) => vs,
_ => panic!("values"),
};
assert_eq!(out, vec![Value::Int(1), Value::Int(2), Value::Int(3)]);
let out = match crate::run("/a | xml", &t).unwrap() {
QueryResult::Values(vs) => vs,
_ => panic!("values"),
};
assert!(out[0].to_string().starts_with("<a>") && out[0].to_string().contains("x.rs"));
let out = match crate::run("^ | \"<r><x>1</x></r>\" | decode(xml) | json", &t).unwrap() {
QueryResult::Values(vs) => vs,
_ => panic!("values"),
};
assert_eq!(out, vec![Value::Str("{\"x\": \"1\"}".into())]);
let out = match crate::run("^ | \"{\\\"a\\\":1}\" | decode(json) | json", &t).unwrap() {
QueryResult::Values(vs) => vs,
_ => panic!("values"),
};
assert_eq!(out, vec![Value::Str("{\"a\": 1}".into())]);
}
#[test]
fn value_match() {
let t = MockTree::sample();
assert_eq!(
vals("/a/* | (:::name ?= \"x.rs\" ? 1 : \"y.txt\" ? 2 : 0)", &t),
vec![Value::Int(1), Value::Int(2)]
);
assert_eq!(
vals(
"//*.rs @| [..1] | (:::name ?= (/^x/) ? \"ex\" : \"other\")",
&t
),
vec![Value::Str("ex".into())]
);
assert_eq!(
vals("/a/*[1] | (:::name ?= \"nope\" ? 1 : 9)", &t),
vec![Value::Int(9)]
);
}
#[test]
fn anchored_operands() {
let t = MockTree::sample();
assert_eq!(run("//*[:::name = ^/a/*:::name]", &t), vec![2, 3]);
assert_eq!(run("/a/*[^//w.rs]", &t), vec![2, 3]);
assert_eq!(
vals("/a/*[1] | (^//*.rs @| count)", &t),
vec![Value::Int(3)]
);
}
#[test]
fn outer_spread() {
let t = MockTree::sample();
assert_eq!(
vals("/a/* | .m(->ref:::name) | $.m | ...", &t),
vec![Value::Str("w.rs".into())]
);
assert_eq!(
vals("/a/* | .m(->ref:::name) | $.m | ...?", &t),
vec![Value::Str("w.rs".into()), Value::Null]
);
}
#[test]
fn outer_correlation() {
let t = MockTree::sample();
let q = "//*.rs <=> /a/*[:::name = _:::name]";
assert_eq!(run(q, &t), vec![2]);
let q = "//*.rs <=>? /a/*[:::name = _:::name]";
assert_eq!(run(q, &t), vec![2, 5, 7]);
assert_eq!(
vals("//*.rs <=>? /a/*[:::name = _:::name] | $$1:::name", &t),
vec![Value::Str("x.rs".into()), Value::Null, Value::Null]
);
let q = "//*.rs <=>? /a/*[:::name = _:::name] | [!$$1:::name]";
assert_eq!(run(q, &t), vec![5, 7]);
let q = "//*.rs <=>? /a/*[:::name = _:::name] | [$$1:::name]";
assert_eq!(run(q, &t), vec![2]);
}
#[test]
fn outer_correlation_indirect_ctx() {
let t = MockTree::sample();
let q = "//*.rs <=>? /a/*[(:::name ?= _:::name ? 1 : 0) = 1]";
assert_eq!(run(q, &t), vec![2, 5, 7]);
let q = "//*.rs <=>? /a/*[/*[_:::name = :::name]]";
assert_eq!(run(q, &t), vec![2, 5, 7]);
}
#[test]
fn now_operand() {
let t = MockTree::sample();
assert_eq!(vals("^ | (now())", &t), vec![Value::Null]);
struct Clocked(MockTree, i64, u32);
impl AstAdapter for Clocked {
fn root(&self) -> NodeId {
self.0.root()
}
fn children(&self, n: NodeId) -> Vec<NodeId> {
self.0.children(n)
}
fn name(&self, n: NodeId) -> Option<String> {
self.0.name(n)
}
fn parent(&self, n: NodeId) -> Option<NodeId> {
self.0.parent(n)
}
}
let (s, n, _) = crate::temporal::parse_iso("2026-07-12T09:00:00Z").unwrap();
let t = Clocked(MockTree::sample(), s, n);
let clocked = RunOptions {
now: Some((t.1, t.2)),
..Default::default()
};
let got = match eval_with(
&parse(&lex("^ | (now())").unwrap()).unwrap(),
&t,
Some(clocked),
) {
QueryResult::Values(vs) => vs,
_ => panic!("expected values"),
};
assert_eq!(
got,
vec![Value::Instant {
secs: s,
nanos: n,
offset_min: Some(0),
}]
);
assert_eq!(got[0].to_string(), "2026-07-12T09:00:00Z");
}
#[test]
fn temporal_arith_overflow_nulls() {
let sc: &dyn Fn(&str) -> Option<(f64, String)> = &crate::quantity::scale_expr;
let inst = |secs| Value::Instant {
secs,
nanos: 0,
offset_min: Some(0),
};
let dur = |secs| Value::Duration { secs, nanos: 0 };
assert!(matches!(
arith(ArithOp::Add, &inst(i64::MAX), &dur(1), sc),
Value::Null
));
assert!(matches!(
arith(ArithOp::Add, &dur(i64::MAX), &dur(1), sc),
Value::Null
));
assert!(matches!(
arith(ArithOp::Sub, &inst(i64::MIN), &inst(i64::MAX), sc),
Value::Null
));
assert_eq!(
arith(ArithOp::Add, &inst(0), &dur(60), sc).to_string(),
"1970-01-01T00:01:00Z"
);
}
#[test]
fn contains_and_regex_null_propagate() {
let sc: &dyn Fn(&str) -> Option<(f64, String)> = &crate::quantity::scale_expr;
let name = Value::Str("chapter one".into());
assert!(!compare(&name, CmpOp::Contains, &Value::Null, sc));
assert!(compare(
&name,
CmpOp::Contains,
&Value::Str(String::new()),
sc
));
assert!(compare(
&name,
CmpOp::Contains,
&Value::Str("one".into()),
sc
));
assert!(!compare(&name, CmpOp::Match, &Value::Null, sc));
assert!(compare(&name, CmpOp::NotMatch, &Value::Null, sc));
assert!(compare(
&name,
CmpOp::Match,
&Value::Str("^chap".into()),
sc
));
}
#[test]
fn nav_command_resumes_navigation() {
let t = MockTree::sample();
assert_eq!(run("/* | /*", &t), vec![2, 3, 5, 6]);
let vs = vals("/* | .dir(:::name) | /* | .file(:::name) | %.", &t);
assert_eq!(vs.len(), 4);
let Value::Record(fields) = &vs[0] else {
panic!("expected a record, got {:?}", vs[0]);
};
assert_eq!(
fields[0],
("dir".to_string(), Value::Str("a".into())),
"first column is the ancestor's push"
);
assert_eq!(fields[1], ("file".to_string(), Value::Str("x.rs".into())));
assert_eq!(
vals("/a | /*:::name", &t),
vec![Value::Str("x.rs".into()), Value::Str("y.txt".into())]
);
assert_eq!(run("/a/x.rs | ^/b", &t), vec![4]);
assert_eq!(run("/a .m /x.rs | $$.m/y.txt", &t), vec![3]);
assert_eq!(run("/a | .m | /x.rs | $$.m/y.txt", &t), vec![3]);
assert_eq!(run("/a/x.rs | ->ref", &t), vec![7]);
assert_eq!(run("/a | :::name | . | /*", &t), vec![2, 3]);
}
#[test]
fn nav_command_mode_discipline() {
let refuse = |q: &str| {
parse(&lex(q).unwrap())
.expect_err("should refuse")
.to_string()
};
assert!(refuse("/a:::name | /b").contains("scalar mode"));
assert!(refuse("/a | :::name | /b").contains("scalar mode"));
assert!(refuse("/a @| /b").contains("per-thread"));
assert!(refuse("/a | ... $| /b").contains("map pipe"));
}
#[test]
fn named_groups_push_into_the_register() {
let t = MockTree::sample();
let bare = vals(
"/a/* | [:::name == (/^(?<stem>\\w+)\\.(?<ext>\\w+)$/)] | %.",
&t,
);
assert!(
bare.iter()
.all(|r| matches!(r, Value::Record(f) if f.is_empty())),
"{bare:?}"
);
let stems = vals(
"/a/* | [:::name == (/^(?<stem>\\w+)\\.(?<ext>\\w+)$/)] | %+:stem",
&t,
);
assert!(
stems.iter().all(|v| matches!(v, Value::Str(_))),
"{stems:?}"
);
let q = "/a/* | [:::name == (/^(?<stem>\\w+)\\.(?<ext>\\w+)$/)] | %+ | .% | %.";
let rows = vals(q, &t);
assert!(!rows.is_empty());
for r in &rows {
let Value::Record(fields) = r else {
panic!("expected a record, got {r:?}");
};
let keys: Vec<&str> = fields.iter().map(|(k, _)| k.as_str()).collect();
assert_eq!(keys, vec!["stem", "ext"], "{r:?}");
}
let exts = vals(
"/a/* | [:::name == (/^(?<stem>\\w+)\\.(?<ext>\\w+)$/)] | %+ | .% | $.ext",
&t,
);
assert!(
exts.iter()
.all(|v| matches!(v, Value::Str(s) if s == "rs" || s == "txt")),
"{exts:?}"
);
let stems = vals("/a/* | [:::name == (/^(?<stem>\\w+)\\.(\\w+)$/)] | $1", &t);
assert_eq!(stems.len(), exts.len());
}
#[test]
fn record_fields_and_named_captures() {
let t = MockTree::sample();
let q = "/a/* | [:::name == (/^(?<stem>\\w+)\\.(?<ext>\\w+)$/)] | %+:ext";
let exts = vals(q, &t);
assert!(
!exts.is_empty()
&& exts
.iter()
.all(|v| matches!(v, Value::Str(s) if s == "rs" || s == "txt")),
"{exts:?}"
);
let recs = vals("/a/* | [:::name == (/^(?<stem>\\w+)/)] | %+", &t);
assert!(
recs.iter()
.all(|r| matches!(r, Value::Record(f) if f.len() == 1 && f[0].0 == "stem")),
"{recs:?}"
);
let ones = vals("/a/* | %(:::name; n = 1) | :n", &t);
assert!(
!ones.is_empty() && ones.iter().all(|v| matches!(v, Value::Int(1))),
"{ones:?}"
);
let ones = vals("/a/* | %(:::name; n = 1) | .r | $.r:n", &t);
assert!(
!ones.is_empty() && ones.iter().all(|v| matches!(v, Value::Int(1))),
"{ones:?}"
);
let nulls = vals("/a/* | %(:::name; n = 1) | :zzz", &t);
assert!(nulls.iter().all(|v| matches!(v, Value::Null)), "{nulls:?}");
let empty = vals("/a/* | %+", &t);
assert!(
empty
.iter()
.all(|v| matches!(v, Value::Record(f) if f.is_empty())),
"{empty:?}"
);
}
#[test]
fn list_literal_gathers_every_value() {
let t = MockTree::sample();
assert_eq!(
vals("/a | @(1; \"x\"; 2 + 1)", &t),
vec![Value::list(vec![
Value::Int(1),
Value::Str("x".into()),
Value::Int(3)
])]
);
let gathered = vals("/a | @(/*:::name)", &t);
assert!(
matches!(&gathered[0], Value::List(items) if items.len() > 1),
"{gathered:?}"
);
assert_eq!(vals("/a | @()", &t), vec![Value::list(Vec::new())]);
assert_eq!(vals("/a | *(1; 2)", &t), vals("/a | @(1; 2)", &t));
assert_eq!(
vals("/a | @(1; \"x\")", &t)[0].display_form(),
"@(1; \"x\")"
);
}
#[test]
fn json_and_jsonl_over_the_stream() {
let t = MockTree::sample();
let each = vals("/a/* | :::name | json", &t);
assert!(
each.len() > 1
&& each
.iter()
.all(|v| matches!(v, Value::Str(s) if s.starts_with('"'))),
"{each:?}"
);
let whole = vals("/a/* | :::name @| json", &t);
assert!(
matches!(&whole[..], [Value::Str(s)] if s.starts_with("[\"") && s.ends_with(']')),
"{whole:?}"
);
let lines = vals("/a/* | :::name @| jsonl", &t);
assert!(
matches!(&lines[..], [Value::Str(s)] if s.lines().count() == each.len()),
"{lines:?}"
);
let rec = vals("/a/* | %(n = :::name) | json", &t);
assert!(
rec.iter()
.all(|v| matches!(v, Value::Str(s) if s.starts_with("{\"n\": "))),
"{rec:?}"
);
}
#[test]
fn record_push_in_one_step() {
let t = MockTree::sample();
let ones = vals("/a/* | .r%(:::name; n = 1) | $.r:n", &t);
assert!(
!ones.is_empty() && ones.iter().all(|v| matches!(v, Value::Int(1))),
"{ones:?}"
);
let views = vals("/a/* | .r%(:::name) | %.", &t);
assert!(
views
.iter()
.all(|v| matches!(v, Value::Record(f) if f.len() == 1 && f[0].0 == "r")),
"{views:?}"
);
let anon = vals("/a/* | .%(:::name; n = 2) | $.:n", &t);
assert!(
!anon.is_empty() && anon.iter().all(|v| matches!(v, Value::Int(2))),
"{anon:?}"
);
let rich = vals("/a/* | .k(:::name) | .%%(n = 3) | :k", &t);
assert!(
!rich.is_empty() && rich.iter().all(|v| matches!(v, Value::Str(_))),
"{rich:?}"
);
}
#[test]
fn mark_array_symmetry() {
let t = MockTree::sample();
assert_eq!(run("/a . /x.rs | ((.1))/y.txt", &t), vec![3]);
assert_eq!(run("/a . /x.rs | $$./y.txt", &t), vec![3]);
assert_eq!(run("/a | . | /x.rs | ((.1))/y.txt", &t), vec![3]);
assert_eq!(
vals("/a . /x.rs .m | ((.2)):::name", &t),
vec![Value::Str("x.rs".into())]
);
assert_eq!(
vals("/a | . | @. | json", &t),
vec![Value::Str("[]".into())]
);
assert_eq!(run("/a . /*[((.1))/x.rs]", &t), vec![2, 3]);
}
#[test]
fn marks_fan_out() {
let t = MockTree::sample();
assert_eq!(run("/a/x.rs | . | ^/b/z.rs | . | ((@))", &t), vec![2, 5]);
assert_eq!(run("/a | . | ^/b | . | ((@))/*.rs", &t), vec![2, 5]);
assert_eq!(run("/a .s /x.rs | ^/b .s /z.rs | ((@s))", &t), vec![1, 4]);
assert_eq!(run("/a | ((@))/x.rs", &t), Vec::<u64>::new());
}
#[test]
fn numeric_mark_names_refused() {
let refuse = |q: &str| {
parse(&lex(q).unwrap())
.expect_err("should refuse")
.to_string()
};
assert!(refuse("/a .1 /b").contains("starts with a letter"));
assert!(refuse("/a | .1").contains("starts with a letter"));
}
#[test]
fn pushes_return_to_navigation_mode() {
let t = MockTree::sample();
let vs = vals("/* | .dir(:::name) | /*[:::name = x.rs] | %.", &t);
assert_eq!(vs.len(), 1);
let Value::Record(fields) = &vs[0] else {
panic!("expected a record, got {:?}", vs[0]);
};
assert_eq!(fields[0], ("dir".to_string(), Value::Str("a".into())));
assert_eq!(vals("/a:::name | . | $.", &t), vec![Value::Str("a".into())]);
}
fn traced(q: &str, t: &MockTree) -> Vec<Traced> {
eval_traced_prov(&parse(&lex(q).unwrap()).unwrap(), t)
}
fn origins(known: &[(u64, u32)]) -> Origins {
Origins {
known: known
.iter()
.map(|&(n, s)| Origin {
node: NodeId(n),
command: s,
})
.collect(),
more: 0,
}
}
fn provenanced() -> MockTree {
let mut t = MockTree::sample();
let (s2, n2, o2) = crate::temporal::parse_iso("2026-04-07T02:00:01Z").unwrap();
let (s7, n7, o7) = crate::temporal::parse_iso("2026-05-01T00:00:00Z").unwrap();
t.prov.insert(
2,
Provenance {
source: Some("api-gateway".into()),
instant: Some((s2, n2, o2)),
dpid: Some("request-42".into()),
..Default::default()
},
);
t.prov.insert(
7,
Provenance {
source: Some("crm".into()),
instant: Some((s7, n7, o7)),
dpid: Some("row-7".into()),
..Default::default()
},
);
t
}
#[test]
fn push_records_the_read_node_and_command() {
let t = MockTree::sample();
let rows = traced("/a/* | .n(:::name)", &t);
assert_eq!(rows.len(), 2);
assert_eq!(rows[0].node, NodeId(2));
assert_eq!(rows[0].prov.origins, origins(&[(2, 1)]));
assert_eq!(rows[1].prov.origins, origins(&[(3, 1)]));
let rows = traced("/a/x.rs:::name", &t);
assert_eq!(rows[0].prov.origins, origins(&[(2, 0)]));
}
#[test]
fn expression_unions_its_operands_origins() {
let t = MockTree::sample();
let rows = traced("/a/x.rs | .n(:::index) | ->ref | $.n + :::index", &t);
assert_eq!(rows.len(), 1);
assert_eq!(rows[0].node, NodeId(7));
assert_eq!(rows[0].prov.origins, origins(&[(2, 1), (7, 3)]));
}
#[test]
fn selection_aggregate_keeps_the_winners_origins() {
let t = MockTree::sample();
let rows = traced("/a/*:::index @| max", &t);
assert_eq!(rows.len(), 1);
assert_eq!(rows[0].prov.origins.known.len(), 1);
let all = traced("/a/*:::index @| count", &t);
assert!(
all[0].prov.origins.known.len() > 1,
"a computed reduction keeps the union"
);
}
#[test]
fn a_projected_count_streams_and_keeps_the_counted_nodes() {
let t = MockTree::sample();
let plain = traced("/a/* @| count", &t);
let projected = traced("/a/*:::index @| count", &t);
assert_eq!(plain[0].topic, projected[0].topic);
let Some(Value::Int(n)) = projected[0].topic else {
panic!("{projected:?}")
};
assert_eq!(projected[0].prov.origins.known.len(), n as usize);
let missing = traced("/a/*::no-such-property @| count", &t);
assert_eq!(missing[0].topic, projected[0].topic);
}
#[test]
fn a_count_behind_a_descendant_lead_step_streams_and_agrees() {
let t = MockTree::sample();
let streamed = vals("//*//* @| count", &t);
let walked = vals("//*//* @| unique @| count", &t);
assert_eq!(streamed, walked);
let streamed = vals("//a//x.rs @| count", &t);
let walked = vals("//a//x.rs @| unique @| count", &t);
assert_eq!(streamed, walked);
assert_ne!(streamed, vec![Value::Int(0)]);
}
#[test]
fn fork_keeps_the_register_provenance() {
let t = MockTree::sample();
let rows = traced("/a/x.rs | .n(:::name) | ->ref | $.n", &t);
assert_eq!(rows[0].node, NodeId(7));
assert_eq!(rows[0].topic, Some(Value::Str("x.rs".into())));
assert_eq!(rows[0].prov.origins, origins(&[(2, 1)]));
}
#[test]
fn record_keeps_a_part_per_field() {
let t = MockTree::sample();
let rows = traced(
"/a/x.rs | .n(:::index) | ->ref | %(a = $.n; b = :::name)",
&t,
);
let p = &rows[0].prov;
assert_eq!(p.origins, origins(&[(2, 1), (7, 3)]));
assert_eq!(p.part("a").origins, origins(&[(2, 1)]));
assert_eq!(p.part("b").origins, origins(&[(7, 3)]));
let rows = traced(
"/a/x.rs | .n(:::index) | ->ref | %(a = $.n; b = :::name) | .% | $.b",
&t,
);
assert_eq!(rows[0].prov.origins, origins(&[(7, 3)]));
let rows = traced(
"/a/x.rs | .n(:::index) | ->ref | %(a = $.n; b = :::name) | .% | %.",
&t,
);
assert_eq!(rows[0].prov.part("a").origins, origins(&[(2, 1)]));
assert_eq!(rows[0].prov.part("b").origins, origins(&[(7, 3)]));
}
#[test]
fn aggregate_unions_and_bounds() {
let t = MockTree::sample();
let rows = traced("/a/* | :::index @| count", &t);
assert_eq!(rows.len(), 1);
assert_eq!(rows[0].prov.origins, origins(&[(2, 1), (3, 1)]));
let mut wide = MockTree::sample();
wide.names = (0..13)
.map(|i| if i == 0 { None } else { Some(format!("n{i}")) })
.collect();
wide.kids = HashMap::new();
wide.kids.insert(0, (1..13).collect());
wide.links = HashMap::new();
let rows = traced("/* | :::index @| sum", &wide);
assert_eq!(rows[0].prov.origins.known.len(), ORIGIN_CAP);
assert_eq!(rows[0].prov.origins.more, 4);
}
#[test]
fn predicate_reads_are_not_origins() {
let t = MockTree::sample();
let rows = traced(
"/a/x.rs | .n(:::index) | ->ref | (:::name = \"w.rs\" ? $.n : 0)",
&t,
);
assert_eq!(rows[0].topic, Some(Value::Int(1)));
assert_eq!(rows[0].prov.origins, origins(&[(2, 1)]));
}
#[test]
fn group_and_ungroup_carry_origins() {
let t = MockTree::sample();
let rows = traced("/*/* | .d(\\:::name) | :::index @| group(d = $.d)", &t);
assert_eq!(rows.len(), 2);
assert_eq!(rows[0].prov.origins, origins(&[(2, 2), (3, 2)]));
assert_eq!(rows[1].prov.origins, origins(&[(5, 2), (6, 2)]));
let rows = traced(
"/*/* | .d(\\:::name) | :::index @| group(d = $.d) | $.d",
&t,
);
assert_eq!(rows[0].prov.origins, origins(&[(1, 1)]));
let rows = traced(
"/*/* | .d(\\:::name) | :::index @| group(d = $.d) @| ungroup | $.d",
&t,
);
assert_eq!(rows.len(), 4);
assert_eq!(rows[3].prov.origins, origins(&[(4, 1)]));
}
#[test]
fn a_group_sums_a_record_field() {
let t = MockTree::sample();
let by_value = traced(
"/*/* | .d(\\:::name) | :::index @| group(d = $.d) | sum",
&t,
);
let keyed = traced(
"/*/* | .d(\\:::name) | %(n = :::index) @| group(d = $.d) | sum(:n)",
&t,
);
let read = traced(
"/*/* | .d(\\:::name) | %(n = :::index) @| group(d = $.d) | :n | sum",
&t,
);
assert_eq!(by_value.len(), 2);
for i in 0..2 {
assert!(by_value[i].topic.is_some());
assert_eq!(keyed[i].topic, by_value[i].topic);
assert_eq!(read[i].topic, by_value[i].topic);
}
}
#[test]
fn piped_operand_reads_its_path() {
let t = MockTree::sample();
let rows = traced("/a/x.rs | .n(->ref @| count)", &t);
assert_eq!(rows[0].topic, Some(Value::Int(1)));
assert!(
rows[0]
.prov
.origins
.known
.iter()
.any(|o| o.node == NodeId(7))
);
}
#[test]
fn value_side_provenance_projection() {
let t = provenanced();
assert_eq!(
vals("/a/x.rs | .n(:::index) | $.n:::provenance", &t),
vec![Value::Str(
"?api-gateway@2026-04-07T02:00:01Z#request-42".into()
)]
);
assert_eq!(
vals("/a/x.rs | .n(:::index) | $.n:::source", &t),
vec![Value::Str("api-gateway".into())]
);
let q = "/a/x.rs | .n(:::index) | ->ref | $.n + :::index";
assert_eq!(
vals(&format!("{q} | $_:::provenance"), &t),
vec![Value::Str(
"?api-gateway@2026-04-07T02:00:01Z#request-42;crm@2026-05-01T00:00:00Z#row-7".into()
)]
);
assert_eq!(
vals(&format!("{q} | $_:::source"), &t),
vec![Value::Str("api-gateway".into())]
);
assert_eq!(
vals(&format!("{q} | $_:::dpid"), &t),
vec![Value::Str("request-42".into())]
);
let (s7, n7, o7) = crate::temporal::parse_iso("2026-05-01T00:00:00Z").unwrap();
assert_eq!(
vals(&format!("{q} | $_:::instant"), &t),
vec![Value::Instant {
secs: s7,
nanos: n7,
offset_min: o7
}]
);
let (s2, n2, o2) = crate::temporal::parse_iso("2026-04-07T02:00:01Z").unwrap();
let rec = |src: &str, path: &str, inst: (i64, u32, Option<i16>), dpid: &str| {
Value::Record(vec![
("source".into(), Value::Str(src.into())),
("path".into(), Value::Str(path.into())),
(
"instant".into(),
Value::Instant {
secs: inst.0,
nanos: inst.1,
offset_min: inst.2,
},
),
("instant-from".into(), Value::Null),
("dpid".into(), Value::Str(dpid.into())),
])
};
assert_eq!(
vals(&format!("{q} | $_:::@provenance"), &t),
vec![Value::list(vec![
rec("api-gateway", "/a/x.rs", (s2, n2, o2), "request-42"),
rec("crm", "/b/deep/w.rs", (s7, n7, o7), "row-7"),
])]
);
assert_eq!(
vals(&format!("{q} | $_:::%provenance"), &t),
vec![rec("api-gateway", "/a/x.rs", (s7, n7, o7), "request-42")]
);
assert_eq!(
vals(&format!("{q} | $_:::path"), &t),
vec![Value::Str("/a/x.rs".into())]
);
assert_eq!(
vals("/a/y.txt:::path", &t),
vec![Value::Str("/a/y.txt".into())]
);
assert_eq!(vals("/a/y.txt:::instant", &t), vec![Value::Null]);
let coord = |id: i64, command: i64| {
Value::Record(vec![
("id".into(), Value::Int(id)),
("command".into(), Value::Int(command)),
])
};
assert_eq!(vals(&format!("{q} | $_:::origin"), &t), vec![Value::Int(2)]);
assert_eq!(vals(&format!("{q} | $_:::%origin"), &t), vec![coord(2, 1)]);
assert_eq!(
vals(&format!("{q} | $_:::@origin"), &t),
vec![Value::list(vec![coord(2, 1), coord(7, 3)])]
);
assert_eq!(
vals(&format!("{q} | $_:::origin | . | ((!7)):::name"), &t),
vec![Value::Str("w.rs".into())]
);
assert_eq!(vals(&format!("{q} | $_:::elided"), &t), vec![Value::Int(0)]);
assert_eq!(
vals(
"/a/x.rs | .n(:::index) | ->ref | .r%(a = $.n; b = :::name) | $.r:b:::source",
&t
),
vec![Value::Str("crm".into())]
);
let vs = vals(
"/a/x.rs | .n(:::index) | %(v = $.n; from = $.n:::provenance)",
&t,
);
let Value::Record(fields) = &vs[0] else {
panic!("expected a record")
};
assert_eq!(
fields[1],
(
"from".to_string(),
Value::Str("?api-gateway@2026-04-07T02:00:01Z#request-42".into())
)
);
assert_eq!(
vals("/a/x.rs:::@provenance | count", &t),
vec![Value::Int(1)]
);
assert_eq!(vals("/a/x.rs:::origin", &t), vec![Value::Int(2)]);
assert_eq!(vals("/a/x.rs:::%origin", &t), vec![coord(2, 0)]);
assert_eq!(vals("/a/x.rs:::elided", &t), vec![Value::Int(0)]);
assert_eq!(
vals("/a/y.txt | .n(:::index) | $.n:::provenance", &t),
vec![Value::Null]
);
}
#[test]
fn value_meta_needs_a_value_base() {
assert!(parse(&lex("/a/x.rs | 1:::source").unwrap()).is_err());
assert!(parse(&lex("/a | $.n:::provenance").unwrap()).is_ok());
}
#[test]
fn peer_read_is_by_location() {
let t = MockTree::sample();
assert_eq!(
vals("//*<file> | .v(:::index) | ->ref:.v", &t),
vec![Value::Int(1), Value::Null, Value::Null, Value::Null]
);
assert_eq!(
vals("//*<file> | .v(:::index) | <-ref:.v", &t),
vec![Value::Null, Value::Null, Value::Null, Value::Int(1)]
);
assert_eq!(vals("/a/x.rs | .v(7) | ->ref:.v", &t), vec![Value::Null]);
let t2 = MockTree::sample();
assert_eq!(
vals(
"//*<file> | .v(:::index) | .w(:::name) | ->ref:. | (:::name = \"x.rs\" ? $_ : \"-\")",
&t2
),
vec![
Value::Str("w.rs".into()),
Value::Str("-".into()),
Value::Str("-".into()),
Value::Str("-".into())
]
);
assert_eq!(
vals("//*<file> | .v(:::index * 10) | .w(:::name) | ->ref:.1", &t),
vec![Value::Int(10), Value::Null, Value::Null, Value::Null]
);
}
#[test]
fn peer_read_sees_the_input_snapshot() {
let t = MockTree::sample();
let pair = "//*<file>[:::name = \"x.rs\" || :::name = \"w.rs\"]";
let vs = vals(
&format!(
"{pair} | .v((:::name = \"x.rs\" ? 5 : 9)) | .v((:::name = \"x.rs\" ? ->ref:.v : <-ref:.v)) | $.v"
),
&t,
);
assert_eq!(vs, vec![Value::Int(9), Value::Int(5)]);
assert_eq!(
vals(
&format!(
"{pair} | .v((:::name = \"x.rs\" ? 5 : 9)) | .a(->ref:.v) | .b(<-ref:.a) | $.b"
),
&t
),
vec![Value::Null, Value::Int(9)]
);
}
#[test]
fn peer_read_after_a_fork_reads_the_children() {
let t = MockTree::sample();
assert_eq!(
vals("/a | .p(1) | /* | .r(:::index) | \\:.p", &t),
vec![Value::Null, Value::Null]
);
assert_eq!(
vals("/a | .p(1) | /* | .r(:::index) | (\\/*:.r @| sum)", &t),
vec![Value::Int(3), Value::Int(3)]
);
assert_eq!(
vals("/a | .p(1) | /* | .r(:::index) | (\\/*:.p @| count)", &t),
vec![Value::Int(2), Value::Int(2)]
);
assert_eq!(
vals("/a | .p(1) | /* | .r(:::index) | (\\/*:%. | :r @| sum)", &t),
vec![Value::Int(3), Value::Int(3)]
);
}
#[test]
fn peer_read_answers_every_capsa_standing_there() {
let t = MockTree::sample();
assert_eq!(
vals("/a/* | .v(:::index) | \\ | (\\/a:.v @| count)", &t),
vec![Value::Int(2), Value::Int(2)]
);
assert_eq!(
vals("/a/* | .v(1) | \\ | (\\/a:.v @| count)", &t),
vec![Value::Int(1)]
);
}
#[test]
fn peer_read_carries_the_regula_provenance() {
let t = MockTree::sample();
let pair = "//*<file>[:::name = \"x.rs\" || :::name = \"w.rs\"]";
let rows = traced(&format!("{pair} | .v(:::index) | ->ref:.v"), &t);
assert_eq!(rows[0].node, NodeId(2));
assert_eq!(rows[0].prov.origins, origins(&[(7, 1)]));
let t2 = provenanced();
assert_eq!(
vals(&format!("{pair} | .v(:::index) | ->ref:.v:::source"), &t2)[0],
Value::Str("crm".into())
);
}
#[test]
fn peer_read_is_null_without_a_context() {
let t = MockTree::sample();
assert_eq!(run("/a/x.rs[->ref:.v = 1]", &t), Vec::<u64>::new());
assert_eq!(run("/a/x.rs[!->ref:.v]", &t), vec![2]);
}
#[test]
fn peer_read_needs_a_node_side() {
assert!(parse(&lex("/a | $.v:.r").unwrap()).is_err());
assert!(parse(&lex("/a | ->ref::x:.r").unwrap()).is_err());
assert!(parse(&lex("/a | (::x ? 1 : .5)").unwrap()).is_ok());
}
#[test]
fn node_id_anchor() {
let t = MockTree::sample();
assert_eq!(run("((!7))", &t), vec![7]);
assert_eq!(vals("((!7)):::name", &t), vec![Value::Str("w.rs".into())]);
assert_eq!(
vals("/a | ((!7))\\:::name", &t),
vec![Value::Str("deep".into())]
);
assert_eq!(run("((!99))", &t), Vec::<u64>::new());
assert_eq!(vals("/a | ((!99)):::name", &t), Vec::<Value>::new());
assert_eq!(vals("/b/deep/w.rs:::id", &t), vec![Value::Int(7)]);
assert_eq!(
vals("/a/x.rs | .v(:::index) | ->ref | $.v:::origin", &t),
vec![Value::Int(2)]
);
assert_eq!(
vals(
"/a/x.rs | .v(:::index) | ->ref | .o($.v:::origin) | ((!$.o)):::name",
&t
),
vec![Value::Str("x.rs".into())]
);
assert_eq!(
vals(
"/a/x.rs | .v(:::index) | ->ref | ((!$.v:::%origin)):::name",
&t
),
vec![Value::Str("x.rs".into())]
);
assert_eq!(
vals("/a/* | .v(:::index) | ((!$.v + 1)):::name", &t),
vec![Value::Str("x.rs".into()), Value::Str("y.txt".into())]
);
assert_eq!(
vals("/a | ((!@(2; 3; 99))):::name", &t),
vec![Value::Str("x.rs".into()), Value::Str("y.txt".into())]
);
let rows = traced(
"/a/x.rs | .v(:::index) | ->ref | ((!$.v:::origin)) | :::name",
&t,
);
assert_eq!(rows[0].node, NodeId(2));
assert_eq!(rows[0].prov.origins, origins(&[(2, 4)]));
}
#[test]
fn quantified_push_iterates() {
let t = MockTree::sample();
assert_eq!(
vals("/a | .v(1) | .v($.v * 2){3} | $.v", &t),
vec![Value::Int(8)]
);
assert_eq!(
vals("/a | .v(1) | .v($.v * 2){3} | @. | count", &t),
vec![Value::Int(4)]
);
let pair = "//*<file>[:::name = \"x.rs\" || :::name = \"w.rs\"]";
assert_eq!(
vals(
&format!(
"{pair} | .v(:::index * 10) | .v(($.v + (->ref:.v | default(0)) + (<-ref:.v | default(0)))){{2}} | $.v"
),
&t
),
vec![Value::Int(40), Value::Int(40)]
);
assert_eq!(
vals(
"/a | .v(5) | .v(($.v > 8 ? $.v : $.v + 1)){1;} | @. | count",
&t
),
vec![Value::Int(6)]
);
assert_eq!(
vals("/a | .v(5) | .v($.v){3;} | @. | count", &t),
vec![Value::Int(5)]
);
assert_eq!(
vals("/a | .v(5) | .v($.v + 1){2;3} | $.v", &t),
vec![Value::Int(8)]
);
assert!(parse(&lex("/a | upper{3}").unwrap()).is_err());
assert!(parse(&lex("/a | .v(1){3;2}").unwrap()).is_err());
}