1use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
12pub struct SourcePiece {
13 pub at: u32,
15 pub file: String,
17 pub first_line: u32,
19}
20
21#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
23pub struct SourceMap {
24 pub pieces: Vec<SourcePiece>,
26}
27
28impl SourceMap {
29 pub fn whole(file: impl Into<String>) -> Self {
31 Self {
32 pieces: vec![SourcePiece {
33 at: 1,
34 file: file.into(),
35 first_line: 1,
36 }],
37 }
38 }
39
40 pub fn is_empty(&self) -> bool {
42 self.pieces.is_empty()
43 }
44
45 pub fn append(&mut self, text: &mut String, body: &str, file: &str, first_line: u32) {
49 if !text.is_empty() {
50 text.push('\n');
51 }
52 let at = line_at(text, text.len());
53 text.push_str(body);
54 self.pieces.push(SourcePiece {
55 at,
56 file: file.to_owned(),
57 first_line,
58 });
59 }
60
61 pub fn append_mapped(&mut self, text: &mut String, body: &str, map: &SourceMap) {
64 if !text.is_empty() {
65 text.push('\n');
66 }
67 let offset = line_at(text, text.len()) - 1;
68 text.push_str(body);
69 self.pieces.extend(map.pieces.iter().map(|p| SourcePiece {
70 at: p.at + offset,
71 ..p.clone()
72 }));
73 }
74
75 pub fn name_unnamed(&mut self, file: &str) {
77 for piece in self.pieces.iter_mut().filter(|p| p.file.is_empty()) {
78 piece.file = file.to_owned();
79 }
80 }
81
82 pub fn place_in(&mut self, text: &str, written: &str) {
91 let lines: Vec<&str> = text.split('\n').collect();
92 let ends: Vec<u32> = self
93 .pieces
94 .iter()
95 .skip(1)
96 .map(|p| p.at - 1)
97 .chain(std::iter::once(lines.len() as u32))
98 .collect();
99 for (piece, end) in self.pieces.iter_mut().zip(ends) {
100 let from = (piece.at as usize).saturating_sub(1);
101 let to = (end as usize).min(lines.len());
102 if from >= to {
103 continue;
104 }
105 let body = lines[from..to].join("\n");
106 if body.trim().is_empty() {
107 continue;
108 }
109 if let Some((found, _)) = written
110 .match_indices(body.as_str())
111 .find(|(i, _)| written[..*i].ends_with('>'))
112 {
113 piece.first_line = line_at(written, found);
114 }
115 }
116 }
117
118 pub fn locate(&self, line: u32) -> Option<(&str, u32)> {
121 let piece = self.pieces.iter().rev().find(|p| p.at <= line)?;
122 Some((piece.file.as_str(), piece.first_line + (line - piece.at)))
123 }
124}
125
126pub fn line_at(text: &str, offset: usize) -> u32 {
128 let end = offset.min(text.len());
129 text.as_bytes()[..end]
130 .iter()
131 .filter(|b| **b == b'\n')
132 .count() as u32
133 + 1
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139
140 #[test]
141 fn appended_pieces_locate_back_to_their_files() {
142 let mut text = String::new();
143 let mut map = SourceMap::default();
144 map.append(&mut text, "a1\na2", "main.lmn", 7);
145 map.append(&mut text, "b1\nb2\nb3", "src/b.cdl", 1);
146 assert_eq!(map.locate(1), Some(("main.lmn", 7)));
147 assert_eq!(map.locate(2), Some(("main.lmn", 8)));
148 assert_eq!(map.locate(3), Some(("src/b.cdl", 1)));
149 assert_eq!(map.locate(5), Some(("src/b.cdl", 3)));
150 assert_eq!(map.locate(0), None);
151 }
152
153 #[test]
154 fn pieces_are_placed_at_their_line_in_the_written_file() {
155 let written = "<root>\n<include src=\"a.lmn\"/>\n<script>\nlet x = 1;\n</script>\n</root>";
156 let mut text = String::new();
158 let mut map = SourceMap::default();
159 map.append(&mut text, "\nlet x = 1;\n", "", 5);
160 map.name_unnamed("main.lmn");
161 map.place_in(&text, written);
162 assert_eq!(map.locate(2), Some(("main.lmn", 4)));
163 }
164
165 #[test]
166 fn a_mapped_append_shifts_every_piece() {
167 let mut inner_text = String::new();
168 let mut inner = SourceMap::default();
169 inner.append(&mut inner_text, "x", "a.lmn", 3);
170 inner.append(&mut inner_text, "y", "b.lmn", 9);
171
172 let mut text = String::new();
173 let mut map = SourceMap::default();
174 map.append(&mut text, "one\ntwo", "first.cdl", 1);
175 map.append_mapped(&mut text, &inner_text, &inner);
176 assert_eq!(text, "one\ntwo\nx\ny");
177 assert_eq!(map.locate(3), Some(("a.lmn", 3)));
178 assert_eq!(map.locate(4), Some(("b.lmn", 9)));
179 }
180}