Skip to main content

sqruff_lib/utils/reflow/
depth_map.rs

1use hashbrown::{HashMap, HashSet};
2use nohash_hasher::{IntMap, IntSet};
3use sqruff_lib_core::dialects::syntax::SyntaxSet;
4use sqruff_lib_core::parser::segments::{ErasedSegment, PathStep};
5
6/// An element of the stack_positions property of DepthInfo.
7#[derive(Debug, PartialEq, Eq, Clone)]
8pub struct StackPosition {
9    pub idx: usize,
10    pub len: usize,
11    pub type_: Option<StackPositionType>,
12}
13
14#[derive(Debug, PartialEq, Eq, Clone)]
15pub enum StackPositionType {
16    Solo,
17    Start,
18    End,
19}
20
21impl StackPosition {
22    /// Interpret a path step for stack_positions.
23    fn stack_pos_interpreter(path_step: &PathStep) -> Option<StackPositionType> {
24        if path_step.code_idxs.is_empty() {
25            None
26        } else if path_step.code_idxs.len() == 1 {
27            Some(StackPositionType::Solo)
28        } else if path_step.idx == *path_step.code_idxs.first().unwrap() {
29            Some(StackPositionType::Start)
30        } else if path_step.idx == *path_step.code_idxs.last().unwrap() {
31            Some(StackPositionType::End)
32        } else {
33            None
34        }
35    }
36
37    /// Interpret a PathStep to construct a StackPosition
38    fn from_path_step(path_step: &PathStep) -> StackPosition {
39        StackPosition {
40            idx: path_step.idx,
41            len: path_step.len,
42            type_: StackPosition::stack_pos_interpreter(path_step),
43        }
44    }
45}
46
47pub struct DepthMap {
48    depth_info: HashMap<u32, DepthInfo>,
49}
50
51impl DepthMap {
52    fn new<'a>(raws_with_stack: impl Iterator<Item = &'a (ErasedSegment, Vec<PathStep>)>) -> Self {
53        let depth_info = raws_with_stack
54            .into_iter()
55            .map(|(raw, stack)| (raw.id(), DepthInfo::from_stack(stack)))
56            .collect();
57        Self { depth_info }
58    }
59
60    pub fn from_raws_with_stack(raws_with_stack: &[(ErasedSegment, Vec<PathStep>)]) -> Self {
61        Self::new(raws_with_stack.iter())
62    }
63
64    pub fn get_depth_info(&self, seg: &ErasedSegment) -> DepthInfo {
65        self.depth_info[&seg.id()].clone()
66    }
67
68    pub fn copy_depth_info(
69        &mut self,
70        anchor: &ErasedSegment,
71        new_segment: &ErasedSegment,
72        trim: u32,
73    ) {
74        self.depth_info.insert(
75            new_segment.id(),
76            self.get_depth_info(anchor).trim(trim.try_into().unwrap()),
77        );
78    }
79
80    pub fn from_parent(parent: &ErasedSegment) -> Self {
81        Self::from_raws_with_stack(parent.raw_segments_with_ancestors())
82    }
83
84    pub fn from_raws_and_root(
85        raw_segments: impl Iterator<Item = ErasedSegment>,
86        root_segment: &ErasedSegment,
87    ) -> DepthMap {
88        let depth_info = raw_segments
89            .into_iter()
90            .map(|raw| {
91                let stack = root_segment.path_to(&raw);
92                (raw.id(), DepthInfo::from_stack(&stack))
93            })
94            .collect();
95
96        DepthMap { depth_info }
97    }
98}
99
100/// An object to hold the depth information for a specific raw segment.
101#[derive(Debug, PartialEq, Eq, Clone)]
102pub struct DepthInfo {
103    pub stack_depth: usize,
104    pub stack_hashes: Vec<u64>,
105    /// This is a convenience cache to speed up operations.
106    pub stack_hash_set: IntSet<u64>,
107    pub stack_class_types: Vec<SyntaxSet>,
108    pub stack_positions: IntMap<u64, StackPosition>,
109}
110
111impl DepthInfo {
112    fn from_stack(stack: &[PathStep]) -> DepthInfo {
113        // Build all structures in a single pass to avoid repeated iteration and
114        // intermediate allocations.
115        let mut stack_hashes = Vec::with_capacity(stack.len());
116        let mut stack_hash_set: IntSet<u64> = IntSet::default();
117        stack_hash_set.reserve(stack.len());
118        let mut stack_class_types = Vec::with_capacity(stack.len());
119        let mut stack_positions: IntMap<u64, StackPosition> = IntMap::default();
120        stack_positions.reserve(stack.len());
121
122        for path in stack {
123            let hash = path.segment.hash_value();
124            stack_hashes.push(hash);
125            stack_hash_set.insert(hash);
126            stack_class_types.push(path.segment.class_types().clone());
127            stack_positions.insert(hash, StackPosition::from_path_step(path));
128        }
129
130        DepthInfo {
131            stack_depth: stack_hashes.len(),
132            stack_hashes,
133            stack_hash_set,
134            stack_class_types,
135            stack_positions,
136        }
137    }
138
139    pub fn trim(self, amount: usize) -> DepthInfo {
140        // Return a DepthInfo object with some amount trimmed.
141        if amount == 0 {
142            // The trivial case.
143            return self;
144        }
145
146        let slice_set: IntSet<_> = IntSet::from_iter(
147            self.stack_hashes[self.stack_hashes.len() - amount..]
148                .iter()
149                .copied(),
150        );
151
152        let new_hash_set: IntSet<_> = self
153            .stack_hash_set
154            .difference(&slice_set)
155            .copied()
156            .collect();
157
158        let stack_positions = self
159            .stack_positions
160            .into_iter()
161            .filter(|(hash, _)| new_hash_set.contains(hash))
162            .collect();
163
164        DepthInfo {
165            stack_depth: self.stack_depth - amount,
166            stack_hashes: self.stack_hashes[..self.stack_hashes.len() - amount].to_vec(),
167            stack_hash_set: new_hash_set,
168            stack_class_types: self.stack_class_types[..self.stack_class_types.len() - amount]
169                .to_vec(),
170            stack_positions,
171        }
172    }
173
174    pub fn common_with(&self, other: &DepthInfo) -> Vec<u64> {
175        // Get the common depth and hashes with the other.
176        // We use HashSet intersection because it's efficient and hashes should be
177        // unique.
178
179        let common_hashes: HashSet<_> = self
180            .stack_hash_set
181            .intersection(&other.stack_hash_set)
182            .copied()
183            .collect();
184
185        // We should expect there to be _at least_ one common ancestor, because
186        // they should share the same file segment. If that's not the case we
187        // should error because it's likely a bug or programming error.
188        assert!(
189            !common_hashes.is_empty(),
190            "DepthInfo comparison shares no common ancestor!"
191        );
192
193        let common_depth = common_hashes.len();
194        self.stack_hashes
195            .iter()
196            .take(common_depth)
197            .copied()
198            .collect()
199    }
200}