use crate::parser::{Expr, SeqItem};
use plotnik_bytecode::Nav;
pub use crate::parser::is_truly_empty_scope;
pub fn expr_is_anonymous(expr: Option<&Expr>) -> bool {
matches!(expr, Some(Expr::AnonymousNode(_)))
}
pub fn check_trailing_anchor(items: &[SeqItem]) -> (bool, bool) {
if matches!(items.last(), Some(SeqItem::Anchor(_))) {
let prev_expr = items.iter().rev().skip(1).find_map(|item| {
if let SeqItem::Expr(e) = item {
Some(e)
} else {
None
}
});
return (true, expr_is_anonymous(prev_expr));
}
if items.len() == 1
&& let Some(SeqItem::Expr(Expr::SeqExpr(seq))) = items.first()
{
let seq_items: Vec<_> = seq.items().collect();
return check_trailing_anchor(&seq_items);
}
(false, false)
}
pub fn compute_nav_modes(items: &[SeqItem], is_inside_node: bool) -> Vec<(usize, Option<Nav>)> {
let mut result = Vec::new();
let mut pending_anchor = false;
let mut prev_is_anonymous = false;
let mut is_first_expr = true;
for (idx, item) in items.iter().enumerate() {
match item {
SeqItem::Anchor(_) => {
pending_anchor = true;
}
SeqItem::Expr(expr) => {
let current_is_anonymous = matches!(expr, Expr::AnonymousNode(_));
let nav = if pending_anchor {
let is_exact = prev_is_anonymous || current_is_anonymous;
if is_first_expr && is_inside_node {
Some(if is_exact {
Nav::DownExact
} else {
Nav::DownSkip
})
} else if !is_first_expr {
Some(if is_exact {
Nav::NextExact
} else {
Nav::NextSkip
})
} else {
None
}
} else if !is_first_expr {
Some(Nav::Next)
} else {
None
};
result.push((idx, nav));
pending_anchor = false;
prev_is_anonymous = current_is_anonymous;
is_first_expr = false;
}
}
}
result
}
pub fn repeat_nav_for(first_nav: Option<Nav>) -> Option<Nav> {
match first_nav {
Some(Nav::Down) => Some(Nav::Next),
Some(Nav::DownSkip) => Some(Nav::NextSkip),
Some(Nav::DownExact) => Some(Nav::NextExact),
Some(nav @ (Nav::Next | Nav::NextSkip | Nav::NextExact)) => Some(nav),
None | Some(Nav::Stay) => Some(Nav::Next),
_ => None,
}
}
pub fn is_down_nav(nav: Option<Nav>) -> bool {
matches!(nav, Some(Nav::Down | Nav::DownSkip | Nav::DownExact))
}
fn quantifier_operator_kind(expr: &Expr) -> Option<crate::parser::SyntaxKind> {
let expr = match expr {
Expr::CapturedExpr(cap) => cap.inner()?,
e => e.clone(),
};
let Expr::QuantifiedExpr(q) = &expr else {
return None;
};
Some(q.operator()?.kind())
}
pub fn is_skippable_quantifier(expr: &Expr) -> bool {
use crate::parser::SyntaxKind;
quantifier_operator_kind(expr).is_some_and(|k| {
matches!(
k,
SyntaxKind::Question
| SyntaxKind::QuestionQuestion
| SyntaxKind::Star
| SyntaxKind::StarQuestion
)
})
}
pub fn is_star_or_plus_quantifier(expr: Option<&Expr>) -> bool {
use crate::parser::SyntaxKind;
expr.and_then(quantifier_operator_kind).is_some_and(|k| {
matches!(
k,
SyntaxKind::Star
| SyntaxKind::StarQuestion
| SyntaxKind::Plus
| SyntaxKind::PlusQuestion
)
})
}
pub fn inner_creates_scope(inner: &Expr) -> bool {
match inner {
Expr::SeqExpr(_) | Expr::AltExpr(_) => true,
Expr::QuantifiedExpr(q) => q.inner().is_some_and(|i| inner_creates_scope(&i)),
_ => false,
}
}