Skip to main content

cp_ast_core/structure/
structure_ast.rs

1use super::node_id::NodeId;
2use super::node_kind::NodeKind;
3use super::structure_node::StructureNode;
4
5/// Arena-based structure AST.
6///
7/// Nodes are stored in a Vec indexed by `NodeId`. Insertion order is preserved
8/// for deterministic canonical rendering (Rev.1 M-1).
9#[derive(Debug, Clone)]
10pub struct StructureAst {
11    root: NodeId,
12    arena: Vec<Option<StructureNode>>,
13    next_id: u64,
14}
15
16impl StructureAst {
17    /// Create a new AST with an empty Sequence as root.
18    #[must_use]
19    pub fn new() -> Self {
20        let root_id = NodeId::from_raw(0);
21        let root_node = StructureNode::new(
22            root_id,
23            NodeKind::Sequence {
24                children: Vec::new(),
25            },
26        );
27        Self {
28            root: root_id,
29            arena: vec![Some(root_node)],
30            next_id: 1,
31        }
32    }
33
34    /// Add a node to the arena and return its assigned `NodeId`.
35    #[allow(clippy::cast_possible_truncation)] // NodeId values are controlled and expected to fit in usize
36    pub fn add_node(&mut self, kind: NodeKind) -> NodeId {
37        let id = NodeId::from_raw(self.next_id);
38        self.next_id += 1;
39        let node = StructureNode::new(id, kind);
40        let idx = id.value() as usize;
41        if idx >= self.arena.len() {
42            self.arena.resize_with(idx + 1, || None);
43        }
44        self.arena[idx] = Some(node);
45        id
46    }
47
48    /// Get a reference to a node by ID.
49    #[must_use]
50    #[allow(clippy::cast_possible_truncation)] // NodeId values are controlled and expected to fit in usize
51    pub fn get(&self, id: NodeId) -> Option<&StructureNode> {
52        self.arena
53            .get(id.value() as usize)
54            .and_then(|slot| slot.as_ref())
55    }
56
57    /// Get a mutable reference to a node by ID.
58    #[allow(clippy::cast_possible_truncation)] // NodeId values are controlled and expected to fit in usize
59    pub fn get_mut(&mut self, id: NodeId) -> Option<&mut StructureNode> {
60        self.arena
61            .get_mut(id.value() as usize)
62            .and_then(|slot| slot.as_mut())
63    }
64
65    /// Remove a node from the arena, returning it if it existed.
66    #[allow(clippy::cast_possible_truncation)] // NodeId values are controlled and expected to fit in usize
67    pub fn remove(&mut self, id: NodeId) -> Option<StructureNode> {
68        self.arena
69            .get_mut(id.value() as usize)
70            .and_then(Option::take)
71    }
72
73    /// Returns the root node ID.
74    #[must_use]
75    pub fn root(&self) -> NodeId {
76        self.root
77    }
78
79    /// Set a new root node ID.
80    pub fn set_root(&mut self, id: NodeId) {
81        self.root = id;
82    }
83
84    /// Check if a node exists in the arena.
85    #[must_use]
86    pub fn contains(&self, id: NodeId) -> bool {
87        self.get(id).is_some()
88    }
89
90    /// Returns the count of live (non-removed) nodes.
91    #[must_use]
92    pub fn len(&self) -> usize {
93        self.arena.iter().filter(|s| s.is_some()).count()
94    }
95
96    /// Returns true if the arena has no live nodes.
97    #[must_use]
98    pub fn is_empty(&self) -> bool {
99        self.len() == 0
100    }
101
102    /// Iterate over all live nodes in arena order.
103    pub fn iter(&self) -> impl Iterator<Item = &StructureNode> {
104        self.arena.iter().filter_map(|s| s.as_ref())
105    }
106
107    /// Returns the next ID that will be assigned.
108    #[must_use]
109    pub fn next_id(&self) -> u64 {
110        self.next_id
111    }
112
113    /// Returns a raw view of the arena including tombstone (`None`) slots.
114    ///
115    /// Used by serialization layers that need lossless arena snapshots.
116    #[must_use]
117    pub fn arena_raw(&self) -> &[Option<StructureNode>] {
118        &self.arena
119    }
120
121    /// Reconstruct a `StructureAst` from raw parts.
122    ///
123    /// Used by deserialization layers for lossless arena restoration.
124    /// The caller must ensure arena consistency (IDs match indices, etc.).
125    #[must_use]
126    pub fn from_raw_parts(root: NodeId, arena: Vec<Option<StructureNode>>, next_id: u64) -> Self {
127        Self {
128            root,
129            arena,
130            next_id,
131        }
132    }
133}
134
135impl Default for StructureAst {
136    fn default() -> Self {
137        Self::new()
138    }
139}