use compact_str::CompactString;
use crate::error::{TraverserError, TraverserResult, ValueError};
use crate::graph::GraphStorage;
use crate::traversal::optimizer::path_analysis::{PathAnalysis, PathRequirement};
use crate::traversal::{
AnonymousTraversal, ArgsModulation, By, ByModulation, ByModulatorCheck, Traverser,
TraverserContext, args_reason,
};
use crate::value::path::TraverserPathStep;
use crate::value::{PathRef, TraverserValue};
#[derive(Debug, Clone, Default)]
pub(crate) struct PathWindow {
from: Option<CompactString>,
to: Option<CompactString>,
calls: usize,
rejected: Option<(usize, &'static str)>,
}
impl PathWindow {
pub(crate) fn is_set(&self) -> bool {
self.from.is_some() || self.to.is_some()
}
pub(crate) fn push(&mut self, args: Vec<String>, modulator: &str) -> ArgsModulation {
self.calls += 1;
let mut args = args.into_iter();
match (args.next(), args.next()) {
(Some(label), None) => {
let label = CompactString::from(label);
if modulator == "to" {
self.to = Some(label);
} else {
self.from = Some(label);
}
}
_ => {
if self.rejected.is_none() {
self.rejected = Some((self.calls, args_reason::ONE_LABEL));
}
}
}
ArgsModulation::Accepted
}
pub(crate) fn check(&self, step: &str) -> TraverserResult<()> {
match self.rejected {
Some((position, reason)) => Err(Box::new(TraverserError::InvalidModulator {
step: step.to_string(),
position,
modulator: args_reason::MODULATOR,
reason: reason.to_string(),
})),
None => Ok(()),
}
}
pub(crate) fn bounds<S: GraphStorage>(
&self,
steps: &[TraverserPathStep<S>],
) -> TraverserResult<std::ops::Range<usize>> {
let last_with = |label: &str| {
steps.iter().rposition(|step| {
step.label
.as_ref()
.is_some_and(|labels| labels.iter().any(|candidate| candidate == label))
})
};
let missing = |label: &CompactString| {
Box::new(TraverserError::Value(ValueError::PropertyNotFound {
key: label.to_string(),
element_type: "path",
}))
};
let start = match &self.from {
Some(label) => last_with(label).ok_or_else(|| missing(label))?,
None => 0,
};
let end = match &self.to {
Some(label) => last_with(label).ok_or_else(|| missing(label))?,
None => steps.len().saturating_sub(1),
};
if start > end {
return Err(Box::new(TraverserError::QueryExecution {
query: "path window".to_string(),
reason: format!(
"the path position labelled '{}' comes after the one labelled '{}', so from() \
and to() select no sub-path; swap them or label the positions in path order \
with as()",
self.from.as_deref().unwrap_or(""),
self.to.as_deref().unwrap_or(""),
),
}));
}
if steps.is_empty() {
return Ok(0..0);
}
Ok(start..end + 1)
}
pub(crate) fn slice<S: GraphStorage>(
&self,
steps: &mut Vec<TraverserPathStep<S>>,
) -> TraverserResult<()> {
if !self.is_set() {
return Ok(());
}
let range = self.bounds(steps)?;
steps.truncate(range.end);
steps.drain(..range.start);
Ok(())
}
pub(crate) fn render(&self) -> String {
let mut text = String::new();
if let Some(from) = &self.from {
text.push_str(&format!(
".from({})",
crate::value::literal::rhai_string(from)
));
}
if let Some(to) = &self.to {
text.push_str(&format!(".to({})", crate::value::literal::rhai_string(to)));
}
text
}
}
pub(crate) fn project_object<'g, S: GraphStorage + 'g>(
by: &By<'g, S>,
child_needs_path: bool,
context: &mut TraverserContext<'_, S>,
value: &TraverserValue<S>,
) -> TraverserResult<Option<TraverserValue<S>>> {
let mut object = Traverser::start(context, value.clone());
if child_needs_path && matches!(by, By::Traversal(_)) {
object.path = context
.paths
.push(PathRef::NONE, object.value.clone(), None)?;
}
TraverserValue::fetch_traverser_value_by(context, &object, &object.value, by)
}
pub(crate) fn apply_ring<'g, S: GraphStorage + 'g>(
by: &[By<'g, S>],
child_needs_path: bool,
context: &mut TraverserContext<'_, S>,
steps: &mut [TraverserPathStep<S>],
) -> TraverserResult<bool> {
if by.is_empty() {
return Ok(true);
}
for (position, step) in steps.iter_mut().enumerate() {
let by = &by[position % by.len()];
match project_object(by, child_needs_path, context, &step.value)? {
Some(value) => step.value = value,
None => return Ok(false),
}
}
Ok(true)
}
pub(crate) struct SimplicityFilter<'g, S: GraphStorage + 'g> {
by: Vec<By<'g, S>>,
window: PathWindow,
by_check: ByModulatorCheck,
child_needs_path: bool,
}
impl<'g, S: GraphStorage + 'g> Clone for SimplicityFilter<'g, S> {
fn clone(&self) -> Self {
Self {
by: self.by.clone(),
window: self.window.clone(),
by_check: self.by_check.clone(),
child_needs_path: self.child_needs_path,
}
}
}
impl<'g, S: GraphStorage + 'g> Default for SimplicityFilter<'g, S> {
fn default() -> Self {
Self {
by: Vec::new(),
window: PathWindow::default(),
by_check: ByModulatorCheck::default(),
child_needs_path: false,
}
}
}
impl<'g, S: GraphStorage + 'g> SimplicityFilter<'g, S> {
pub(crate) fn render_modulators(&self) -> String {
let mut text = self.window.render();
for by in &self.by {
text.push_str(&crate::traversal::render_by(by));
}
text
}
pub(crate) fn push_by(&mut self, by: By<'g, S>) -> ByModulation {
self.by_check.observe_ring(&by);
self.by.push(by);
ByModulation::Accepted
}
pub(crate) fn push_args(&mut self, args: Vec<String>, modulator: &str) -> ArgsModulation {
self.window.push(args, modulator)
}
pub(crate) fn plan_check(&self, step: &str) -> TraverserResult<()> {
self.by_check.check(step)?;
self.window.check(step)
}
pub(crate) fn traversals_mut(&mut self) -> Vec<&mut AnonymousTraversal<'g, S>> {
self.by.iter_mut().filter_map(By::traversal_mut).collect()
}
pub(crate) fn analyze(&mut self, analysis: &PathAnalysis) -> PathRequirement {
let entry = By::analyze_all_paths(&mut self.by, analysis);
self.child_needs_path = !entry.is_none();
PathRequirement::None
}
pub(crate) fn is_simple(
&self,
step: &str,
context: &mut TraverserContext<'_, S>,
source: &Traverser<S>,
) -> TraverserResult<Option<bool>> {
if self.by.is_empty() && !self.window.is_set() {
return Ok(Some(context.paths.is_simple(
&*context.storage,
source.path,
&mut context.scratch_path,
)));
}
self.plan_check(step)?;
let mut path = context.paths.materialize(source.path);
self.window.slice(&mut path.steps)?;
if !apply_ring(&self.by, self.child_needs_path, context, &mut path.steps)? {
return Ok(None);
}
let values: Vec<&TraverserValue<S>> = path.steps.iter().map(|step| &step.value).collect();
Ok(Some(crate::value::path_arena::is_simple_values(
&*context.storage,
&values,
)))
}
}