sphinx/debug/symbol/
resolver.rs

1// Symbol Resolution
2
3use core::cmp;
4use core::iter;
5
6use std::io;
7use std::rc::Rc;
8use std::collections::{BinaryHeap, HashMap};
9
10use crate::source::{ModuleSource, SourceText};
11use crate::debug::symbol::{DebugSymbol, ResolvedSymbol, TokenIndex};
12use crate::debug::symbol::errors::{SymbolResolutionError, ErrorKind};
13
14
15pub trait DebugSymbolResolver {
16    fn resolve_symbols<'s, S>(&self, symbols: S) -> io::Result<ResolvedSymbolTable<'s>> where S: Iterator<Item=&'s DebugSymbol>;
17}
18
19
20impl DebugSymbolResolver for ModuleSource {
21    fn resolve_symbols<'s, S>(&self, symbols: S) -> io::Result<ResolvedSymbolTable<'s>> where S: Iterator<Item=&'s DebugSymbol> {
22        match self.read_text()? {
23            SourceText::String(text) => Ok(resolve_debug_symbols(text.chars().map(Ok), symbols)),
24            SourceText::File(text) => Ok(resolve_debug_symbols(text, symbols))
25        }
26    }
27}
28
29pub struct BufferedResolver {
30    buffer: String,
31}
32
33impl BufferedResolver {
34    pub fn new(string: impl ToString) -> Self {
35        Self { buffer: string.to_string() }
36    }
37}
38
39impl DebugSymbolResolver for BufferedResolver {
40    fn resolve_symbols<'s, S>(&self, symbols: S) -> io::Result<ResolvedSymbolTable<'s>> where S: Iterator<Item=&'s DebugSymbol> {
41        Ok(resolve_debug_symbols(self.buffer.chars().map(Ok), symbols))
42    }
43}
44
45
46pub struct ResolvedSymbolTable<'s> {
47    table: HashMap<&'s DebugSymbol, Result<ResolvedSymbol, SymbolResolutionError>>,
48}
49
50impl<'s> iter::FromIterator<(&'s DebugSymbol, Result<ResolvedSymbol, SymbolResolutionError>)> for ResolvedSymbolTable<'s> {
51    fn from_iter<T>(iter: T) -> Self 
52    where T: IntoIterator<Item=(&'s DebugSymbol, Result<ResolvedSymbol, SymbolResolutionError>)> {
53        Self {
54            table: HashMap::from_iter(iter)
55        }
56    }
57}
58
59impl<'s> ResolvedSymbolTable<'s> {
60    pub fn lookup(&self, symbol: &DebugSymbol) -> Option<Result<&ResolvedSymbol, &SymbolResolutionError>> {
61        self.table.get(symbol).map(|result| result.as_ref())
62    }
63    
64    pub fn iter(&self) -> impl Iterator<Item=(&DebugSymbol, Result<&ResolvedSymbol, &SymbolResolutionError>)> {
65        self.table.iter().map(|(symbol, resolved)| (*symbol, resolved.as_ref()))
66    }
67    
68    fn new() -> Self {
69        Self { table: HashMap::new() }
70    }
71    
72    fn insert(&mut self, symbol: &'s DebugSymbol, result: Result<ResolvedSymbol, SymbolResolutionError>) {
73        self.table.insert(symbol, result);
74    }
75}
76
77
78// resolution procedure
79
80
81fn resolve_debug_symbols<'s>(source: impl Iterator<Item=io::Result<char>>, symbols: impl Iterator<Item=&'s DebugSymbol>) -> ResolvedSymbolTable<'s> {
82    // deduplicate symbols
83    let mut dedup_symbols = symbols.collect::<Vec<&DebugSymbol>>();
84    dedup_symbols.dedup();
85    
86    // put all the symbols into a priority queue based on first occurrence in the source text
87    let mut next_symbols = BinaryHeap::new();
88    next_symbols.extend(dedup_symbols.into_iter().map(|sym| IndexSort(sym, SortIndex::Start)).map(cmp::Reverse));
89    // let symbol_count = next_symbols.len();
90    
91    let mut open_symbols = BinaryHeap::new();
92    let mut active_symbols = HashMap::<&DebugSymbol, (Vec<Rc<String>>, usize, usize)>::new(); // values are (buffer, line number, start index)
93    let mut closing_symbols = HashMap::<&DebugSymbol, (Vec<Rc<String>>, usize, usize, usize)>::new();
94    let mut resolved_symbols = ResolvedSymbolTable::new();
95    
96    // handle symbols at EOF by adding a single blank at the end
97    let source = source.chain(iter::once(Ok(' ')));
98    
99    let mut lineno = 1;  // count lines
100    let mut current_line = String::new();
101    for (char_result, index) in source.zip(0..) {
102        // check for io::Errors 
103        let c = match char_result {
104            Ok(c) => c,
105            
106            Err(ioerror) => {
107                let ioerror = Rc::new(ioerror);
108                
109                // drop all open symbols and close all closing symbols
110                active_symbols.clear();
111                for cmp::Reverse(IndexSort(symbol,..)) in open_symbols.drain() {
112                    let error = SymbolResolutionError::caused_by(*symbol, ioerror.clone());
113                    resolved_symbols.insert(symbol, Err(error));
114                }
115                
116                // we've already have the required text for all closing symbols, so we don't need to drop them
117                // just add a suffix that indicates that there was some trailing line content that was lost due to an error
118                let error_rc = Rc::new(current_line.clone() + "...???");
119                for (symbol, (mut lines, lineno, start_index, end_index)) in closing_symbols.drain() {
120                    lines.push(error_rc.clone());
121                    let resolved = ResolvedSymbol::new(lines, lineno, start_index, end_index);
122                    resolved_symbols.insert(symbol, Ok(resolved));
123                }
124                
125                current_line.clear();
126                current_line += "???...";
127            
128                break;
129            },
130        };
131        
132        // add the char to the current line
133        current_line.push(c);
134        
135        // if we are at the start of a new symbol, open it
136        while matches!(next_symbols.peek(), Some(&cmp::Reverse(IndexSort(sym,..))) if index == sym.start()) {
137            let symbol = next_symbols.pop().unwrap().0.0;
138            
139            active_symbols.entry(symbol).or_insert_with(|| {
140                open_symbols.push(cmp::Reverse(IndexSort(symbol, SortIndex::End)));
141                
142                let start_index = current_line.len() - 1;
143                (Vec::new(), lineno, start_index)
144            });
145        }
146        
147        // if we are at the end of an open symbol, mark it as closing
148        while matches!(open_symbols.peek(), Some(&cmp::Reverse(IndexSort(sym,..))) if index == sym.end()) {
149            let symbol = open_symbols.pop().unwrap().0.0;
150            if let Some((lines, lineno, start_index)) = active_symbols.remove(&symbol) {
151                closing_symbols.entry(symbol).or_insert_with(|| {
152                    
153                    // calculate end_index
154                    let total_len = lines.iter()
155                        .map(|line| line.len())
156                        .reduce(|acc, n| acc+n)
157                        .unwrap_or(0);
158                    
159                    let end_index = total_len + current_line.len() - 1;
160                    
161                    (lines, lineno, start_index, end_index)
162                });
163            }
164        }
165        
166        // once we complete the current line, add it to all open and closing symbols
167        if c == '\n' {
168            lineno +=1; // count newlines
169            let line_rc = Rc::new(current_line.clone());
170            
171            // open symbols
172            for (ref mut lines, ..) in active_symbols.values_mut() {
173                lines.push(Rc::clone(&line_rc));
174            }
175            
176            // close all closing symbols
177            for (symbol, (mut lines, lineno, start_index, end_index)) in closing_symbols.drain() {
178                lines.push(Rc::clone(&line_rc));
179                
180                let resolved = ResolvedSymbol::new(lines, lineno, start_index, end_index);
181                resolved_symbols.insert(symbol, Ok(resolved));
182            }
183            
184            // prepare buffer for next line
185            current_line.clear();
186        }
187        
188        // println!("{}: {}", lineno, current_line);
189        // println!("next_symbols: {:?}", next_symbols);
190        // println!("open_symbols: {:?}", open_symbols);
191        // println!("active_symbols: {:?}", active_symbols);
192        // println!("closing_symbols: {:?}", closing_symbols);
193        // println!("resolved_symbols: {:?}", resolved_symbols);
194        
195        if next_symbols.is_empty() && active_symbols.is_empty() && closing_symbols.is_empty() {
196            break;
197        }
198    }
199    
200    // process any active or closing symbols left after we hit EOF
201    for cmp::Reverse(IndexSort(symbol,..)) in open_symbols.drain() {
202        let error = SymbolResolutionError::new(*symbol, ErrorKind::EOFReached);
203        resolved_symbols.insert(symbol, Err(error));
204    }
205    
206    let line_rc = Rc::new(current_line.clone());
207    for (symbol, (mut lines, lineno, start_index, end_index)) in closing_symbols.drain() {
208        lines.push(line_rc.clone());
209        
210        let resolved = ResolvedSymbol::new(lines, lineno, start_index, end_index);
211        resolved_symbols.insert(symbol, Ok(resolved));
212    }
213    
214    // debug_assert!(symbol_count == resolved_symbols.len());
215    
216    resolved_symbols
217}
218
219
220#[derive(Debug)]
221enum SortIndex { Start, End }
222
223// comparison based on start or end index
224
225#[derive(Debug)]
226struct IndexSort<'s>(&'s DebugSymbol, SortIndex);
227
228impl IndexSort<'_> {
229    fn sort_value(&self) -> TokenIndex {
230        match self.1 {
231            SortIndex::Start => self.0.start(),
232            SortIndex::End => self.0.end(),
233        }
234    }
235}
236
237impl PartialEq for IndexSort<'_> {
238    fn eq(&self, other: &Self) -> bool {
239        self.sort_value() == other.sort_value()
240    }
241}
242impl Eq for IndexSort<'_> { }
243
244impl PartialOrd for IndexSort<'_> {
245    fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
246        Some(TokenIndex::cmp(&self.sort_value(), &other.sort_value()))
247    }
248}
249
250impl Ord for IndexSort<'_> {
251    fn cmp(&self, other: &Self) -> cmp::Ordering {
252        IndexSort::partial_cmp(self, other).unwrap()
253    }
254}