use std::cell::RefCell;
use crate::adapter::{AstAdapter, NodeId};
use crate::ast::{
Anchor, Arg, Axis, Branch, Group, Matcher, Operand, PathElem, PredExpr, Predicate, Projection,
PushBody, Query, Reach, RefProp, RegRef, Stage, Step, TraitClause,
};
use crate::error::Result;
use crate::value::Value;
use crate::{lexer, parser};
fn anchor_props(anchor: &Anchor) -> Vec<(String, Value)> {
match anchor {
Anchor::Current => Vec::new(),
Anchor::Root => vec![("anchored".to_string(), Value::Bool(true))],
Anchor::Mark(m) => vec![("mark".to_string(), Value::Str(m.clone()))],
Anchor::MarkIndex(n) => {
vec![("mark-index".to_string(), Value::Int(*n as i64))]
}
Anchor::MarkTop => vec![("mark-top".to_string(), Value::Bool(true))],
Anchor::MarksAll => vec![("marks-all".to_string(), Value::Bool(true))],
Anchor::MarksNamed(m) => {
vec![("marks-name".to_string(), Value::Str(m.clone()))]
}
Anchor::Id(op) => vec![(
"node-id".to_string(),
match op.as_ref() {
crate::ast::Operand::Lit(v @ Value::Int(_)) => v.clone(),
other => Value::Str(crate::unparse::operand_text(other)),
},
)],
}
}
struct RNode {
name: Option<String>,
props: Vec<(String, Value)>,
parent: Option<usize>,
children: Vec<usize>,
}
pub struct QueryArbor {
nodes: Vec<RNode>,
source: String,
}
impl QueryArbor {
pub fn parse(text: &str) -> Result<Self> {
let tokens = lexer::lex(text.trim())?;
let query = parser::parse(&tokens)?;
let mut arbor = QueryArbor {
nodes: Vec::new(),
source: text.trim().to_string(),
};
arbor.intern(None, Vec::new(), None);
arbor.walk_query(&query, 0, None);
Ok(arbor)
}
fn intern(
&mut self,
name: Option<&str>,
props: Vec<(String, Value)>,
parent: Option<usize>,
) -> usize {
let id = self.nodes.len();
self.nodes.push(RNode {
name: name.map(str::to_string),
props,
parent,
children: Vec::new(),
});
if let Some(p) = parent {
self.nodes[p].children.push(id);
}
id
}
fn walk_query(&mut self, q: &Query, parent: usize, role: Option<&str>) -> usize {
let mut props = Vec::new();
if let Some(role) = role {
props.push(("role".to_string(), Value::Str(role.to_string())));
}
if q.outer {
props.push(("outer".to_string(), Value::Bool(true)));
}
let id = self.intern(Some("query"), props, Some(parent));
for corr in &q.correlations {
self.walk_query(corr, id, Some("correlation"));
}
for branch in &q.branches {
self.walk_branch(branch, id);
}
if !q.pipeline.is_empty() {
let pipe = self.intern(Some("pipeline"), Vec::new(), Some(id));
for stage in &q.pipeline {
self.walk_stage(stage, pipe);
}
}
id
}
fn walk_branch(&mut self, b: &Branch, parent: usize) {
let id = self.intern(Some("branch"), anchor_props(&b.anchor), Some(parent));
for elem in &b.steps {
self.walk_elem(elem, id);
}
if let Some(p) = &b.projection {
self.walk_projection(p, id);
}
}
fn walk_elem(&mut self, e: &PathElem, parent: usize) {
match e {
PathElem::Mark(name) => {
let mut props = Vec::new();
if let Some(n) = name {
props.push(("name".to_string(), Value::Str(n.clone())));
}
self.intern(Some("mark"), props, Some(parent));
}
PathElem::Step(s) => self.walk_step(s, parent),
PathElem::Group(g) => self.walk_group(g, parent),
PathElem::Push { name, body } => {
let mut props = Vec::new();
if let Some(n) = name {
props.push(("name".to_string(), Value::Str(n.clone())));
}
match body {
PushBody::Query(q) => {
let id = self.intern(Some("subcontext"), props, Some(parent));
self.walk_query(q, id, None);
}
PushBody::Expr(e) => {
let id = self.intern(Some("expr-push"), props, Some(parent));
self.walk_operand(e, id);
}
}
}
}
}
fn walk_group(&mut self, g: &Group, parent: usize) {
let mut props = vec![("min".to_string(), Value::Int(g.quant.min as i64))];
if let Some(max) = g.quant.max {
props.push(("max".to_string(), Value::Int(max as i64)));
}
if let Some(mark) = reach_mark(g.reach) {
props.push(("reach".to_string(), Value::Str(mark.to_string())));
}
let id = self.intern(Some("group"), props, Some(parent));
for alt in &g.alts {
let alt_id = self.intern(Some("alt"), Vec::new(), Some(id));
for elem in alt {
self.walk_elem(elem, alt_id);
}
}
for p in &g.predicates {
self.walk_predicate(p, id);
}
}
fn walk_step(&mut self, s: &Step, parent: usize) {
let mut props = vec![
("axis".to_string(), Value::Str(axis_spelling(&s.axis))),
("matcher".to_string(), Value::Str(matcher_text(&s.matcher))),
(
"matcher-kind".to_string(),
Value::Str(matcher_kind(&s.matcher).to_string()),
),
("leaf".to_string(), Value::Bool(s.leaf)),
];
match &s.axis {
Axis::Resolve { property, label } => {
if let Some(p) = property {
props.push(("property".to_string(), Value::Str(p.clone())));
}
if let Some(h) = label {
props.push(("label".to_string(), Value::Str(h.clone())));
}
}
Axis::ReverseResolve {
label,
scope,
property,
} => {
match property {
RefProp::Named(p) => {
props.push(("property".to_string(), Value::Str(p.clone())))
}
RefProp::Any => props.push(("property".to_string(), Value::Str("*".into()))),
RefProp::Default => {}
}
if let Some(h) = label {
props.push(("label".to_string(), Value::Str(h.clone())));
}
if let Some(sp) = scope {
props.push((
"scope".to_string(),
Value::Str(sp.0.iter().map(crate::unparse::elem).collect()),
));
}
}
Axis::ValueMatch {
property,
scope,
target,
} => {
props.push(("property".to_string(), Value::Str(property.clone())));
props.push(("target".to_string(), Value::Str(target.clone())));
if let Some(sp) = scope {
props.push((
"scope".to_string(),
Value::Str(sp.0.iter().map(crate::unparse::elem).collect()),
));
}
}
_ => {}
}
let id = self.intern(Some("step"), props, Some(parent));
for t in &s.traits {
self.walk_trait(t, id);
}
for p in &s.predicates {
self.walk_predicate(p, id);
}
}
fn walk_trait(&mut self, t: &TraitClause, parent: usize) {
let alts = Value::list(t.alts.iter().map(|a| Value::Str(a.clone())).collect());
self.intern(
Some("trait"),
vec![("alts".to_string(), alts)],
Some(parent),
);
}
fn walk_predicate(&mut self, p: &Predicate, parent: usize) {
match p {
Predicate::Index(n) => {
self.intern(
Some("predicate"),
vec![
("kind".to_string(), Value::Str("index".to_string())),
("value".to_string(), Value::Int(*n)),
],
Some(parent),
);
}
Predicate::Range(from, to) => {
let mut props = vec![("kind".to_string(), Value::Str("range".to_string()))];
if let Some(f) = from {
props.push(("from".to_string(), Value::Int(*f)));
}
if let Some(t) = to {
props.push(("to".to_string(), Value::Int(*t)));
}
self.intern(Some("predicate"), props, Some(parent));
}
Predicate::Expr(e) => {
let id = self.intern(
Some("predicate"),
vec![("kind".to_string(), Value::Str("expr".to_string()))],
Some(parent),
);
self.walk_pred_expr(e, id);
}
}
}
fn walk_pred_expr(&mut self, e: &PredExpr, parent: usize) {
match e {
PredExpr::Or(a, b) => {
let id = self.intern(Some("or"), Vec::new(), Some(parent));
self.walk_pred_expr(a, id);
self.walk_pred_expr(b, id);
}
PredExpr::And(a, b) => {
let id = self.intern(Some("and"), Vec::new(), Some(parent));
self.walk_pred_expr(a, id);
self.walk_pred_expr(b, id);
}
PredExpr::Not(a) => {
let id = self.intern(Some("not"), Vec::new(), Some(parent));
self.walk_pred_expr(a, id);
}
PredExpr::Compare(l, op, r) => {
let id = self.intern(
Some("compare"),
vec![("op".to_string(), Value::Str(cmp_spelling(*op).to_string()))],
Some(parent),
);
self.walk_operand(l, id);
self.walk_operand(r, id);
}
PredExpr::Truthy(o) => {
self.walk_operand(o, parent);
}
}
}
fn walk_operand(&mut self, o: &Operand, parent: usize) {
match o {
Operand::Pattern(segs) => {
let props = vec![(
"form".to_string(),
Value::Str(crate::unparse::operand_text(o)),
)];
let id = self.intern(Some("pattern"), props, Some(parent));
for seg in segs {
match seg {
crate::ast::PatSeg::Star => {
self.intern(Some("star"), Vec::new(), Some(id));
}
crate::ast::PatSeg::Lit(t) => {
self.intern(
Some("literal"),
vec![
("value".to_string(), Value::Str(t.clone())),
("type".to_string(), Value::Str("text".to_string())),
],
Some(id),
);
}
}
}
}
Operand::Match {
scrutinee,
arms,
other,
} => {
let id = self.intern(Some("match"), Vec::new(), Some(parent));
self.walk_operand(scrutinee, id);
for (test, regex, result) in arms {
let props = if *regex {
vec![("regex".to_string(), Value::Bool(true))]
} else {
Vec::new()
};
let arm = self.intern(Some("when"), props, Some(id));
self.walk_operand(test, arm);
self.walk_operand(result, arm);
}
self.walk_operand(other, id);
}
Operand::Rel {
steps,
projection,
anchor,
} => {
let id = self.intern(Some("path"), anchor_props(anchor), Some(parent));
for e in steps {
self.walk_elem(e, id);
}
if let Some(p) = projection {
self.walk_projection(p, id);
}
}
Operand::Lit(v) => {
self.intern(
Some("literal"),
vec![
("value".to_string(), v.clone()),
("type".to_string(), Value::Str(type_name(v).to_string())),
],
Some(parent),
);
}
Operand::Arith { op, left, right } => {
let spelling = match op {
crate::ast::ArithOp::Add => "+",
crate::ast::ArithOp::Sub => "-",
crate::ast::ArithOp::Mul => "*",
crate::ast::ArithOp::Div => "div",
crate::ast::ArithOp::IDiv => "idiv",
crate::ast::ArithOp::Mod => "mod",
};
let id = self.intern(
Some("arith"),
vec![("op".to_string(), Value::Str(spelling.to_string()))],
Some(parent),
);
self.walk_operand(left, id);
self.walk_operand(right, id);
}
Operand::Neg(inner) => {
let id = self.intern(Some("neg"), Vec::new(), Some(parent));
self.walk_operand(inner, id);
}
Operand::Group(e) => {
let id = self.intern(Some("parens"), Vec::new(), Some(parent));
self.walk_pred_expr(e, id);
}
Operand::Recall(r) => {
self.intern(
Some("recall"),
vec![("ref".to_string(), Value::Str(reg_spelling(r)))],
Some(parent),
);
}
Operand::Topic => {
self.intern(Some("topic"), Vec::new(), Some(parent));
}
Operand::Field { base, name } => {
let id = self.intern(
Some("field"),
vec![("name".to_string(), Value::Str(name.clone()))],
Some(parent),
);
self.walk_operand(base, id);
}
Operand::ValueMeta { base, key } => {
let id = self.intern(
Some("value-meta"),
vec![("key".to_string(), Value::Str(key.clone()))],
Some(parent),
);
self.walk_operand(base, id);
}
Operand::PeerReg { at, reg } => {
let id = self.intern(
Some("peer-reg"),
vec![("ref".to_string(), Value::Str(reg_spelling(reg)))],
Some(parent),
);
self.walk_operand(at, id);
}
Operand::NamedCaptures => {
self.intern(Some("captures"), Vec::new(), Some(parent));
}
Operand::List(items) => {
let id = self.intern(Some("list"), Vec::new(), Some(parent));
for item in items {
self.walk_operand(item, id);
}
}
Operand::Now => {
self.intern(Some("now"), Vec::new(), Some(parent));
}
Operand::Edge { projection } => {
let id = self.intern(Some("edge"), Vec::new(), Some(parent));
if let Some(p) = projection {
self.walk_projection(p, id);
}
}
Operand::Edges { projection } => {
let id = self.intern(Some("edges"), Vec::new(), Some(parent));
if let Some(p) = projection {
self.walk_projection(p, id);
}
}
Operand::Capsae { projection } => {
let id = self.intern(Some("capsae"), Vec::new(), Some(parent));
if let Some(p) = projection {
self.walk_projection(p, id);
}
}
Operand::Piped { expr, stages } => {
let id = self.intern(Some("piped"), Vec::new(), Some(parent));
self.walk_operand(expr, id);
for st in stages {
self.walk_stage(st, id);
}
}
Operand::Cond { cond, then, other } => {
let id = self.intern(Some("cond"), Vec::new(), Some(parent));
self.walk_pred_expr(cond, id);
self.walk_operand(then, id);
self.walk_operand(other, id);
}
Operand::Ordinal => {
self.intern(Some("ordinal"), Vec::new(), Some(parent));
}
Operand::Capture(n) => {
self.intern(
Some("capture"),
vec![("group".to_string(), Value::Int(*n as i64))],
Some(parent),
);
}
Operand::Param(name) => {
self.intern(
Some("param"),
vec![("name".to_string(), Value::Str(name.clone()))],
Some(parent),
);
}
Operand::Outer(inner) => {
let id = self.intern(Some("outer"), Vec::new(), Some(parent));
self.walk_operand(inner, id);
}
Operand::Interp(segs) => {
let id = self.intern(Some("interp"), Vec::new(), Some(parent));
for seg in segs {
match seg {
crate::ast::InterpSeg::Text(t) => {
self.intern(
Some("literal"),
vec![
("value".to_string(), Value::Str(t.clone())),
("type".to_string(), Value::Str("text".to_string())),
],
Some(id),
);
}
crate::ast::InterpSeg::Expr(e) => self.walk_operand(e, id),
crate::ast::InterpSeg::Strict(e, msg) => {
let mut props = Vec::new();
if let Some(m) = msg {
props.push(("message".to_string(), Value::Str(m.clone())));
}
let hid = self.intern(Some("strict"), props, Some(id));
self.walk_operand(e, hid);
}
crate::ast::InterpSeg::Default(e, f) => {
let hid = self.intern(Some("default"), Vec::new(), Some(id));
self.walk_operand(e, hid);
self.walk_operand(f, hid);
}
}
}
}
Operand::Ctx {
index,
steps,
projection,
} => {
let mut props = Vec::new();
if let Some(i) = index {
props.push(("index".to_string(), Value::Int(*i as i64)));
}
let id = self.intern(Some("context"), props, Some(parent));
for e in steps {
self.walk_elem(e, id);
}
if let Some(p) = projection {
self.walk_projection(p, id);
}
}
}
}
fn walk_projection(&mut self, p: &Projection, parent: usize) {
let (kind, key) = match p {
Projection::Property(k) => ("property", k.clone()),
Projection::CoreMeta(k) => ("core", Some(k.clone())),
Projection::AdapterMeta(k) => ("adapter", Some(k.clone())),
};
let mut props = vec![("kind".to_string(), Value::Str(kind.to_string()))];
if let Some(k) = key {
props.push(("key".to_string(), Value::Str(k)));
}
self.intern(Some("projection"), props, Some(parent));
}
fn walk_stage(&mut self, stage: &Stage, parent: usize) {
match stage {
Stage::Func(call) => {
let id = self.intern(
Some("func"),
vec![("name".to_string(), Value::Str(call.name.clone()))],
Some(parent),
);
self.walk_args(&call.args, id);
}
Stage::Agg(call) => {
let id = self.intern(
Some("agg"),
vec![("name".to_string(), Value::Str(call.name.clone()))],
Some(parent),
);
self.walk_args(&call.args, id);
}
Stage::Nav(b) => {
let id = self.intern(Some("nav"), Vec::new(), Some(parent));
self.walk_branch(b, id);
}
Stage::Push(name) => {
let mut props = Vec::new();
if let Some(n) = name {
props.push(("name".to_string(), Value::Str(n.clone())));
}
self.intern(Some("push"), props, Some(parent));
}
Stage::Subcontext { name, body } => {
let mut props = Vec::new();
if let Some(n) = name {
props.push(("name".to_string(), Value::Str(n.clone())));
}
let id = self.intern(Some("subcontext"), props, Some(parent));
self.walk_query(body, id, None);
}
Stage::Expr(e) => {
let id = self.intern(Some("expr"), Vec::new(), Some(parent));
self.walk_operand(e, id);
}
Stage::FieldsPush => {
self.intern(Some("fields-push"), Vec::new(), Some(parent));
}
Stage::ExprPush { name, expr } => {
let mut props = Vec::new();
if let Some(n) = name {
props.push(("name".to_string(), Value::Str(n.clone())));
}
let id = self.intern(Some("expr-push"), props, Some(parent));
self.walk_operand(expr, id);
}
Stage::Select(p) => {
let id = self.intern(Some("select"), Vec::new(), Some(parent));
self.walk_predicate(p, id);
}
Stage::Filter(e) => {
let id = self.intern(Some("filter"), Vec::new(), Some(parent));
self.walk_pred_expr(e, id);
}
Stage::Recall(r) => {
self.intern(
Some("recall"),
vec![("ref".to_string(), Value::Str(reg_spelling(r)))],
Some(parent),
);
}
Stage::RecordWith(call) => {
let id = self.intern(Some("record-with"), Vec::new(), Some(parent));
self.walk_args(&call.args, id);
}
Stage::RecordPush {
name,
call,
enriched,
} => {
let mut props = Vec::new();
if let Some(n) = name {
props.push(("name".to_string(), Value::Str(n.clone())));
}
if *enriched {
props.push(("enriched".to_string(), Value::Bool(true)));
}
let id = self.intern(Some("record-push"), props, Some(parent));
self.walk_args(&call.args, id);
}
Stage::Spread { outer } => {
let props = if *outer {
vec![("outer".to_string(), Value::Bool(true))]
} else {
Vec::new()
};
self.intern(Some("spread"), props, Some(parent));
}
Stage::Map(inner) => {
let id = self.intern(Some("map"), Vec::new(), Some(parent));
self.walk_stage(inner, id);
}
Stage::Repeat { stage, min, max } => {
let mut props = vec![("min".to_string(), Value::Int(*min as i64))];
if let Some(n) = max {
props.push(("max".to_string(), Value::Int(*n as i64)));
}
let id = self.intern(Some("repeat"), props, Some(parent));
self.walk_stage(stage, id);
}
}
}
fn walk_args(&mut self, args: &[Arg], parent: usize) {
for arg in args {
match arg {
Arg::Lit(v) => {
self.intern(
Some("literal"),
vec![
("value".to_string(), v.clone()),
("type".to_string(), Value::Str(type_name(v).to_string())),
],
Some(parent),
);
}
Arg::Expr(e) => self.walk_operand(e, parent),
Arg::Range(from, to) => {
let mut props = vec![("kind".to_string(), Value::Str("range".to_string()))];
if let Some(f) = from {
props.push(("from".to_string(), Value::Int(*f)));
}
if let Some(t) = to {
props.push(("to".to_string(), Value::Int(*t)));
}
self.intern(Some("span"), props, Some(parent));
}
Arg::Named { name, value } => {
self.intern(
Some("option"),
vec![
("name".to_string(), Value::Str(name.clone())),
("value".to_string(), value.clone()),
],
Some(parent),
);
}
}
}
}
fn correlation_operands(&self, query: usize) -> Vec<usize> {
let mut out = Vec::new();
for &c in &self.nodes[query].children {
match self.nodes[c].name.as_deref() {
Some("query") => out.extend(self.correlation_operands(c)),
Some("branch") => out.push(c),
_ => {}
}
}
out
}
fn scope_query_of(&self, mut idx: usize) -> Option<usize> {
while let Some(p) = self.nodes[idx].parent {
if self.nodes[p].name.as_deref() == Some("query") {
return Some(p);
}
idx = p;
}
None
}
fn in_scope(&self, mut idx: usize, scope: usize) -> bool {
while let Some(p) = self.nodes[idx].parent {
if p == scope {
return true;
}
if self.nodes[p].name.as_deref() == Some("query") {
return false;
}
idx = p;
}
false
}
pub fn inventory(&self) -> Vec<(String, Vec<String>)> {
let mut map: Vec<(String, Vec<String>)> = Vec::new();
for node in &self.nodes {
let Some(kind) = &node.name else { continue };
let entry = match map.iter_mut().find(|(k, _)| k == kind) {
Some((_, keys)) => keys,
None => {
map.push((kind.clone(), Vec::new()));
&mut map.last_mut().expect("just pushed").1
}
};
for (key, _) in &node.props {
if !entry.contains(key) {
entry.push(key.clone());
}
}
}
map.sort();
for (_, keys) in &mut map {
keys.sort();
}
map
}
pub fn locator(&self, node: NodeId) -> String {
let idx = node.0 as usize;
if idx == 0 || idx >= self.nodes.len() {
return "/".to_string();
}
let mut segments = Vec::new();
let mut cur = idx;
while cur != 0 {
segments.push(self.segment(cur));
cur = match self.nodes[cur].parent {
Some(p) => p,
None => break,
};
}
segments.reverse();
format!("/{}", segments.join("/"))
}
fn segment(&self, idx: usize) -> String {
let name = self.nodes[idx].name.clone().unwrap_or_default();
let Some(parent) = self.nodes[idx].parent else {
return name;
};
let siblings: Vec<usize> = self.nodes[parent]
.children
.iter()
.copied()
.filter(|&c| self.nodes[c].name == self.nodes[idx].name)
.collect();
if siblings.len() > 1 {
let pos = siblings.iter().position(|&c| c == idx).unwrap_or(0) + 1;
format!("{name}[{pos}]")
} else {
name
}
}
}
pub(crate) enum MacroBinding {
One(Operand),
Rest(Vec<Operand>),
}
pub(crate) fn expansion_arbor(bindings: &[(String, MacroBinding)]) -> QueryArbor {
let mut arbor = QueryArbor {
nodes: Vec::new(),
source: String::new(),
};
arbor.intern(None, Vec::new(), None);
for (name, binding) in bindings {
let forms: &[Operand] = match binding {
MacroBinding::One(op) => std::slice::from_ref(op),
MacroBinding::Rest(ops) => ops,
};
let mut props = Vec::new();
if let MacroBinding::One(op) = binding {
props.push((
"form".to_string(),
Value::Str(crate::unparse::operand_text(op)),
));
}
let pid = arbor.intern(Some(name), props, Some(0));
for op in forms {
let before = arbor.nodes[pid].children.len();
arbor.walk_operand(op, pid);
if let Some(&child) = arbor.nodes[pid].children.get(before) {
arbor.nodes[child].props.push((
"form".to_string(),
Value::Str(crate::unparse::operand_text(op)),
));
}
}
}
arbor
}
pub(crate) struct ExpansionAdapter<'a> {
params: QueryArbor,
data: &'a dyn AstAdapter,
data_ids: RefCell<Vec<NodeId>>,
}
const DATA_BASE: u64 = 1 << 63;
impl<'a> ExpansionAdapter<'a> {
pub(crate) fn new(params: QueryArbor, data: &'a dyn AstAdapter) -> Self {
ExpansionAdapter {
params,
data,
data_ids: RefCell::new(Vec::new()),
}
}
fn tag(&self, n: NodeId) -> NodeId {
let mut table = self.data_ids.borrow_mut();
let i = match table.iter().position(|&x| x == n) {
Some(i) => i,
None => {
table.push(n);
table.len() - 1
}
};
NodeId(DATA_BASE + i as u64)
}
fn untag(&self, n: NodeId) -> Option<NodeId> {
self.data_ids
.borrow()
.get((n.0 - DATA_BASE) as usize)
.copied()
}
fn is_data(n: NodeId) -> bool {
n.0 >= DATA_BASE
}
}
impl AstAdapter for ExpansionAdapter<'_> {
fn root(&self) -> NodeId {
NodeId(0)
}
fn children(&self, node: NodeId) -> Vec<NodeId> {
if Self::is_data(node) {
let Some(real) = self.untag(node) else {
return Vec::new();
};
return self
.data
.children(real)
.into_iter()
.map(|n| self.tag(n))
.collect();
}
let mut out = self.params.children(node);
if node == NodeId(0) {
out.push(self.tag(self.data.root()));
}
out
}
fn name(&self, node: NodeId) -> Option<String> {
if Self::is_data(node) {
let real = self.untag(node)?;
if real == self.data.root() {
return Some("data".to_string());
}
return self.data.name(real);
}
self.params.name(node)
}
fn parent(&self, node: NodeId) -> Option<NodeId> {
if Self::is_data(node) {
let real = self.untag(node)?;
if real == self.data.root() {
return Some(NodeId(0));
}
return self.data.parent(real).map(|n| self.tag(n));
}
self.params.parent(node)
}
fn traits(&self, node: NodeId) -> Vec<String> {
if Self::is_data(node) {
let Some(real) = self.untag(node) else {
return Vec::new();
};
return self.data.traits(real);
}
self.params.traits(node)
}
fn property(&self, node: NodeId, name: &str) -> Option<Value> {
if Self::is_data(node) {
return self.data.property(self.untag(node)?, name);
}
self.params.property(node, name)
}
fn child_host(&self, node: NodeId) -> NodeId {
if Self::is_data(node)
&& let Some(real) = self.untag(node)
{
return self.tag(self.data.child_host(real));
}
node
}
fn properties(&self, node: NodeId) -> Vec<(String, Value)> {
if Self::is_data(node)
&& let Some(real) = self.untag(node)
{
return self.data.properties(real);
}
Vec::new()
}
fn walks_for_references(&self) -> bool {
self.data.walks_for_references()
}
fn can_refer_to(&self, source: NodeId, target: NodeId) -> Option<bool> {
if !Self::is_data(source) || !Self::is_data(target) {
return None;
}
self.data
.can_refer_to(self.untag(source)?, self.untag(target)?)
}
fn reference_properties(&self, source: NodeId, target: NodeId) -> Vec<String> {
if !Self::is_data(source) || !Self::is_data(target) {
return Vec::new();
}
match (self.untag(source), self.untag(target)) {
(Some(s), Some(t)) => self.data.reference_properties(s, t),
_ => Vec::new(),
}
}
fn resolves_references(&self) -> bool {
self.data.resolves_references()
}
fn tree_identities(&self) -> Vec<(String, NodeId)> {
self.data
.tree_identities()
.into_iter()
.map(|(i, n)| (i, self.tag(n)))
.collect()
}
fn reverse_label(&self, target: NodeId) -> Option<String> {
if !Self::is_data(target) {
return None;
}
self.data.reverse_label(self.untag(target)?)
}
fn resolve_needs_label(&self, node: NodeId, property: &str) -> bool {
Self::is_data(node)
&& self
.untag(node)
.is_some_and(|n| self.data.resolve_needs_label(n, property))
}
fn property_names(&self, node: NodeId) -> Vec<String> {
if !Self::is_data(node) {
return Vec::new();
}
self.untag(node)
.map(|n| self.data.property_names(n))
.unwrap_or_default()
}
fn index_lookup(&self, scope: &str, property: &str, value: &Value) -> Option<Vec<NodeId>> {
let v = self.data.index_lookup(scope, property, value)?;
Some(v.into_iter().map(|n| self.tag(n)).collect())
}
fn index_referrers(
&self,
scope: &str,
property: &str,
label: Option<&str>,
target: NodeId,
) -> Option<Vec<NodeId>> {
if !Self::is_data(target) {
return None;
}
let v = self
.data
.index_referrers(scope, property, label, self.untag(target)?)?;
Some(v.into_iter().map(|n| self.tag(n)).collect())
}
fn non_tree_nodes(&self) -> Vec<NodeId> {
self.data
.non_tree_nodes()
.into_iter()
.map(|n| self.tag(n))
.collect()
}
fn children_named(&self, node: NodeId, name: &str) -> Vec<NodeId> {
if Self::is_data(node) {
let Some(real) = self.untag(node) else {
return Vec::new();
};
return self
.data
.children_named(real, name)
.into_iter()
.map(|n| self.tag(n))
.collect();
}
self.children(node)
.into_iter()
.filter(|&c| self.name(c).as_deref() == Some(name))
.collect()
}
fn default_value(&self, node: NodeId) -> Option<Value> {
if Self::is_data(node) {
return self.data.default_value(self.untag(node)?);
}
self.params.default_value(node)
}
fn metadata(&self, node: NodeId, key: &str) -> Option<Value> {
if Self::is_data(node) {
return self.data.metadata(self.untag(node)?, key);
}
self.params.metadata(node, key)
}
fn links(&self, node: NodeId) -> Vec<(String, NodeId)> {
if Self::is_data(node) {
let Some(real) = self.untag(node) else {
return Vec::new();
};
return self
.data
.links(real)
.into_iter()
.map(|(l, n)| (l, self.tag(n)))
.collect();
}
Vec::new()
}
fn backlinks(&self, node: NodeId) -> Vec<(String, NodeId)> {
if Self::is_data(node) {
let Some(real) = self.untag(node) else {
return Vec::new();
};
return self
.data
.backlinks(real)
.into_iter()
.map(|(l, n)| (l, self.tag(n)))
.collect();
}
Vec::new()
}
fn resolve(&self, node: NodeId, property: &str, hint: Option<&str>) -> Option<NodeId> {
if Self::is_data(node) {
return self
.data
.resolve(self.untag(node)?, property, hint)
.map(|n| self.tag(n));
}
None
}
fn link_property(
&self,
source: NodeId,
label: &str,
target: NodeId,
name: &str,
) -> Option<Value> {
if Self::is_data(source) && Self::is_data(target) {
return self
.data
.link_property(self.untag(source)?, label, self.untag(target)?, name);
}
None
}
fn quantifier_bound(&self) -> usize {
self.data.quantifier_bound()
}
fn unit_scale(&self, expr: &str) -> Option<(f64, String)> {
self.data.unit_scale(expr)
}
}
fn default_key(kind: &str) -> Option<&'static str> {
match kind {
"func" | "agg" | "push" | "expr-push" | "subcontext" => Some("name"),
"literal" | "predicate" => Some("value"),
"step" => Some("matcher"),
"compare" | "arith" => Some("op"),
"recall" | "peer-reg" => Some("ref"),
"projection" => Some("key"),
_ => None,
}
}
fn cmp_spelling(op: crate::ast::CmpOp) -> &'static str {
use crate::ast::CmpOp;
match op {
CmpOp::Eq => "=",
CmpOp::Ne => "!=",
CmpOp::Lt => "<",
CmpOp::Le => "<=",
CmpOp::Gt => ">",
CmpOp::Ge => ">=",
CmpOp::Match => "==",
CmpOp::NotMatch => "!==",
CmpOp::Contains => "*=",
}
}
fn type_name(v: &Value) -> &'static str {
match v {
Value::Null => "null",
Value::Bool(_) => "bool",
Value::Int(_) => "int",
Value::Float(_) => "float",
Value::Str(_) => "text",
Value::List(_) => "list",
Value::Record(_) => "record",
Value::Instant { .. } => "instant",
Value::Duration { .. } => "duration",
Value::Period { .. } => "period",
Value::Quantity { .. } => "quantity",
}
}
fn axis_spelling(a: &Axis) -> String {
match a {
Axis::Child => "/".to_string(),
Axis::Descendant(Reach::All) => "//".to_string(),
Axis::Descendant(Reach::Proximal) => "//?".to_string(),
Axis::Descendant(Reach::Distal) => "//!".to_string(),
Axis::Universal => "*//".to_string(),
Axis::Parent => "\\".to_string(),
Axis::Ancestor(Reach::All) => "\\\\".to_string(),
Axis::Ancestor(Reach::Proximal) => "\\\\?".to_string(),
Axis::Ancestor(Reach::Distal) => "\\\\!".to_string(),
Axis::NextSibling => ">".to_string(),
Axis::PrevSibling => "<".to_string(),
Axis::FollowingSiblings(Reach::All) => ">>".to_string(),
Axis::FollowingSiblings(Reach::Proximal) => ">>?".to_string(),
Axis::FollowingSiblings(Reach::Distal) => ">>!".to_string(),
Axis::PrecedingSiblings(Reach::All) => "<<".to_string(),
Axis::PrecedingSiblings(Reach::Proximal) => "<<?".to_string(),
Axis::PrecedingSiblings(Reach::Distal) => "<<!".to_string(),
Axis::OutLink => "->".to_string(),
Axis::InLink => "<-".to_string(),
Axis::BothLink => "--".to_string(),
Axis::Resolve { .. } => "-->".to_string(),
Axis::ReverseResolve { .. } => "<--".to_string(),
Axis::ValueMatch { .. } => "=>".to_string(),
}
}
fn matcher_text(m: &Matcher) -> String {
match m {
Matcher::Name(n) => n.clone(),
Matcher::Glob(g) => g.glob().glob().to_string(),
Matcher::Regex(r) => r.as_str().to_string(),
Matcher::Any => "*".to_string(),
Matcher::Dot => ".".to_string(),
}
}
fn matcher_kind(m: &Matcher) -> &'static str {
match m {
Matcher::Name(_) => "name",
Matcher::Glob(_) => "glob",
Matcher::Regex(_) => "regex",
Matcher::Any => "any",
Matcher::Dot => "dot",
}
}
fn reach_mark(r: Reach) -> Option<&'static str> {
match r {
Reach::All => None,
Reach::Proximal => Some("?"),
Reach::Distal => Some("!"),
}
}
fn reg_spelling(r: &RegRef) -> String {
match r {
RegRef::Top => "$.".to_string(),
RegRef::Index(n) => format!("$.{n}"),
RegRef::Named(n) => format!("$.{n}"),
RegRef::Whole => "@.".to_string(),
RegRef::Record => "%.".to_string(),
RegRef::FullRecord => "%%.".to_string(),
}
}
impl AstAdapter for QueryArbor {
fn root(&self) -> NodeId {
NodeId(0)
}
fn children(&self, node: NodeId) -> Vec<NodeId> {
self.nodes
.get(node.0 as usize)
.map(|n| n.children.iter().map(|&c| NodeId(c as u64)).collect())
.unwrap_or_default()
}
fn name(&self, node: NodeId) -> Option<String> {
self.nodes.get(node.0 as usize).and_then(|n| n.name.clone())
}
fn parent(&self, node: NodeId) -> Option<NodeId> {
self.nodes
.get(node.0 as usize)
.and_then(|n| n.parent)
.map(|p| NodeId(p as u64))
}
fn property(&self, node: NodeId, name: &str) -> Option<Value> {
let n = self.nodes.get(node.0 as usize)?;
n.props
.iter()
.find(|(k, _)| k == name)
.map(|(_, v)| v.clone())
}
fn default_value(&self, node: NodeId) -> Option<Value> {
let n = self.nodes.get(node.0 as usize)?;
let key = default_key(n.name.as_deref()?)?;
self.property(node, key)
}
fn resolve(&self, node: NodeId, property: &str, _hint: Option<&str>) -> Option<NodeId> {
let idx = node.0 as usize;
let n = self.nodes.get(idx)?;
match (n.name.as_deref()?, property) {
("context", "index") => {
let k = match self.property(node, "index")? {
Value::Int(i) if i >= 1 => i as usize,
_ => return None,
};
let mut tops: Vec<usize> = Vec::new();
let mut cur = idx;
while let Some(p) = self.nodes[cur].parent {
if self.nodes[p].name.as_deref() == Some("query") {
let mut top = p;
while let Some(pp) = self.nodes[top].parent
&& self.nodes[pp].name.as_deref() == Some("query")
{
top = pp;
}
tops.push(top);
cur = top;
} else {
cur = p;
}
}
tops.reverse();
let ops: Vec<usize> = tops
.iter()
.flat_map(|&t| self.correlation_operands(t))
.collect();
ops.get(k - 1).map(|&b| NodeId(b as u64))
}
("recall", "ref") => {
let spelling = match self.property(node, "ref")? {
Value::Str(s) => s,
_ => return None,
};
let rest = spelling.strip_prefix("$.")?;
let mut chain = vec![self.scope_query_of(idx)?];
while let Some(up) = self.scope_query_of(*chain.last().unwrap()) {
if chain.contains(&up) {
break;
}
chain.push(up);
}
chain.reverse();
let inner = *chain.last().unwrap();
let sites: Vec<usize> = chain
.iter()
.flat_map(|&sc| {
(0..self.nodes.len()).filter(move |&i| {
matches!(
self.nodes[i].name.as_deref(),
Some("push") | Some("expr-push") | Some("subcontext")
) && self.in_scope(i, sc)
&& (sc == inner || i < idx)
})
})
.collect();
let found = if rest.is_empty() {
sites.into_iter().rev().find(|&i| i < idx)
} else if let Ok(k) = rest.parse::<usize>() {
k.checked_sub(1).and_then(|k| sites.get(k).copied())
} else {
sites.into_iter().rev().find(|&i| {
i < idx
&& self.nodes[i]
.props
.iter()
.any(|(k, v)| k == "name" && *v == Value::Str(rest.to_string()))
})
};
found.map(|i| NodeId(i as u64))
}
_ => None,
}
}
fn metadata(&self, node: NodeId, key: &str) -> Option<Value> {
if node.0 == 0 {
return match key {
"source" => Some(Value::Str(self.source.clone())),
"vocabulary" => Some(Value::Int(2)),
_ => None,
};
}
None
}
}
pub(crate) struct RegUsage {
pub(crate) pushes: std::collections::BTreeMap<String, usize>,
pub(crate) recalls: std::collections::BTreeMap<String, usize>,
}
fn scratch_arbor() -> QueryArbor {
let mut arbor = QueryArbor {
nodes: Vec::new(),
source: String::new(),
};
arbor.intern(None, Vec::new(), None);
arbor
}
fn node_prop<'a>(n: &'a RNode, key: &str) -> Option<&'a Value> {
n.props.iter().find(|(k, _)| k == key).map(|(_, v)| v)
}
fn collect_usage(arbor: &QueryArbor) -> RegUsage {
let mut u = RegUsage {
pushes: Default::default(),
recalls: Default::default(),
};
for n in &arbor.nodes {
match n.name.as_deref() {
Some("push") | Some("expr-push") => {
if let Some(Value::Str(s)) = node_prop(n, "name") {
*u.pushes.entry(s.clone()).or_insert(0) += 1;
}
}
Some("recall") => {
if let Some(Value::Str(s)) = node_prop(n, "ref") {
if let Some(name) = s.strip_prefix("$.") {
if !name.is_empty() {
*u.recalls.entry(name.to_string()).or_insert(0) += 1;
}
}
}
}
_ => {}
}
}
u
}
pub(crate) fn usage_of_query(q: &Query) -> RegUsage {
let mut a = scratch_arbor();
a.walk_query(q, 0, None);
collect_usage(&a)
}
pub(crate) fn usage_of_stages(stages: &[Stage]) -> RegUsage {
let mut a = scratch_arbor();
for s in stages {
a.walk_stage(s, 0);
}
collect_usage(&a)
}
pub(crate) fn usage_of_operand(op: &Operand) -> RegUsage {
let mut a = scratch_arbor();
a.walk_operand(op, 0);
collect_usage(&a)
}
pub(crate) fn operand_invites(op: &Operand) -> std::collections::BTreeSet<String> {
let mut a = scratch_arbor();
a.walk_operand(op, 0);
let mut names = std::collections::BTreeSet::new();
for n in &a.nodes {
match n.name.as_deref() {
Some("push") | Some("expr-push") | Some("subcontext") => {
if let Some(Value::Str(s)) = node_prop(n, "name") {
names.insert(s.clone());
}
}
Some("recall") => {
if let Some(Value::Str(s)) = node_prop(n, "ref") {
if let Some(x) = s.strip_prefix("$.") {
if !x.is_empty() {
names.insert(x.to_string());
}
}
}
}
Some("step") => {
if let Some(Value::Str(s)) = node_prop(n, "matcher") {
names.insert(s.clone());
}
}
Some("literal") => {
if let Some(Value::Str(s)) = node_prop(n, "value") {
names.insert(s.clone());
}
}
_ => {}
}
}
names
}
#[cfg(test)]
mod tests {
use super::*;
struct HighBitData;
impl AstAdapter for HighBitData {
fn root(&self) -> NodeId {
NodeId(1 << 63)
}
fn children(&self, node: NodeId) -> Vec<NodeId> {
if node == NodeId(1 << 63) {
vec![NodeId(u64::MAX)]
} else {
Vec::new()
}
}
fn name(&self, node: NodeId) -> Option<String> {
if node == NodeId(u64::MAX) {
Some("leaf".to_string())
} else {
None
}
}
fn property(&self, node: NodeId, name: &str) -> Option<Value> {
if node == NodeId(u64::MAX) && name == "kind" {
Some(Value::Str("commit".to_string()))
} else {
None
}
}
}
#[test]
fn data_ids_with_top_bit_round_trip() {
let params = expansion_arbor(&[]);
let data = HighBitData;
let ad = ExpansionAdapter::new(params, &data);
let root = ad.root();
let data_node = ad
.children(root)
.into_iter()
.find(|&c| ad.name(c).as_deref() == Some("data"))
.expect("data mount reachable by name");
let kids = ad.children(data_node);
assert_eq!(kids.len(), 1);
let leaf = kids[0];
assert_eq!(ad.name(leaf).as_deref(), Some("leaf"));
assert_eq!(
ad.property(leaf, "kind"),
Some(Value::Str("commit".to_string()))
);
assert_eq!(ad.children(data_node), kids);
}
fn find_all(arbor: &QueryArbor, kind: &str) -> Vec<NodeId> {
(0..arbor.nodes.len())
.filter(|&i| arbor.nodes[i].name.as_deref() == Some(kind))
.map(|i| NodeId(i as u64))
.collect()
}
#[test]
fn resolve_context_index_to_correlation_operand() {
let a = QueryArbor::parse(
"/a/* <=> /b/*[::x = _::x] <=> /c/*[::y = $$1::y] | %($$1::n; $$2::m)",
)
.unwrap();
let first_matcher = |b: NodeId| {
let step = a
.children(b)
.into_iter()
.find(|&c| a.name(c).as_deref() == Some("step"))
.unwrap();
match a.property(step, "matcher").unwrap() {
Value::Str(s) => s,
v => panic!("matcher: {v:?}"),
}
};
for ctx in find_all(&a, "context") {
let k = match a.property(ctx, "index") {
Some(Value::Int(i)) => i as usize,
_ => continue,
};
let branch = a.resolve(ctx, "index", None).expect("operand resolves");
assert_eq!(a.name(branch).as_deref(), Some("branch"));
assert_eq!(first_matcher(branch), ["b", "c"][k - 1], "$*{k}");
}
}
#[test]
fn resolve_recall_ref_to_definition_site() {
let a = QueryArbor::parse(
"/row | .adult(::age) | .fare(::fare) \
| %(a = $.adult; f = $.fare; one = $.1; top = $.)",
)
.unwrap();
let name_of = |n: NodeId| match a.property(n, "name") {
Some(Value::Str(s)) => s,
v => panic!("name: {v:?}"),
};
let recalls = find_all(&a, "recall");
assert_eq!(recalls.len(), 4);
assert_eq!(
name_of(a.resolve(recalls[0], "ref", None).unwrap()),
"adult"
);
assert_eq!(name_of(a.resolve(recalls[1], "ref", None).unwrap()), "fare");
assert_eq!(
name_of(a.resolve(recalls[2], "ref", None).unwrap()),
"adult"
);
assert_eq!(name_of(a.resolve(recalls[3], "ref", None).unwrap()), "fare");
let b = QueryArbor::parse("/row | .x(::n) | %(all = @.)").unwrap();
let r = find_all(&b, "recall")[0];
assert!(b.resolve(r, "ref", None).is_none());
}
}