1pub use super::{TrimmedLiteral, TrimmedLiteralDisplay};
2
3use crate::{CheckableChunk, CommentVariant, Range};
4
5use std::fmt;
6
7#[derive(Clone, Default, Debug, Hash, PartialEq, Eq)]
11pub struct LiteralSet {
12 literals: Vec<TrimmedLiteral>,
14 pub coverage: (usize, usize),
16 variant: CommentVariant,
18}
19
20impl LiteralSet {
21 pub fn from(literal: TrimmedLiteral) -> Self {
23 Self {
24 coverage: (literal.span().start.line, literal.span().end.line),
25 variant: literal.variant(),
26 literals: vec![literal],
27 }
28 }
29
30 pub fn add_adjacent(&mut self, literal: TrimmedLiteral) -> Result<(), TrimmedLiteral> {
35 if literal.variant().category() != self.variant.category() {
36 log::debug!(
37 "Adjacent literal is not the same comment variant: {:?} vs {:?}",
38 literal.variant().category(),
39 self.variant.category()
40 );
41 return Err(literal);
42 }
43 let previous_line = literal.span().end.line;
44 if previous_line == self.coverage.1 + 1 {
45 self.coverage.1 += 1;
46 self.literals.push(literal);
47 return Ok(());
48 }
49
50 let next_line = literal.span().start.line;
51 if next_line + 1 == self.coverage.0 {
52 self.literals.push(literal);
53 self.coverage.1 -= 1;
54 return Ok(());
55 }
56
57 Err(literal)
58 }
59
60 pub fn literals(&self) -> Vec<&TrimmedLiteral> {
62 self.literals.iter().by_ref().collect()
63 }
64
65 pub fn len(&self) -> usize {
67 self.literals.len()
68 }
69
70 pub fn is_empty(&self) -> bool {
71 self.literals.is_empty()
72 }
73
74 pub fn into_chunk(self) -> crate::CheckableChunk {
78 let n = self.len();
79 let mut source_mapping = indexmap::IndexMap::with_capacity(n);
80 let mut content = String::with_capacity(n * 120);
81 if n > 0 {
82 let mut cursor = 0usize;
84 let mut start; let mut end; let mut it = self.literals.iter();
88 let mut next = it.next();
89 while let Some(literal) = next {
90 start = cursor;
91 cursor += literal.len_in_chars();
92 end = cursor;
93
94 let span = literal.span();
95 let range = Range { start, end };
96
97 if literal.variant() != CommentVariant::MacroDocEqMacro {
101 if let Some(span_len) = span.one_line_len() {
102 assert_eq!(range.len(), span_len);
103 }
104 }
105 source_mapping.insert(range, span);
107 content.push_str(literal.as_str());
108 next = it.next();
110 if next.is_some() {
111 content.push('\n');
113 cursor += 1;
114 }
115 }
116 }
117 let variant = if let Some(literal) = self.literals.first() {
119 literal.variant()
120 } else {
121 crate::CommentVariant::Unknown
122 };
123 CheckableChunk::from_string(content, source_mapping, variant)
124 }
125}
126
127impl fmt::Display for LiteralSet {
128 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
129 let n = self.len();
130 if n > 0 {
131 for literal in self.literals.iter().take(n - 1) {
132 writeln!(formatter, "{}", literal.as_str())?;
133 }
134 if let Some(literal) = self.literals.last() {
135 write!(formatter, "{}", literal.as_str())?;
136 }
137 }
138 Ok(())
139 }
140}
141#[macro_export]
143macro_rules! chyrp_dbg {
144 ($first:literal $(, $( $line:literal ),+ )? $(,)? $(@ $prefix:literal)? ) => {
145 dbg!(concat!($first $( $(, "\n", $line )+ )?).len());
146 dbg!(concat!($first $( $(, "\n", $line )+ )?));
147 }
148}
149
150#[macro_export]
165macro_rules! chyrp_up {
166 ([ $( $line:literal ),+ $(,)? ] $(@ $prefix:literal)? ) => {
167 chyrp_up!( $( $line ),+ $(@ $prefix)? )
168 };
169 ($first:literal $(, $( $line:literal ),+ )? $(,)? $(@ $prefix:literal)? ) => {
170 concat!($( $prefix ,)? r##"#[doc=r#""##, $first $( $(, "\n", $line )+ )?, r##""#]"##, "\n", "struct ChyrpChyrp;")
171 };
172}
173
174#[macro_export]
189macro_rules! fluff_up {
190 ([ $( $line:literal ),+ $(,)?] $( @ $prefix:literal)?) => {
191 fluff_up!($( $line ),+ $(@ $prefix)?)
192 };
193 ($($line:literal ),+ $(,)? ) => {
194 fluff_up!($( $line ),+ @ "")
195 };
196 ($($line:literal ),+ $(,)? @ $prefix:literal ) => {
197 concat!("" $(, $prefix, "/// ", $line, "\n")+ , "struct Fluff;")
198 };
199}
200
201pub mod testhelper {
202 use super::*;
203 use crate::testcase::annotated_literals;
204
205 pub fn gen_literal_set(source: &str) -> LiteralSet {
206 let literals = dbg!(annotated_literals(dbg!(source)));
207
208 let mut iter = dbg!(literals).into_iter();
209 let literal = iter
210 .next()
211 .expect("Must have at least one item in laterals");
212 let mut cls = LiteralSet::from(literal);
213
214 for literal in iter {
215 assert!(cls.add_adjacent(literal).is_ok());
216 }
217 dbg!(cls)
218 }
219}
220
221#[cfg(test)]
222mod tests {
223 use super::*;
224
225 use super::testhelper::gen_literal_set;
226 use crate::util::load_span_from;
227 use crate::util::sub_chars;
228
229 #[test]
230 fn fluff_one() {
231 const RAW: &str = fluff_up!(["a"]);
232 const EXPECT: &str = r#"/// a
233struct Fluff;"#;
234 assert_eq!(RAW, EXPECT);
235 }
236
237 #[test]
238 fn fluff_multi() {
239 const RAW: &str = fluff_up!(["a", "b", "c"]);
240 const EXPECT: &str = r#"/// a
241/// b
242/// c
243struct Fluff;"#;
244 assert_eq!(RAW, EXPECT);
245 }
246
247 const EXMALIBU_RANGE_START: usize = 9;
249 const EXMALIBU_RANGE_END: usize = EXMALIBU_RANGE_START + 8;
250 const EXMALIBU_RANGE: Range = EXMALIBU_RANGE_START..EXMALIBU_RANGE_END;
251 const RAW: &str = r#"/// Another exmalibu verification pass.
252/// 🚤w🌴x🌋y🍈z🍉0
253/// ♫ Boats float, ♫♫ don't they? ♫
254struct Vikings;
255"#;
256
257 const EXMALIBU_CHUNK_STR: &str = r#" Another exmalibu verification pass.
258 🚤w🌴x🌋y🍈z🍉0
259 ♫ Boats float, ♫♫ don't they? ♫"#;
260
261 #[test]
262 fn combine_literals() {
263 let _ = env_logger::builder()
264 .is_test(true)
265 .filter(None, log::LevelFilter::Trace)
266 .try_init();
267
268 let cls = gen_literal_set(RAW);
269
270 assert_eq!(cls.len(), 3);
271 assert_eq!(cls.to_string(), EXMALIBU_CHUNK_STR.to_owned());
272 }
273
274 #[test]
275 fn coverage() {
276 let _ = env_logger::builder()
277 .is_test(true)
278 .filter(None, log::LevelFilter::Trace)
279 .try_init();
280
281 let literal_set = gen_literal_set(RAW);
282 let chunk: CheckableChunk = literal_set.into_chunk();
283 let map_range_to_span = chunk.find_spans(EXMALIBU_RANGE);
284 let (_range, _span) = map_range_to_span
285 .first()
286 .expect("Must be at least one literal");
287
288 let range_for_raw_str = Range {
289 start: EXMALIBU_RANGE_START,
290 end: EXMALIBU_RANGE_END,
291 };
292
293 assert_eq!("exmalibu", &EXMALIBU_CHUNK_STR[EXMALIBU_RANGE]);
295
296 assert_eq!(
298 &EXMALIBU_CHUNK_STR[EXMALIBU_RANGE],
299 &chunk.as_str()[range_for_raw_str.clone()]
300 );
301 }
302
303 macro_rules! test_raw {
304 ($test: ident, [ $($txt: literal),+ $(,)? ]; $range: expr, $expected: literal) => {
305 #[test]
306 fn $test() {
307 test_raw!([$($txt),+] ; $range, $expected);
308 }
309 };
310
311 ([$($txt:literal),+ $(,)?]; $range: expr, $expected: literal) => {
312 let _ = env_logger::builder()
313 .filter(None, log::LevelFilter::Trace)
314 .is_test(true)
315 .try_init();
316
317 let range: Range = $range;
318
319 const RAW: &str = fluff_up!($( $txt),+);
320 const START: usize = 3; let _end: usize = START $( + $txt.len())+;
322 let literal_set = gen_literal_set(dbg!(RAW));
323
324
325 let chunk: CheckableChunk = dbg!(literal_set.into_chunk());
326 let map_range_to_span = chunk.find_spans(range.clone());
327
328 let mut iter = dbg!(map_range_to_span).into_iter();
329 let (range, _span) = iter.next().expect("Must be at least one literal");
330
331 let range_for_raw_str = Range {
333 start: range.start + START,
334 end: range.end + START,
335 };
336
337 assert_eq!(&RAW[range_for_raw_str.clone()], &chunk.as_str()[range], "Testing range extract vs stringified chunk for integrity");
338 assert_eq!(&RAW[range_for_raw_str], $expected, "Testing range extract vs expected");
339 };
340 }
341
342 #[test]
343 fn first_line_extract_0() {
344 test_raw!(["livelyness", "yyy"] ; 2..6, "ivel");
345 }
346
347 #[test]
348 fn first_line_extract_1() {
349 test_raw!(["+ 12 + x0"] ; 9..10, "0");
350 }
351
352 #[test]
353 fn literal_set_into_chunk() {
354 let _ = env_logger::builder()
355 .filter(None, log::LevelFilter::Trace)
356 .is_test(true)
357 .try_init();
358
359 let literal_set = dbg!(gen_literal_set(RAW));
360
361 let chunk = dbg!(literal_set.clone().into_chunk());
362 let it = literal_set.literals();
363
364 for (range, span, s) in itertools::cons_tuples(chunk.iter().zip(it)) {
365 if range.len() == 0 {
366 continue;
367 }
368 assert_eq!(
369 load_span_from(RAW.as_bytes(), span.clone()).expect("Span extraction must work"),
370 sub_chars(chunk.as_str(), range.clone())
371 );
372
373 let r: Range = span.to_content_range(&chunk).expect("Should work");
374 assert_eq!(
376 sub_chars(chunk.as_str(), range.clone()),
377 s.as_str().to_owned()
378 );
379 assert_eq!(&r, range);
380 }
381 }
382}