use crate::grammar::{ProductionId, TokenKind};
use super::node::CstNode;
#[must_use]
pub fn preorder<'a, 'src>(root: &'a CstNode<'src>) -> Vec<&'a CstNode<'src>> {
let mut out = Vec::new();
collect_preorder(root, &mut out);
out
}
fn collect_preorder<'a, 'src>(node: &'a CstNode<'src>, out: &mut Vec<&'a CstNode<'src>>) {
out.push(node);
for child in &node.children {
collect_preorder(child, out);
}
}
#[must_use]
pub fn postorder<'a, 'src>(root: &'a CstNode<'src>) -> Vec<&'a CstNode<'src>> {
let mut out = Vec::new();
collect_postorder(root, &mut out);
out
}
fn collect_postorder<'a, 'src>(node: &'a CstNode<'src>, out: &mut Vec<&'a CstNode<'src>>) {
for child in &node.children {
collect_postorder(child, out);
}
out.push(node);
}
#[must_use]
pub fn tokens_only<'a, 'src>(root: &'a CstNode<'src>) -> Vec<&'a CstNode<'src>> {
preorder(root)
.into_iter()
.filter(|n| n.is_token())
.collect()
}
#[must_use]
pub fn internals_only<'a, 'src>(root: &'a CstNode<'src>) -> Vec<&'a CstNode<'src>> {
preorder(root)
.into_iter()
.filter(|n| n.is_internal())
.collect()
}
#[must_use]
pub fn find_by_production<'a, 'src>(
root: &'a CstNode<'src>,
production: ProductionId,
) -> Vec<&'a CstNode<'src>> {
preorder(root)
.into_iter()
.filter(|n| n.production() == Some(production))
.collect()
}
#[must_use]
pub fn find_first_by_production<'a, 'src>(
root: &'a CstNode<'src>,
production: ProductionId,
) -> Option<&'a CstNode<'src>> {
preorder(root)
.into_iter()
.find(|n| n.production() == Some(production))
}
#[must_use]
pub fn find_by_token_kind<'a, 'src>(
root: &'a CstNode<'src>,
kind: TokenKind,
) -> Vec<&'a CstNode<'src>> {
preorder(root)
.into_iter()
.filter(|n| n.token_kind() == Some(kind))
.collect()
}
#[must_use]
pub fn count_by_production(root: &CstNode<'_>, production: ProductionId) -> usize {
find_by_production(root, production).len()
}
#[must_use]
pub fn depth(node: &CstNode<'_>) -> usize {
if node.children.is_empty() {
return 0;
}
1 + node.children.iter().map(depth).max().unwrap_or(0)
}
#[cfg(test)]
mod tests {
#![allow(clippy::expect_used, clippy::panic)]
use super::*;
use crate::cst::CstNode;
use crate::cst::build::build_cst;
use crate::engine::Recognizer;
use crate::errors::Span;
use crate::grammar::toy::TOY;
use crate::grammar::{ProductionId, TokenKind};
use crate::lexer::Token;
fn t(kind: TokenKind) -> Token<'static> {
Token::synthetic(kind)
}
fn make_toy_cst<'src>(tokens: &[Token<'src>]) -> CstNode<'src> {
let result = Recognizer::new(&TOY).recognize(tokens);
build_cst(&TOY, tokens, &result).expect("must build")
}
#[test]
fn preorder_visits_root_first_then_children() {
let mut root = CstNode::internal(ProductionId(0), 0, Span::SYNTHETIC);
root.push_child(CstNode::token(Token::synthetic(TokenKind::Keyword("a"))));
let mut sub = CstNode::internal(ProductionId(1), 0, Span::SYNTHETIC);
sub.push_child(CstNode::token(Token::synthetic(TokenKind::Keyword("b"))));
root.push_child(sub);
let order: Vec<_> = preorder(&root)
.iter()
.map(|n| {
if let Some(p) = n.production() {
format!("I({})", p.0)
} else if let Some(k) = n.token_kind() {
format!("T({k:?})")
} else {
"?".to_string()
}
})
.collect();
assert_eq!(order[0], "I(0)");
assert_eq!(order[1], "T(Keyword(\"a\"))");
assert_eq!(order[2], "I(1)");
assert_eq!(order[3], "T(Keyword(\"b\"))");
}
#[test]
fn postorder_visits_children_first_then_root() {
let mut root = CstNode::internal(ProductionId(0), 0, Span::SYNTHETIC);
let mut sub = CstNode::internal(ProductionId(1), 0, Span::SYNTHETIC);
sub.push_child(CstNode::token(Token::synthetic(TokenKind::Keyword("a"))));
root.push_child(sub);
root.push_child(CstNode::token(Token::synthetic(TokenKind::Keyword("b"))));
let order: Vec<_> = postorder(&root)
.iter()
.map(|n| {
if let Some(p) = n.production() {
format!("I({})", p.0)
} else if let Some(k) = n.token_kind() {
format!("T({k:?})")
} else {
"?".to_string()
}
})
.collect();
assert_eq!(
order,
vec![
"T(Keyword(\"a\"))".to_string(),
"I(1)".to_string(),
"T(Keyword(\"b\"))".to_string(),
"I(0)".to_string(),
]
);
}
#[test]
fn tokens_only_returns_only_leaves() {
let cst = make_toy_cst(&[
t(TokenKind::Keyword("n")),
t(TokenKind::Punct("+")),
t(TokenKind::Keyword("n")),
]);
let toks = tokens_only(&cst);
assert_eq!(toks.len(), 3);
assert!(toks.iter().all(|n| n.is_token()));
}
#[test]
fn internals_only_returns_only_internals() {
let cst = make_toy_cst(&[t(TokenKind::Keyword("n"))]);
let internals = internals_only(&cst);
assert_eq!(internals.len(), 3);
assert!(internals.iter().all(|n| n.is_internal()));
}
#[test]
fn find_by_production_collects_all_matches_in_preorder() {
let cst = make_toy_cst(&[
t(TokenKind::Keyword("n")),
t(TokenKind::Punct("+")),
t(TokenKind::Keyword("n")),
t(TokenKind::Punct("+")),
t(TokenKind::Keyword("n")),
]);
let es = find_by_production(&cst, ProductionId(0));
assert_eq!(
es.len(),
3,
"Erwartet 3 E-Knoten in n+n+n, gefunden {}",
es.len()
);
}
#[test]
fn find_first_by_production_returns_root_if_match() {
let cst = make_toy_cst(&[t(TokenKind::Keyword("n"))]);
let first_e = find_first_by_production(&cst, ProductionId(0));
assert!(first_e.map(|n| std::ptr::eq(n, &cst)).unwrap_or(false));
}
#[test]
fn find_first_by_production_none_for_missing() {
let cst = make_toy_cst(&[t(TokenKind::Keyword("n"))]);
assert!(find_first_by_production(&cst, ProductionId(99)).is_none());
}
#[test]
fn count_by_production_matches_find_length() {
let cst = make_toy_cst(&[
t(TokenKind::Keyword("n")),
t(TokenKind::Punct("*")),
t(TokenKind::Keyword("n")),
t(TokenKind::Punct("*")),
t(TokenKind::Keyword("n")),
]);
let t_nodes = find_by_production(&cst, ProductionId(1));
assert_eq!(count_by_production(&cst, ProductionId(1)), t_nodes.len());
}
#[test]
fn find_by_token_kind_collects_matching_tokens() {
let cst = make_toy_cst(&[
t(TokenKind::Keyword("n")),
t(TokenKind::Punct("+")),
t(TokenKind::Keyword("n")),
t(TokenKind::Punct("+")),
t(TokenKind::Keyword("n")),
]);
let pluses = find_by_token_kind(&cst, TokenKind::Punct("+"));
assert_eq!(pluses.len(), 2);
let ns = find_by_token_kind(&cst, TokenKind::Keyword("n"));
assert_eq!(ns.len(), 3);
}
#[test]
fn depth_of_single_token_is_zero() {
let leaf = CstNode::token(Token::synthetic(TokenKind::Keyword("x")));
assert_eq!(depth(&leaf), 0);
}
#[test]
fn depth_of_root_with_single_child_is_one() {
let mut root = CstNode::internal(ProductionId(0), 0, Span::SYNTHETIC);
root.push_child(CstNode::token(Token::synthetic(TokenKind::Keyword("x"))));
assert_eq!(depth(&root), 1);
}
#[test]
fn depth_of_toy_single_n_is_three() {
let cst = make_toy_cst(&[t(TokenKind::Keyword("n"))]);
assert_eq!(depth(&cst), 3);
}
#[test]
fn depth_takes_max_of_children() {
let mut root = CstNode::internal(ProductionId(0), 0, Span::SYNTHETIC);
root.push_child(CstNode::token(Token::synthetic(TokenKind::Keyword("a"))));
let mut deep = CstNode::internal(ProductionId(1), 0, Span::SYNTHETIC);
let mut deeper = CstNode::internal(ProductionId(2), 0, Span::SYNTHETIC);
deeper.push_child(CstNode::token(Token::synthetic(TokenKind::Keyword("b"))));
deep.push_child(deeper);
root.push_child(deep);
assert_eq!(depth(&root), 3);
}
}