lutra_compiler/codespan/
line_numbers.rs1use crate::codespan::{LineColumn, Range, Span};
2use std::collections::HashMap;
3
4#[derive(Debug, Clone, PartialEq, Eq)]
8pub struct LineNumbers {
9 pub line_starts: Vec<u32>,
11 pub length: u32,
13 pub mapping: HashMap<usize, CharLen>,
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub struct CharLen {
34 pub length_utf8: u8,
36 pub length_utf16: u8,
38}
39
40impl LineNumbers {
41 pub fn new(src: &str) -> Self {
42 Self {
43 length: src.len() as u32,
44 line_starts: std::iter::once(0)
45 .chain(src.match_indices('\n').map(|(i, _)| i as u32 + 1))
46 .collect(),
47 mapping: Self::mapping(src),
48 }
49 }
50
51 fn mapping(src: &str) -> HashMap<usize, CharLen> {
52 let mut map = HashMap::new();
53
54 for (i, char) in src.char_indices() {
55 let length = char.len_utf8();
56 if length != 1 {
57 _ = map.insert(
58 i,
59 CharLen {
60 length_utf8: length as u8,
61 length_utf16: char.len_utf16() as u8,
62 },
63 );
64 }
65 }
66
67 map
68 }
69
70 pub fn line_number(&self, byte_index: u32) -> u32 {
72 self.line_starts
73 .binary_search(&byte_index)
74 .unwrap_or_else(|next_line| next_line - 1) as u32
75 }
76
77 pub fn line_and_column_number(&self, byte_index: u32) -> LineColumn {
79 let line = self.line_number(byte_index);
80 let line_start = self
81 .line_starts
82 .get(line as usize)
83 .copied()
84 .unwrap_or_default();
85
86 let mut u8_offset = line_start;
87 let mut u16_offset = 0;
88
89 loop {
90 if u8_offset >= byte_index {
91 break;
92 }
93
94 if let Some(length) = self.mapping.get(&(u8_offset as usize)) {
95 u8_offset += length.length_utf8 as u32;
96 u16_offset += length.length_utf16 as u32;
97 } else {
98 u16_offset += 1;
99 u8_offset += 1;
100 }
101 }
102
103 LineColumn {
104 line,
105 column: u16_offset,
106 }
107 }
108
109 pub fn byte_index_of_line_col(&self, position: LineColumn) -> u32 {
112 let line_start = match self.line_starts.get(position.line as usize) {
113 Some(&line_start) => line_start,
114 None => return self.length,
115 };
116
117 let mut u8_offset = line_start;
118 let mut u16_offset = 0;
119
120 loop {
121 if u16_offset >= position.column {
122 break;
123 }
124
125 if let Some(length) = self.mapping.get(&(u8_offset as usize)) {
126 u8_offset += length.length_utf8 as u32;
127 u16_offset += length.length_utf16 as u32;
128 } else {
129 u16_offset += 1;
130 u8_offset += 1;
131 }
132 }
133
134 u8_offset
135 }
136
137 pub fn spans_entire_line(&self, span: &Span) -> bool {
140 self.line_starts.iter().any(|&line_start| {
141 line_start == span.start && self.line_starts.contains(&(span.end() + 1))
142 })
143 }
144
145 pub fn span_of_range(&self, range: &Range, source_id: u16) -> Span {
147 let start = self.byte_index_of_line_col(range.start);
148 let end = self.byte_index_of_line_col(range.end);
149 let len = end.saturating_sub(start) as u16;
150 Span {
151 start,
152 len,
153 source_id,
154 }
155 }
156
157 pub fn range_of_span(&self, span: Span) -> Range {
159 let start = self.line_and_column_number(span.start);
160 let end = self.line_and_column_number(span.end());
161 Range { start, end }
162 }
163}