1use itertools::Itertools;
4use std::{cmp, fmt::Write, mem, sync};
5
6#[derive(Clone, Copy, PartialEq, Eq, Debug)]
7pub struct PosIdx(u32);
10
11#[derive(Clone, Copy, PartialEq, Eq)]
12pub struct FileIdx(u32);
15
16struct File {
18 name: String,
20 source: String,
22}
23
24struct PosData {
25 file: FileIdx,
28 start: usize,
30 end: usize,
32}
33
34pub struct PositionTable {
36 files: Vec<File>,
38 indices: Vec<PosData>,
40}
41
42impl Default for PositionTable {
43 fn default() -> Self {
44 Self::new()
45 }
46}
47
48impl PositionTable {
49 pub const UNKNOWN: PosIdx = PosIdx(0);
51
52 pub fn new() -> Self {
54 let mut table = PositionTable {
55 files: Vec::new(),
56 indices: Vec::new(),
57 };
58 table.add_file("unknown".to_string(), "".to_string());
59 let pos = table.add_pos(FileIdx(0), 0, 0);
60 debug_assert!(pos == Self::UNKNOWN);
61 table
62 }
63
64 pub fn add_file(&mut self, name: String, source: String) -> FileIdx {
66 let file = File { name, source };
67 let file_idx = self.files.len();
68 self.files.push(file);
69 FileIdx(file_idx as u32)
70 }
71
72 fn get_file_data(&self, file: FileIdx) -> &File {
74 &self.files[file.0 as usize]
75 }
76
77 pub fn get_source(&self, file: FileIdx) -> &str {
78 &self.get_file_data(file).source
79 }
80
81 pub fn add_pos(
83 &mut self,
84 file: FileIdx,
85 start: usize,
86 end: usize,
87 ) -> PosIdx {
88 let pos = PosData { file, start, end };
89 let pos_idx = self.indices.len();
90 self.indices.push(pos);
91 PosIdx(pos_idx as u32)
92 }
93
94 fn get_pos(&self, pos: PosIdx) -> &PosData {
95 &self.indices[pos.0 as usize]
96 }
97}
98
99pub struct GlobalPositionTable;
101
102impl GlobalPositionTable {
103 pub fn as_mut() -> &'static mut PositionTable {
105 static mut SINGLETON: mem::MaybeUninit<PositionTable> =
106 mem::MaybeUninit::uninit();
107 static ONCE: sync::Once = sync::Once::new();
108
109 unsafe {
113 ONCE.call_once(|| {
114 SINGLETON.write(PositionTable::new());
115 assert!(PositionTable::UNKNOWN == GPosIdx::UNKNOWN.0)
116 });
117 SINGLETON.assume_init_mut()
118 }
119 }
120
121 pub fn as_ref() -> &'static PositionTable {
123 Self::as_mut()
124 }
125}
126
127#[derive(Clone, Copy, PartialEq, Eq, Debug)]
128pub struct GPosIdx(pub PosIdx);
130
131impl Default for GPosIdx {
132 fn default() -> Self {
133 Self::UNKNOWN
134 }
135}
136
137impl GPosIdx {
138 pub const UNKNOWN: GPosIdx = GPosIdx(PosIdx(0));
140
141 pub fn into_option(self) -> Option<Self> {
144 if self == Self::UNKNOWN {
145 None
146 } else {
147 Some(self)
148 }
149 }
150
151 fn get_lines(&self) -> (Vec<&str>, usize, usize) {
156 let table = GlobalPositionTable::as_ref();
157 let pos_d = table.get_pos(self.0);
158 let file = &table.get_file_data(pos_d.file).source;
159
160 let lines = file.split('\n').collect_vec();
161 let mut pos: usize = 0;
162 let mut linum: usize = 1;
163 let mut collect_lines = false;
164 let mut buf = Vec::new();
165
166 let mut out_line: usize = 0;
167 let mut out_idx: usize = 0;
168 for l in lines {
169 let next_pos = pos + l.len();
170 if pos_d.start >= pos && pos_d.start <= next_pos {
171 out_line = linum;
172 out_idx = pos;
173 collect_lines = true;
174 }
175 if collect_lines && pos_d.end >= pos {
176 buf.push(l)
177 }
178 if pos_d.end <= next_pos {
179 break;
180 }
181 pos = next_pos + 1;
182 linum += 1;
183 }
184 (buf, out_idx, out_line)
185 }
186
187 pub fn format<S: AsRef<str>>(&self, err_msg: S) -> String {
189 let table = GlobalPositionTable::as_ref();
190 let pos_d = table.get_pos(self.0);
191 let name = &table.get_file_data(pos_d.file).name;
192
193 let (lines, pos, linum) = self.get_lines();
194 let mut buf = name.to_string();
195
196 let l = lines[0];
197 let linum_text = format!("{} ", linum);
198 let linum_space: String = " ".repeat(linum_text.len());
199 let mark: String = "^".repeat(cmp::min(
200 pos_d.end - pos_d.start,
201 l.len() - (pos_d.start - pos),
202 ));
203 let space: String = " ".repeat(pos_d.start - pos);
204 writeln!(buf).unwrap();
205 writeln!(buf, "{}|{}", linum_text, l).unwrap();
206 write!(
207 buf,
208 "{}|{}{} {}",
209 linum_space,
210 space,
211 mark,
212 err_msg.as_ref()
213 )
214 .unwrap();
215 buf
216 }
217
218 pub fn get_location(&self) -> (&str, usize, usize) {
219 let table = GlobalPositionTable::as_ref();
220 let pos_d = table.get_pos(self.0);
221 let name = &table.get_file_data(pos_d.file).name;
222 (name, pos_d.start, pos_d.end)
223 }
224
225 pub fn show(&self) -> String {
227 let (lines, _, linum) = self.get_lines();
228 let l = lines[0];
229 let linum_text = format!("{} ", linum);
230 format!("{}|{}\n", linum_text, l)
231 }
232}
233
234pub trait WithPos {
236 fn copy_span(&self) -> GPosIdx;
238}