1use super::TrimmedLiteral;
7use crate::util;
8use crate::Range;
9pub use proc_macro2::LineColumn;
10
11use std::hash::{Hash, Hasher};
12
13use crate::errors::*;
14
15use std::convert::TryFrom;
16
17use super::CheckableChunk;
18
19#[derive(Clone, Debug, Copy, PartialEq, Eq)]
24pub struct Span {
25 pub start: LineColumn,
28 pub end: LineColumn,
30}
31
32impl Hash for Span {
33 fn hash<H: Hasher>(&self, state: &mut H) {
34 self.start.line.hash(state);
35 self.start.column.hash(state);
36 self.end.line.hash(state);
37 self.end.column.hash(state);
38 }
39}
40
41impl Span {
42 pub fn relative_to<X: Into<Span>>(&self, scope: X) -> Result<Range> {
47 let scope: Span = scope.into();
48 let scope: Range = scope.try_into()?;
49 let me: Range = self.try_into()?;
50 if scope.start > me.start {
51 return Err(Error::Span(format!(
52 "start of {me:?} is not inside of {scope:?}",
53 )));
54 }
55 if scope.end < me.end {
56 return Err(Error::Span(format!(
57 "end of {me:?} is not inside of {scope:?}",
58 )));
59 }
60 let offset = me.start - scope.start;
61 let length = me.end - me.start;
62 let range = Range {
63 start: offset,
64 end: offset + length,
65 };
66 Ok(range)
67 }
68
69 pub fn covers_line(&self, line: usize) -> bool {
71 self.end.line <= line && line >= self.start.line
72 }
73
74 pub fn one_line_len(&self) -> Option<usize> {
77 if self.start.line == self.end.line {
78 Some(self.end.column + 1 - self.start.column)
79 } else {
80 None
81 }
82 }
83
84 pub fn is_multiline(&self) -> bool {
86 self.start.line != self.end.line
87 }
88
89 pub fn to_content_range(&self, chunk: &CheckableChunk) -> Result<Range> {
94 if chunk.fragment_count() == 0 {
95 return Err(Error::Span("Chunk contains 0 fragments".to_string()));
96 }
97 for (fragment_range, fragment_span) in chunk
98 .iter()
99 .filter(|(fragment_range, fragment_span)| {
101 log::trace!(
102 "extracting sub from {self:?} ::: {fragment_range:?} -> {fragment_span:?}",
103 );
104 fragment_span.start.line <= self.start.line
105 && self.end.line <= fragment_span.end.line
106 })
107 {
108 match extract_sub_range_from_span(
109 chunk.as_str(),
110 *fragment_span,
111 fragment_range.clone(),
112 *self,
113 ) {
114 Ok(fragment_sub_range) => return Ok(fragment_sub_range),
115 Err(_e) => continue,
116 }
117 }
118 Err(Error::Span(
119 "The chunk internal map from range to span did not contain an overlapping entry"
120 .to_string(),
121 ))
122 }
123}
124
125use std::convert::{From, TryInto};
126
127impl From<proc_macro2::Span> for Span {
128 fn from(original: proc_macro2::Span) -> Self {
129 Self {
130 start: original.start(),
131 end: original.end(),
132 }
133 }
134}
135
136impl TryInto<Range> for Span {
137 type Error = Error;
138 fn try_into(self) -> Result<Range> {
139 (&self).try_into()
140 }
141}
142
143impl TryInto<Range> for &Span {
144 type Error = Error;
145 fn try_into(self) -> Result<Range> {
146 if self.start.line == self.end.line {
147 Ok(Range {
148 start: self.start.column,
149 end: self.end.column + 1,
150 })
151 } else {
152 Err(Error::Span(format!(
153 "Start and end are not in the same line {} vs {}",
154 self.start.line, self.end.line
155 )))
156 }
157 }
158}
159
160impl TryFrom<(usize, Range)> for Span {
161 type Error = Error;
162 fn try_from(original: (usize, Range)) -> Result<Self> {
163 if original.1.start < original.1.end {
164 Ok(Self {
165 start: LineColumn {
166 line: original.0,
167 column: original.1.start,
168 },
169 end: LineColumn {
170 line: original.0,
171 column: original.1.end - 1,
172 },
173 })
174 } else {
175 Err(Error::Span(format!(
176 "range must be valid to be converted to a Span {}..{}",
177 original.1.start, original.1.end
178 )))
179 }
180 }
181}
182
183impl TryFrom<(usize, std::ops::RangeInclusive<usize>)> for Span {
184 type Error = Error;
185 fn try_from(original: (usize, std::ops::RangeInclusive<usize>)) -> Result<Self> {
186 if original.1.start() <= original.1.end() {
187 Ok(Self {
188 start: LineColumn {
189 line: original.0,
190 column: *original.1.start(),
191 },
192 end: LineColumn {
193 line: original.0,
194 column: *original.1.end(),
195 },
196 })
197 } else {
198 Err(Error::Span(format!(
199 "range must be valid to be converted to a Span {}..{}",
200 original.1.start(),
201 original.1.end()
202 )))
203 }
204 }
205}
206
207impl From<&TrimmedLiteral> for Span {
208 fn from(literal: &TrimmedLiteral) -> Self {
209 literal.span()
210 }
211}
212
213fn extract_sub_range_from_span(
222 full_content: &str,
223 span: Span,
224 range: Range,
225 sub_span: Span,
226) -> Result<Range> {
227 if let Some(span_len) = span.one_line_len() {
228 debug_assert_eq!(range.len(), span_len);
229 }
230
231 let s = util::sub_chars(full_content, range.clone());
233 let offset = range.start;
234 let mut start = 0usize;
236 let mut end = 0usize;
237 for (_c, _byte_offset, idx, LineColumn { line, column }) in
238 util::iter_with_line_column_from(s.as_str(), span.start)
239 {
240 if line < sub_span.start.line {
241 continue;
242 }
243 if line > sub_span.end.line {
244 return Err(Error::Span(format!(
245 "range must be valid to be converted to a Span {}..{}",
246 range.start, range.end
247 )));
248 }
249
250 if line == sub_span.start.line && column < sub_span.start.column {
251 continue;
252 }
253
254 if line >= sub_span.end.line && column > sub_span.end.column {
255 return Err(Error::Span(
256 "Moved beyond anticipated column and last line".to_string(),
257 ));
258 }
259 if line == sub_span.start.line && column == sub_span.start.column {
260 start = idx;
261 }
263 end = idx;
264 if line == sub_span.end.line && column == sub_span.end.column {
266 break;
267 }
268
269 if line > sub_span.end.line {
270 return Err(Error::Span("Moved beyond anticipated line".to_string()));
271 }
272
273 if line >= sub_span.end.line && column > sub_span.end.column {
274 return Err(Error::Span(
275 "Moved beyond anticipated column and last line".to_string(),
276 ));
277 }
278 }
279
280 let sub_range = (offset + start)..(offset + end + 1);
281 assert!(sub_range.len() <= range.len());
282
283 if let Some(sub_span_len) = sub_span.one_line_len() {
284 debug_assert_eq!(sub_range.len(), sub_span_len);
285 }
286
287 Ok(sub_range)
288}
289
290#[cfg(test)]
291mod tests {
292 use super::*;
293
294 use crate::literalset::testhelper::gen_literal_set;
295 use crate::util::load_span_from;
296 use crate::{chyrp_dbg, chyrp_up, fluff_up};
297 use crate::{LineColumn, Range, Span};
298
299 #[test]
300 fn span_to_range_singleline() {
301 let _ = env_logger::builder()
302 .is_test(true)
303 .filter(None, log::LevelFilter::Trace)
304 .try_init();
305
306 const CONTENT: &str = fluff_up!("Itsyou!!", " ", "Game-Over!!", " ");
307 let set = gen_literal_set(CONTENT);
308 let chunk = dbg!(CheckableChunk::from_literalset(set));
309
310 const TRIPPLE_SLASH_PLUS_SPACE: usize = 4;
312
313 const INPUTS: &[Span] = &[
315 Span {
316 start: LineColumn {
317 line: 1usize,
318 column: 3usize + TRIPPLE_SLASH_PLUS_SPACE,
319 },
320 end: LineColumn {
321 line: 1usize,
322 column: 7usize + TRIPPLE_SLASH_PLUS_SPACE,
323 },
324 },
325 Span {
326 start: LineColumn {
327 line: 3usize,
328 column: 0usize + TRIPPLE_SLASH_PLUS_SPACE,
329 },
330 end: LineColumn {
331 line: 3usize,
332 column: 8usize + TRIPPLE_SLASH_PLUS_SPACE,
333 },
334 },
335 ];
336
337 const EXPECTED_RANGE: &[Range] = &[4..9, 14..23];
341
342 const FRAGMENT_STR: &[&'static str] = &["you!!", "Game-Over"];
345
346 for (input, expected, fragment) in itertools::cons_tuples(
347 INPUTS
348 .iter()
349 .zip(EXPECTED_RANGE.iter())
350 .zip(FRAGMENT_STR.iter()),
351 ) {
352 log::trace!(
353 ">>>>>>>>>>>>>>>>\ninput: {input:?}\nexpected: {expected:?}\nfragment:>{fragment}<",
354 );
355 let range = input
356 .to_content_range(&chunk)
357 .expect("Inputs are sane, conversion must work.");
358 assert_eq!(range, *expected);
359 assert_eq!(
361 load_span_from(CONTENT.as_bytes(), *input).unwrap(),
362 fragment.to_owned()
363 );
364 assert_eq!(&(&chunk.as_str()[range]), fragment);
365 }
366 }
367
368 #[test]
369 fn span_to_range_multiline() {
370 let _ = env_logger::builder()
371 .is_test(true)
372 .filter(None, log::LevelFilter::Trace)
373 .try_init();
374
375 chyrp_dbg!("Xy fff?? Not.., you again!", "", "AlphaOmega", "");
376 const CONTENT: &str = chyrp_up!("Xy fff?? Not.., you again!", "", "AlphaOmega", "");
377 let set = gen_literal_set(dbg!(CONTENT));
378 let chunk = dbg!(CheckableChunk::from_literalset(set));
379
380 const HASH_BRACKET_DOC_EQ_RAW_HASH_QUOTE: usize = 9;
382 const INPUTS: &[Span] = &[
386 Span {
388 start: LineColumn {
389 line: 1usize,
390 column: 0usize + HASH_BRACKET_DOC_EQ_RAW_HASH_QUOTE,
391 },
392 end: LineColumn {
393 line: 3usize,
394 column: 10usize,
395 },
396 },
397 Span {
399 start: LineColumn {
400 line: 1usize,
401 column: 3usize + HASH_BRACKET_DOC_EQ_RAW_HASH_QUOTE,
402 },
403 end: LineColumn {
404 line: 1usize,
405 column: 7usize + HASH_BRACKET_DOC_EQ_RAW_HASH_QUOTE,
406 },
407 },
408 Span {
409 start: LineColumn {
410 line: 3usize,
411 column: 0usize,
412 },
413 end: LineColumn {
414 line: 3usize,
415 column: 4usize,
416 },
417 },
418 ];
419
420 const EXPECTED_RANGE: &[Range] = &[0..(26 + 1 + 0 + 1 + 10 + 1), 3..8, 28..33];
421
422 const FRAGMENT_STR: &[&'static str] = &[
423 r#"Xy fff?? Not.., you again!
424
425AlphaOmega
426"#,
427 "fff??",
428 "Alpha",
429 ];
430
431 for (input, expected, fragment) in itertools::cons_tuples(
432 INPUTS
433 .iter()
434 .zip(EXPECTED_RANGE.iter())
435 .zip(FRAGMENT_STR.iter()),
436 ) {
437 log::trace!(
438 ">>>>>>>>>>>>>>>>\ninput: {input:?}\nexpected: {expected:?}\nfragment:>{fragment}<",
439 );
440
441 let range = dbg!(input)
442 .to_content_range(&chunk)
443 .expect("Inputs are sane, conversion must work. qed");
444 assert_eq!(range, *expected);
445
446 assert_eq!(
447 load_span_from(CONTENT.as_bytes(), *input).unwrap(),
448 fragment.to_owned()
449 );
450 assert_eq!(&(&chunk.as_str()[range]), fragment);
451 }
452 }
453
454 #[test]
455 fn extraction_fluff() {
456 const CHUNK_S: &str = r#" one
457 two
458 three"#;
459 const FRAGMENT_SPAN: Span = Span {
460 start: LineColumn { line: 1, column: 3 },
461 end: LineColumn { line: 1, column: 6 },
462 };
463 const FRAGMENT_RANGE: Range = 0..4;
464
465 const FRAGMENT_SUB_SPAN: Span = Span {
466 start: LineColumn { line: 1, column: 5 },
467 end: LineColumn { line: 1, column: 6 },
468 };
469 let range = dbg!(extract_sub_range_from_span(
470 CHUNK_S,
471 FRAGMENT_SPAN,
472 FRAGMENT_RANGE,
473 FRAGMENT_SUB_SPAN,
474 )
475 .expect("Must be able to extract trivial sub span"));
476 assert_eq!(&CHUNK_S[dbg!(range.clone())], "ne");
477 assert_eq!(range, 2..4);
478 }
479
480 #[test]
481 fn extraction_chyrp() {
482 const CHUNK_S: &str = r#"one
483two
484three"#;
485 const FRAGMENT_SPAN: Span = Span {
486 start: LineColumn {
487 line: 1,
488 column: 9 + 2,
489 },
490 end: LineColumn { line: 3, column: 5 },
491 };
492 const FRAGMENT_RANGE: Range = 0..(3 + 1 + 3 + 5);
493
494 {
495 const FRAGMENT_SUB_SPAN: Span = Span {
496 start: LineColumn {
497 line: 1,
498 column: 9 + 2 + 1,
499 },
500 end: LineColumn {
501 line: 1,
502 column: 9 + 2 + 1 + 1,
503 },
504 };
505 let range = dbg!(extract_sub_range_from_span(
506 CHUNK_S,
507 FRAGMENT_SPAN,
508 FRAGMENT_RANGE,
509 FRAGMENT_SUB_SPAN,
510 )
511 .expect("Must be able to extract trivial sub span"));
512 assert_eq!(&CHUNK_S[dbg!(range.clone())], "ne");
513 assert_eq!(range, 1..3);
514 }
515 {
516 const FRAGMENT_SUB_SPAN: Span = Span {
517 start: LineColumn { line: 2, column: 1 },
518 end: LineColumn { line: 2, column: 2 },
519 };
520 let range = dbg!(extract_sub_range_from_span(
521 CHUNK_S,
522 FRAGMENT_SPAN,
523 FRAGMENT_RANGE,
524 FRAGMENT_SUB_SPAN,
525 )
526 .expect("Must be able to extract trivial sub span"));
527 assert_eq!(&CHUNK_S[dbg!(range.clone())], "wo");
528 assert_eq!(range, 5..7);
529 }
530 }
531}