plotnik_compiler/analyze/validation/
anchors.rs

1//! Semantic validation for anchor placement.
2//!
3//! Anchors require context to be meaningful:
4//! - **Boundary anchors** (at start/end of sequence) need parent named node context
5//! - **Interior anchors** (between items) are always valid
6//!
7//! This validation ensures anchors are placed where they can be meaningfully compiled.
8
9use crate::SourceId;
10use crate::analyze::visitor::{Visitor, walk_named_node, walk_seq_expr};
11use crate::diagnostics::{DiagnosticKind, Diagnostics};
12use crate::parser::{NamedNode, Root, SeqExpr, SeqItem};
13
14pub fn validate_anchors(source_id: SourceId, ast: &Root, diag: &mut Diagnostics) {
15    let mut visitor = AnchorValidator {
16        diag,
17        source_id,
18        in_named_node: false,
19    };
20    visitor.visit(ast);
21}
22
23struct AnchorValidator<'a> {
24    diag: &'a mut Diagnostics,
25    source_id: SourceId,
26    in_named_node: bool,
27}
28
29impl Visitor for AnchorValidator<'_> {
30    fn visit_named_node(&mut self, node: &NamedNode) {
31        let prev = self.in_named_node;
32        self.in_named_node = true;
33
34        // Check for anchors in the named node's items
35        self.check_items(node.items());
36
37        // Anchors inside named node children are always valid
38        // (the node provides first/last/adjacent context)
39        walk_named_node(self, node);
40
41        self.in_named_node = prev;
42    }
43
44    fn visit_seq_expr(&mut self, seq: &SeqExpr) {
45        // Check for boundary anchors without context
46        self.check_items(seq.items());
47
48        walk_seq_expr(self, seq);
49    }
50}
51
52impl AnchorValidator<'_> {
53    fn check_items(&mut self, items: impl Iterator<Item = SeqItem>) {
54        let items: Vec<_> = items.collect();
55        let len = items.len();
56
57        for (i, item) in items.iter().enumerate() {
58            if let SeqItem::Anchor(anchor) = item {
59                let is_boundary = i == 0 || i == len - 1;
60
61                if is_boundary && !self.in_named_node {
62                    self.diag
63                        .report(
64                            self.source_id,
65                            DiagnosticKind::AnchorWithoutContext,
66                            anchor.text_range(),
67                        )
68                        .emit();
69                }
70            }
71        }
72    }
73}