1use std::{cmp, fmt::Write, sync::LazyLock};
4
5use itertools::Itertools;
6
7#[derive(Clone, Copy, PartialEq, Eq, Debug)]
8pub struct PosIdx(u32);
11
12#[derive(Clone, Copy, PartialEq, Eq)]
13pub struct FileIdx(u32);
16
17struct File {
19 name: Box<str>,
21 source: Box<str>,
23}
24
25struct PosData {
26 file: FileIdx,
29 start: usize,
31 end: usize,
33}
34
35struct PositionTable {
37 files: boxcar::Vec<File>,
39 indices: boxcar::Vec<PosData>,
41}
42
43impl Default for PositionTable {
44 fn default() -> Self {
45 Self::new()
46 }
47}
48
49impl PositionTable {
50 pub const UNKNOWN: PosIdx = PosIdx(0);
52
53 pub fn new() -> Self {
55 let table = PositionTable {
56 files: boxcar::Vec::new(),
57 indices: boxcar::Vec::new(),
58 };
59 table.add_file("unknown".to_string(), "".to_string());
60 let pos = table.add_pos(FileIdx(0), 0, 0);
61 debug_assert!(pos == Self::UNKNOWN);
62 table
63 }
64
65 pub fn add_file(&self, name: String, source: String) -> FileIdx {
67 let file = File {
68 name: name.into(),
69 source: source.into(),
70 };
71 let file_idx = self.files.push(file);
72 FileIdx(file_idx as u32)
73 }
74
75 fn get_file_data(&self, file: FileIdx) -> &File {
77 &self.files[file.0 as usize]
78 }
79
80 pub fn get_source(&self, file: FileIdx) -> &str {
81 &self.get_file_data(file).source
82 }
83
84 pub fn add_pos(&self, file: FileIdx, start: usize, end: usize) -> PosIdx {
86 let pos = PosData { file, start, end };
87 let pos_idx = self.indices.push(pos);
88 PosIdx(pos_idx as u32)
89 }
90
91 fn get_pos(&self, pos: PosIdx) -> &PosData {
92 &self.indices[pos.0 as usize]
93 }
94}
95
96pub struct GlobalPositionTable;
98
99static GPOS_TABLE: LazyLock<PositionTable> = LazyLock::new(PositionTable::new);
100
101impl GlobalPositionTable {
102 fn get_pos(pos: PosIdx) -> &'static PosData {
103 GPOS_TABLE.get_pos(pos)
104 }
105
106 fn get_file_data(file: FileIdx) -> &'static File {
107 GPOS_TABLE.get_file_data(file)
108 }
109
110 pub fn get_source(file: FileIdx) -> &'static str {
111 GPOS_TABLE.get_source(file)
112 }
113
114 pub fn add_file(name: String, source: String) -> FileIdx {
115 GPOS_TABLE.add_file(name, source)
116 }
117
118 pub fn add_pos(file: FileIdx, start: usize, end: usize) -> PosIdx {
119 GPOS_TABLE.add_pos(file, start, end)
120 }
121}
122
123#[derive(Clone, Copy, PartialEq, Eq, Debug)]
124pub struct GPosIdx(pub PosIdx);
126
127impl Default for GPosIdx {
128 fn default() -> Self {
129 Self::UNKNOWN
130 }
131}
132
133impl GPosIdx {
134 pub const UNKNOWN: GPosIdx = GPosIdx(PosIdx(0));
136
137 pub fn into_option(self) -> Option<Self> {
140 if self == Self::UNKNOWN {
141 None
142 } else {
143 Some(self)
144 }
145 }
146
147 fn get_lines(&self) -> (Vec<&str>, usize, usize) {
152 let pos_d = GlobalPositionTable::get_pos(self.0);
153 let file = &*GlobalPositionTable::get_file_data(pos_d.file).source;
154
155 let lines = file.split('\n').collect_vec();
156 let mut pos: usize = 0;
157 let mut linum: usize = 1;
158 let mut collect_lines = false;
159 let mut buf = Vec::new();
160
161 let mut out_line: usize = 0;
162 let mut out_idx: usize = 0;
163 for l in lines {
164 let next_pos = pos + l.len();
165 if pos_d.start >= pos && pos_d.start <= next_pos {
166 out_line = linum;
167 out_idx = pos;
168 collect_lines = true;
169 }
170 if collect_lines && pos_d.end >= pos {
171 buf.push(l)
172 }
173 if pos_d.end <= next_pos {
174 break;
175 }
176 pos = next_pos + 1;
177 linum += 1;
178 }
179 (buf, out_idx, out_line)
180 }
181
182 pub fn get_line_num(&self) -> (&str, (usize, usize)) {
186 let pos_data = GlobalPositionTable::get_pos(self.0);
187 let file_name = &GlobalPositionTable::get_file_data(pos_data.file).name;
188 let (buf, _, line_num) = self.get_lines();
189 let rng = (line_num, line_num + buf.len() - 1);
191 (file_name, rng)
192 }
193
194 pub fn format_raw<S: AsRef<str>>(&self, err_msg: S) -> String {
196 let pos_d = GlobalPositionTable::get_pos(self.0);
197
198 let (lines, pos, linum) = self.get_lines();
199 let mut buf = String::new();
200
201 let l = lines[0];
202 let linum_text = format!("{linum} ");
203 let linum_space: String = " ".repeat(linum_text.len());
204 let mark: String = "^".repeat(cmp::min(
205 pos_d.end - pos_d.start,
206 l.len() - (pos_d.start - pos),
207 ));
208 let space: String = " ".repeat(pos_d.start - pos);
209 writeln!(buf, "{linum_text}|{l}").unwrap();
210 write!(
211 buf,
212 "{}|{}{} {}",
213 linum_space,
214 space,
215 mark,
216 err_msg.as_ref()
217 )
218 .unwrap();
219 buf
220 }
221
222 pub fn format<S: AsRef<str>>(&self, err_msg: S) -> String {
224 let pos_d = GlobalPositionTable::get_pos(self.0);
225 let name = &*GlobalPositionTable::get_file_data(pos_d.file).name;
226
227 let mut buf = name.to_string();
228 writeln!(buf).unwrap();
229 write!(buf, "{}", self.format_raw(err_msg)).unwrap();
230 buf
231 }
232
233 pub fn get_location(&self) -> (&str, usize, usize) {
234 let pos_d = GlobalPositionTable::get_pos(self.0);
235 let name = &*GlobalPositionTable::get_file_data(pos_d.file).name;
236 (name, pos_d.start, pos_d.end)
237 }
238
239 pub fn show(&self) -> String {
241 let (lines, _, linum) = self.get_lines();
242 let l = lines[0];
243 let linum_text = format!("{linum} ");
244 format!("{linum_text}|{l}\n")
245 }
246}
247
248pub trait WithPos {
250 fn copy_span(&self) -> GPosIdx;
252}
253
254impl WithPos for GPosIdx {
255 fn copy_span(&self) -> GPosIdx {
256 *self
257 }
258}