use crate::adapter::{AstAdapter, InstantFrom, NodeId, Provenance, ProvenanceList};
use crate::ast::visit::{self, Visitor as _};
use crate::ast::{
Anchor, Arg, ArithOp, Axis, Branch, CmpOp, Command, FnCall, Group, InterpSeg, Matcher, Operand,
PatSeg, PathElem, PredExpr, Predicate, Projection, PushBody, Query, Reach, RefProp, RegRef,
Step,
};
use crate::stdlib;
use crate::value::{SortKey, Value};
use regex::Regex;
use std::cmp::Ordering;
use std::collections::{HashMap, HashSet};
mod arith;
mod commands;
mod compare;
mod context;
mod correlate;
mod environ;
mod identity;
mod memo;
mod operand;
mod prov;
mod register;
mod run;
mod serialize;
mod tabula;
mod steps;
#[cfg(test)]
mod tests;
mod text;
use arith::*;
pub(crate) use commands::*;
use compare::*;
use context::*;
pub(crate) use correlate::*;
use environ::*;
pub use identity::*;
use memo::*;
pub(crate) use operand::*;
pub use prov::*;
use register::*;
pub use run::*;
use serialize::*;
pub use steps::*;
pub(crate) use text::*;
#[derive(Clone)]
pub(crate) struct Reg {
name: Option<String>,
value: Value,
prov: Prov,
#[allow(dead_code)]
site: Origin,
}
#[derive(Clone, PartialEq)]
pub(crate) struct Mark {
name: Option<String>,
node: NodeId,
}
fn operand_from(
adapter: &impl AstAdapter,
anchor: &Anchor,
current: NodeId,
trace: &Trace,
bound: &[Option<NodeId>],
scope: Scope<'_>,
) -> Vec<NodeId> {
if matches!(anchor, Anchor::Current) && !scope.members.is_empty() {
return scope.members.iter().map(|m| m.node).collect();
}
anchor_nodes(adapter, anchor, current, trace, bound, scope)
}
fn anchor_nodes(
adapter: &impl AstAdapter,
anchor: &Anchor,
current: NodeId,
trace: &Trace,
bound: &[Option<NodeId>],
scope: Scope<'_>,
) -> Vec<NodeId> {
let marks = scope.marks;
match anchor {
Anchor::Current => vec![current],
Anchor::Root => vec![adapter.root()],
Anchor::Id(op) => {
let vs = discard(|| eval_operand(adapter, current, op, trace, bound, scope));
let mut ids: Vec<NodeId> = Vec::new();
fn collect(v: &Value, out: &mut Vec<NodeId>) {
match v {
Value::Int(n) if *n >= 0 => out.push(NodeId(*n as u64)),
Value::Record(fields) => {
if let Some((_, id)) = fields.iter().find(|(k, _)| k == "id") {
collect(id, out);
}
}
Value::List(items) => items.iter().for_each(|i| collect(i, out)),
_ => {}
}
}
for v in &vs {
collect(v, &mut ids);
}
dedup(ids)
.into_iter()
.filter(|&n| adapter.has_node(n))
.collect()
}
Anchor::Mark(m) => marks
.iter()
.rev()
.find(|k| k.name.as_deref() == Some(m))
.map(|k| k.node)
.into_iter()
.collect(),
Anchor::MarkIndex(n) => marks.get(n - 1).map(|k| k.node).into_iter().collect(),
Anchor::MarkTop => marks.last().map(|k| k.node).into_iter().collect(),
Anchor::MarksAll => marks.iter().map(|k| k.node).collect(),
Anchor::MarksNamed(m) => marks
.iter()
.filter(|k| k.name.as_deref() == Some(m))
.map(|k| k.node)
.collect(),
}
}
#[derive(Clone)]
pub(crate) struct Capsa {
pub(crate) node: NodeId,
pub(crate) register: Vec<Reg>,
pub(crate) topic: Option<Value>,
prov: Prov,
members: Vec<Capsa>,
captures: Vec<String>,
named: Vec<(String, String)>,
marks: Vec<Mark>,
bindings: Vec<Option<NodeId>>,
arrived: Vec<EdgeCtx>,
}
impl Capsa {
fn scope<'a>(
&'a self,
ordinal: Option<usize>,
peers: Option<&'a Peers>,
outer: Option<&'a Scope<'a>>,
) -> Scope<'a> {
Scope {
prov: Some(&self.prov),
node: Some(self.node),
register: &self.register,
members: &self.members,
topic: self.topic.as_ref(),
ordinal,
captures: &self.captures,
named: &self.named,
marks: &self.marks,
bindings: &self.bindings,
edge: None,
arrived: &self.arrived,
peers,
outer,
}
}
pub(crate) fn bare(node: NodeId, topic: Option<Value>) -> Capsa {
Capsa {
node,
register: Vec::new(),
topic,
prov: Prov::default(),
members: Vec::new(),
captures: Vec::new(),
named: Vec::new(),
marks: Vec::new(),
bindings: Vec::new(),
arrived: Vec::new(),
}
}
}
#[derive(Clone, PartialEq, Eq, Hash)]
pub(crate) struct EdgeCtx {
pub(crate) source: NodeId,
pub(crate) label: String,
pub(crate) target: NodeId,
}
const CHILD: &str = "[child]";
const PARENT: &str = "[parent]";
const NEXT: &str = "[next]";
const PREV: &str = "[prev]";
struct Peers {
caps: Vec<Capsa>,
by_node: std::cell::OnceCell<HashMap<NodeId, Vec<usize>>>,
}
impl Peers {
fn new(caps: Vec<Capsa>) -> Peers {
Peers {
caps,
by_node: std::cell::OnceCell::new(),
}
}
fn at(&self, node: NodeId) -> &[usize] {
let index = self.by_node.get_or_init(|| {
let mut m: HashMap<NodeId, Vec<usize>> = HashMap::new();
for (i, c) in self.caps.iter().enumerate() {
m.entry(c.node).or_default().push(i);
}
m
});
index.get(&node).map_or(&[], |v| v.as_slice())
}
}
#[derive(Clone, Copy)]
pub(crate) struct Scope<'a> {
register: &'a [Reg],
topic: Option<&'a Value>,
prov: Option<&'a Prov>,
ordinal: Option<usize>,
captures: &'a [String],
named: &'a [(String, String)],
marks: &'a [Mark],
bindings: &'a [Option<NodeId>],
members: &'a [Capsa],
edge: Option<&'a EdgeCtx>,
arrived: &'a [EdgeCtx],
peers: Option<&'a Peers>,
outer: Option<&'a Scope<'a>>,
node: Option<NodeId>,
}
const NO_SCOPE: Scope<'static> = Scope {
prov: None,
register: &[],
members: &[],
topic: None,
ordinal: None,
captures: &[],
named: &[],
marks: &[],
bindings: &[],
edge: None,
arrived: &[],
peers: None,
outer: None,
node: None,
};
#[derive(Debug, Clone, PartialEq)]
pub enum QueryResult {
Nodes(Vec<NodeId>),
Values(Vec<Value>),
}
#[derive(Default)]
pub(crate) struct Correlation {
outer: Vec<bool>,
contexts: Vec<Vec<NodeId>>,
witnesses: std::cell::RefCell<std::collections::HashMap<u64, Vec<Option<NodeId>>>>,
join_keys:
std::cell::RefCell<std::collections::HashMap<(usize, String), std::rc::Rc<ValueKeys>>>,
}
pub(crate) type Trace = Correlation;
pub fn eval(query: &Query, adapter: &impl AstAdapter) -> QueryResult {
eval_with(query, adapter, None)
}
pub(crate) fn eval_with(
query: &Query,
adapter: &impl AstAdapter,
options: Option<RunOptions>,
) -> QueryResult {
let _run = begin_run(options);
eval_query(query, adapter, adapter.root(), &Correlation::default())
}
#[cfg(test)]
pub fn eval_traced_prov(query: &Query, adapter: &impl AstAdapter) -> Vec<Traced> {
eval_traced_prov_with(query, adapter, None)
}
pub(crate) fn eval_traced_prov_with(
query: &Query,
adapter: &impl AstAdapter,
options: Option<RunOptions>,
) -> Vec<Traced> {
let _run = begin_run(options);
let (caps, projected) =
eval_query_caps(query, adapter, adapter.root(), &Correlation::default());
caps.into_iter()
.map(|c| {
let topic = if projected {
Some(c.topic.unwrap_or(Value::Null))
} else {
c.topic
};
Traced {
node: c.node,
topic,
prov: c.prov,
}
})
.collect()
}
fn core_meta(adapter: &impl AstAdapter, node: NodeId, key: &str) -> Option<Value> {
let index = || index_of(adapter, node);
let n_siblings = || {
adapter
.parent(node)
.map(|p| adapter.children(p).len().saturating_sub(1))
};
match key {
"name" => Some(adapter.name(node).map_or(Value::Null, Value::Str)),
"id" => Some(Value::Int(node.0 as i64)),
"index" => Some(index().map_or(Value::Null, |i| Value::Int(i as i64))),
"depth" => Some(Value::Int(depth_of(adapter, node) as i64)),
"parent-name" => Some(
adapter
.parent(node)
.and_then(|p| adapter.name(p))
.map_or(Value::Null, Value::Str),
),
"parent-id" => Some(
adapter
.parent(node)
.map_or(Value::Null, |p| Value::Int(p.0 as i64)),
),
"parent-index" => Some(
adapter
.parent(node)
.and_then(|p| index_of(adapter, p))
.map_or(Value::Null, |i| Value::Int(i as i64)),
),
"n-children" => Some(Value::Int(adapter.children(node).len() as i64)),
"n-descendants" => Some(Value::Int(n_descendants(adapter, node) as i64)),
"n-siblings" => Some(n_siblings().map_or(Value::Null, |n| Value::Int(n as i64))),
"traits" => Some(Value::list(
adapter.traits(node).into_iter().map(Value::Str).collect(),
)),
"is-leaf" => Some(Value::Bool(adapter.children(node).is_empty())),
"is-root" => Some(Value::Bool(adapter.parent(node).is_none())),
"is-first-child" => Some(Value::Bool(index() == Some(1))),
"is-last-child" => Some(Value::Bool(
matches!((index(), n_siblings()), (Some(i), Some(n)) if i == n + 1),
)),
"is-top-level" => Some(Value::Bool(depth_of(adapter, node) == 1)),
"is-non-tree" => Some(Value::Bool(adapter.non_tree_nodes().contains(&node))),
"name-path" => Some(Value::Str(ancestor_path(adapter, node, |a, n| a.name(n)))),
"index-path" => Some(Value::Str(ancestor_path(adapter, node, |a, n| {
index_of(a, n).map(|i| i.to_string())
}))),
"id-path" => Some(Value::Str(ancestor_path(adapter, node, |_, n| {
Some(n.0.to_string())
}))),
"source" => Some(
resolved_provenance(adapter, node)
.first()
.source
.map_or(Value::Null, Value::Str),
),
"path" => Some(
resolved_provenance(adapter, node)
.first()
.path
.map_or(Value::Null, Value::Str),
),
"instant" => Some(resolved_provenance(adapter, node).instant_max().map_or(
Value::Null,
|(secs, nanos, offset_min)| Value::Instant {
secs,
nanos,
offset_min,
},
)),
"dpid" => Some(
resolved_provenance(adapter, node)
.first()
.dpid
.map_or(Value::Null, Value::Str),
),
"provenance" => Some(
resolved_provenance(adapter, node)
.canonical()
.map_or(Value::Null, Value::Str),
),
"%provenance" => Some(prov_record(&resolved_provenance(adapter, node).first())),
"@provenance" => Some(Value::list(
resolved_provenance(adapter, node)
.entries
.iter()
.map(prov_record)
.collect(),
)),
"origin" => Some(Value::Int(node.0 as i64)),
"%origin" => Some(origin_record(Origin { node, command: 0 })),
"@origin" => Some(Value::list(vec![origin_record(Origin {
node,
command: 0,
})])),
"elided" => Some(Value::Int(resolved_provenance(adapter, node).elided as i64)),
_ => None,
}
}
fn ancestor_path<A: AstAdapter>(
adapter: &A,
node: NodeId,
token: impl Fn(&A, NodeId) -> Option<String>,
) -> String {
let mut parts = Vec::new();
let mut cur = Some(node);
while let Some(n) = cur {
if adapter.parent(n).is_none() {
break; }
if let Some(t) = token(adapter, n) {
parts.push(t);
}
cur = adapter.parent(n);
}
parts.reverse();
format!("/{}", parts.join("/"))
}
fn index_of(adapter: &impl AstAdapter, node: NodeId) -> Option<usize> {
let parent = adapter.parent(node)?;
siblings_of(adapter, parent)
.iter()
.position(|&c| c == node)
.map(|i| i + 1)
}
fn depth_of(adapter: &impl AstAdapter, node: NodeId) -> usize {
let mut depth = 0;
let mut cur = adapter.parent(node);
while let Some(p) = cur {
depth += 1;
cur = adapter.parent(p);
}
depth
}
fn n_descendants(adapter: &impl AstAdapter, node: NodeId) -> usize {
let mut n = 0;
let mut stack = adapter.children(node);
while let Some(c) = stack.pop() {
n += 1;
stack.extend(adapter.children(c));
}
n
}
fn matches_name(adapter: &impl AstAdapter, node: NodeId, matcher: &Matcher) -> bool {
match matcher {
Matcher::Any | Matcher::Dot => true,
Matcher::Name(n) => adapter.answers_to(node, n),
_ => adapter
.name(node)
.is_some_and(|name| matcher.matches(&name)),
}
}
fn sibling(adapter: &impl AstAdapter, node: NodeId, offset: isize) -> Option<NodeId> {
let parent = adapter.parent(node)?;
let sibs = siblings_of(adapter, parent);
let i = sibs.iter().position(|&s| s == node)? as isize + offset;
if i < 0 {
return None;
}
sibs.get(i as usize).copied()
}
fn siblings_of(adapter: &impl AstAdapter, parent: NodeId) -> std::rc::Rc<Vec<NodeId>> {
if let Some(v) = with_run(|s| s.children.get(&parent).cloned()) {
return v;
}
let v = std::rc::Rc::new(adapter.children(parent));
with_run(|s| {
if s.children.len() >= 65_536 {
s.children.clear();
}
s.children.insert(parent, v.clone());
});
v
}
fn by_reach(matches: Vec<(NodeId, usize)>, reach: Reach) -> Vec<NodeId> {
let extreme = match reach {
Reach::All => None,
Reach::Proximal => matches.iter().map(|&(_, d)| d).min(),
Reach::Distal => matches.iter().map(|&(_, d)| d).max(),
};
matches
.into_iter()
.filter(|&(_, d)| extreme.is_none_or(|e| d == e))
.map(|(n, _)| n)
.collect()
}
fn dedup(nodes: Vec<NodeId>) -> Vec<NodeId> {
let mut seen = HashSet::new();
nodes.into_iter().filter(|n| seen.insert(*n)).collect()
}