1#![cfg(feature = "post_process")]
2
3use std::borrow::Cow;
4use std::{cmp, slice};
5
6use crate::Error;
7
8const BLANK_START: &[&[u8]] = &[b"lank_", b"!", b"("];
9const BLANK_END: &[&[u8]] = &[b";"];
10const COMMENT_START: &[&[u8]] = &[b"omment_", b"!", b"("];
11const COMMENT_END: &[&[u8]] = &[b")", b";"];
12const COMMENT_END2: &[&[u8]] = &[b";"];
13const DOC_BLOCK_START: &[&[u8]] = &[b"[", b"doc", b"="];
14const DOC_BLOCK_END: &[&[u8]] = &[b"]"];
15
16const EMPTY_COMMENT: &str = "//";
17const COMMENT: &str = "// ";
18const DOC_COMMENT: &str = "///";
19const LF_STR: &str = "\n";
20const CRLF_STR: &str = "\r\n";
21
22const CR: u8 = b'\r';
23const LF: u8 = b'\n';
24
25const MIN_BUFF_SIZE: usize = 128;
26
27struct CopyingCursor<'a> {
42 start_idx: usize,
43 curr_idx: usize,
44 curr: u8,
45
46 iter: slice::Iter<'a, u8>,
49 source: &'a str,
50 buffer: String,
51}
52
53impl<'a> CopyingCursor<'a> {
54 fn new(source: &'a str) -> Option<Self> {
55 let buffer = String::with_capacity(cmp::max(source.len() * 2, MIN_BUFF_SIZE));
57 let mut iter = source.as_bytes().iter();
58
59 iter.next().map(|&ch| Self {
60 start_idx: 0,
61 curr_idx: 0,
62 curr: ch,
63 iter,
64 source,
65 buffer,
66 })
67 }
68
69 #[inline]
70 fn next(&mut self) -> Option<u8> {
71 self.iter.next().map(|&ch| {
72 self.curr_idx += 1;
73 self.curr = ch;
74 ch
75 })
76 }
77
78 #[inline]
79 fn copy_to_marker(&mut self, marker: usize, new_start_idx: usize) {
80 if marker > self.start_idx {
81 self.buffer.push_str(&self.source[self.start_idx..marker]);
83 }
84 self.start_idx = new_start_idx;
85 }
86
87 fn into_buffer(mut self) -> Cow<'a, str> {
88 if self.start_idx > 0 {
90 self.copy_to_marker(self.curr_idx + 1, self.curr_idx + 1);
92
93 self.buffer.shrink_to_fit();
94 Cow::Owned(self.buffer)
95 } else {
97 Cow::Borrowed(self.source)
98 }
99 }
100
101 fn skip_block_comment(&mut self) {
102 enum State {
103 InComment,
104 MaybeStarting,
105 MaybeEnding,
106 }
107
108 let mut nest_level = 1;
109 let mut state = State::InComment;
110
111 while let Some(ch) = self.next() {
112 match (ch, state) {
113 (b'*', State::InComment) => {
114 state = State::MaybeEnding;
115 }
116 (b'/', State::MaybeEnding) => {
117 nest_level -= 1;
118 if nest_level == 0 {
119 break;
120 }
121 state = State::InComment;
122 }
123 (b'*', State::MaybeStarting) => {
124 nest_level += 1;
125 state = State::InComment;
126 }
127 (b'/', State::InComment) => {
128 state = State::MaybeStarting;
129 }
130 (_, _) => {
131 state = State::InComment;
132 }
133 }
134 }
135 }
136
137 fn try_skip_comment(&mut self) -> bool {
138 match self.next() {
139 Some(b'/') => {
141 while let Some(ch) = self.next() {
142 if ch == b'\n' {
143 break;
144 }
145 }
146
147 true
148 }
149 Some(b'*') => {
151 self.skip_block_comment();
152 true
153 }
154 _ => false,
156 }
157 }
158
159 fn skip_string(&mut self) {
160 let mut in_escape = false;
161
162 while let Some(ch) = self.next() {
163 match ch {
164 b'"' if !in_escape => break,
165 b'\\' if !in_escape => in_escape = true,
166 _ if in_escape => in_escape = false,
167 _ => {}
168 }
169 }
170 }
171
172 fn try_skip_raw_string(&mut self) -> bool {
173 let pads = match self.next() {
175 Some(b'#') => {
176 let mut pads = 1;
177
178 while let Some(ch) = self.next() {
179 match ch {
180 b'#' => {
181 pads += 1;
182 }
183 b'"' => break,
184 _ => return false,
186 }
187 }
188
189 pads
190 }
191 Some(b'"') => 0,
192 _ => return false,
193 };
194
195 #[derive(Clone, Copy)]
196 enum State {
197 InRawComment,
198 MaybeEndingComment(i32),
199 }
200
201 let mut state = State::InRawComment;
202
203 while let Some(ch) = self.next() {
206 match (ch, state) {
207 (b'"', State::InRawComment) if pads == 0 => break,
208 (b'"', State::InRawComment) => state = State::MaybeEndingComment(0),
209 (b'#', State::MaybeEndingComment(pads_seen)) => {
210 let pads_seen = pads_seen + 1;
211 if pads_seen == pads {
212 break;
213 }
214 state = State::MaybeEndingComment(pads_seen);
215 }
216 (_, _) => {
217 state = State::InRawComment;
218 }
219 }
220 }
221
222 true
223 }
224
225 #[inline]
226 fn skip_blank_param(&mut self) -> Result<(), Error> {
227 while let Some(ch) = self.next() {
228 if ch == b')' {
229 return Ok(());
230 }
231 }
232
233 Err(Error::BadSourceCode("Unexpected end of input".to_string()))
235 }
236
237 fn try_skip_string(&mut self) -> Result<Option<u8>, Error> {
238 while let Some(ch) = self.next() {
239 if Self::is_whitespace(ch) {
240 continue;
241 }
242
243 return match ch {
244 b'"' => {
246 self.skip_string();
247 Ok(None)
248 }
249 b'r' => {
251 if self.try_skip_raw_string() {
252 Ok(None)
253 } else {
254 Err(Error::BadSourceCode("Bad raw string".to_string()))
255 }
256 }
257 ch => Ok(Some(ch)),
259 };
260 }
261
262 Err(Error::BadSourceCode("Unexpected end of input".to_string()))
264 }
265
266 #[inline]
280 fn is_whitespace(ch: u8) -> bool {
281 matches!(ch, b' ' | b'\n' | b'\r' | b'\t' | b'\x0b' | b'\x0c')
282 }
283
284 fn try_ws_matches(&mut self, slices: &[&[u8]], allow_whitespace_first: bool) -> bool {
285 let mut allow_whitespace = allow_whitespace_first;
286
287 'top: for &sl in slices {
288 let first_ch = sl[0];
290
291 while let Some(ch) = self.next() {
292 if ch == first_ch {
294 let remainder = &sl[1..];
296
297 if !remainder.is_empty() && !self.try_match(remainder) {
298 return false;
299 }
300 allow_whitespace = true;
301 continue 'top;
302 } else if allow_whitespace && Self::is_whitespace(ch) {
303 } else {
305 return false;
306 }
307 }
308
309 return false;
311 }
312
313 true
315 }
316
317 fn try_match(&mut self, sl: &[u8]) -> bool {
318 let iter = sl.iter();
319
320 for &ch in iter {
321 if self.next().is_none() {
322 return false;
325 }
326
327 if self.curr != ch {
328 return false;
329 }
330 }
331
332 true
334 }
335
336 #[inline]
337 fn detect_line_ending(&mut self) -> Option<&'static str> {
338 match self.next() {
339 Some(CR) => match self.next() {
340 Some(LF) => Some(CRLF_STR),
341 _ => None,
342 },
343 Some(LF) => Some(LF_STR),
344 _ => None,
345 }
346 }
347
348 #[inline]
349 fn push_spaces(spaces: usize, buffer: &mut String) {
350 for _ in 0..spaces {
351 buffer.push(' ');
352 }
353 }
354
355 fn process_blanks(
356 _spaces: usize,
357 buffer: &mut String,
358 num: &str,
359 ending: &str,
360 ) -> Result<(), Error> {
361 if num.is_empty() {
363 buffer.push_str(ending);
364 } else {
366 let num: syn::LitInt = syn::parse_str(num)?;
367 let blanks: u32 = num.base10_parse()?;
368
369 for _ in 0..blanks {
370 buffer.push_str(ending);
371 }
372 }
373
374 Ok(())
375 }
376
377 fn process_comments(
378 spaces: usize,
379 buffer: &mut String,
380 s: &str,
381 ending: &str,
382 ) -> Result<(), Error> {
383 if s.is_empty() {
385 Self::push_spaces(spaces, buffer);
386 buffer.push_str(EMPTY_COMMENT);
387 buffer.push_str(ending);
388 } else {
390 let s: syn::LitStr = syn::parse_str(s)?;
391 let comment = s.value();
392
393 if comment.is_empty() {
395 Self::push_spaces(spaces, buffer);
396 buffer.push_str(EMPTY_COMMENT);
397 buffer.push_str(ending);
398 } else {
399 for line in comment.lines() {
400 Self::push_spaces(spaces, buffer);
401
402 if line.is_empty() {
403 buffer.push_str(EMPTY_COMMENT);
404 } else {
405 buffer.push_str(COMMENT);
406 buffer.push_str(line);
407 }
408
409 buffer.push_str(ending);
410 }
411 }
412 }
413
414 Ok(())
415 }
416
417 fn process_doc_block(
420 spaces: usize,
421 buffer: &mut String,
422 s: &str,
423 ending: &str,
424 ) -> Result<(), Error> {
425 if s.is_empty() {
427 Self::push_spaces(spaces, buffer);
428 buffer.push_str(DOC_COMMENT);
429 buffer.push_str(ending);
430 } else {
432 let s: syn::LitStr = syn::parse_str(s)?;
433 let comment = s.value();
434
435 if comment.is_empty() {
437 Self::push_spaces(spaces, buffer);
438 buffer.push_str(DOC_COMMENT);
439 buffer.push_str(ending);
440 } else {
441 for line in comment.lines() {
442 Self::push_spaces(spaces, buffer);
443 buffer.push_str(DOC_COMMENT);
444 buffer.push_str(line);
445 buffer.push_str(ending);
446 }
447 }
448 }
449
450 Ok(())
451 }
452
453 fn try_match_prefixes(
454 &mut self,
455 indent: usize,
456 chars_matched: usize,
457 prefixes: &[&[u8]],
458 allow_ws_first: bool,
459 ) -> Option<(usize, usize)> {
460 let mark_start_ident = self.curr_idx - ((chars_matched + indent) - 1);
462
463 if self.try_ws_matches(prefixes, allow_ws_first) {
464 let mark_start_value = self.curr_idx + 1;
465 Some((mark_start_ident, mark_start_value))
466 } else {
467 None
468 }
469 }
470
471 fn try_replace<F>(
472 &mut self,
473 spaces: usize,
474 chars_matched: usize,
475 suffixes: &[&[u8]],
476 mark_start_ident: usize,
477 mark_start_value: usize,
478 f: F,
479 ) -> Result<(), Error>
480 where
481 F: FnOnce(usize, &mut String, &str, &str) -> Result<(), Error>,
482 {
483 let mark_end_value = self.curr_idx + (1 - chars_matched);
485
486 if !self.try_ws_matches(suffixes, true) {
487 return Err(Error::BadSourceCode(
488 "Unable to match suffix on doc block or marker.".to_string(),
489 ));
490 }
491
492 if let Some(ending) = self.detect_line_ending() {
493 let mark_end_ident = self.curr_idx + 1;
495
496 self.copy_to_marker(mark_start_ident, mark_end_ident);
498
499 f(
501 spaces,
502 &mut self.buffer,
503 &self.source[mark_start_value..mark_end_value],
504 ending,
505 )?;
506 Ok(())
507 } else {
508 Err(Error::BadSourceCode("Expected CR or LF".to_string()))
509 }
510 }
511
512 fn try_replace_blank_marker(&mut self, spaces: usize) -> Result<bool, Error> {
513 match self.try_match_prefixes(spaces, 2, BLANK_START, false) {
516 Some((ident_start, value_start)) => {
517 self.skip_blank_param()?;
518
519 self.try_replace(
520 spaces,
521 1,
522 BLANK_END,
523 ident_start,
524 value_start,
525 CopyingCursor::process_blanks,
526 )?;
527 Ok(true)
528 }
529 None => Ok(false),
530 }
531 }
532
533 fn try_replace_comment_marker(&mut self, spaces: usize) -> Result<bool, Error> {
534 match self.try_match_prefixes(spaces, 2, COMMENT_START, false) {
537 Some((ident_start, value_start)) => {
538 let (matched, suffix) = match self.try_skip_string()? {
540 None => (0, COMMENT_END),
542 Some(b')') => (1, COMMENT_END2),
544 Some(ch) => {
545 return Err(Error::BadSourceCode(format!(
546 "Expected ')' or string, but got: {}",
547 ch as char
548 )))
549 }
550 };
551
552 self.try_replace(
553 spaces,
554 matched,
555 suffix,
556 ident_start,
557 value_start,
558 CopyingCursor::process_comments,
559 )?;
560 Ok(true)
561 }
562 None => Ok(false),
563 }
564 }
565
566 fn try_replace_doc_block(&mut self, spaces: usize) -> Result<bool, Error> {
567 match self.try_match_prefixes(spaces, 1, DOC_BLOCK_START, true) {
570 Some((ident_start, value_start)) => {
571 match self.try_skip_string()? {
573 None => {
575 self.try_replace(
576 spaces,
577 0,
578 DOC_BLOCK_END,
579 ident_start,
580 value_start,
581 CopyingCursor::process_doc_block,
582 )?;
583 Ok(true)
584 }
585 Some(ch) => Err(Error::BadSourceCode(format!(
586 "Expected string, but got: {}",
587 ch as char
588 ))),
589 }
590 }
591 None => Ok(false),
592 }
593 }
594}
595
596pub(crate) fn replace_markers(s: &str, replace_doc_blocks: bool) -> Result<Cow<str>, Error> {
597 match CopyingCursor::new(s) {
598 Some(mut cursor) => {
599 let mut indent = 0;
600
601 loop {
602 match cursor.curr {
603 b'r' => {
605 indent = 0;
606 if !cursor.try_skip_raw_string() {
607 continue;
608 }
609 }
610 b'\"' => {
612 indent = 0;
613 cursor.skip_string()
614 }
615 b'/' => {
617 indent = 0;
618 if !cursor.try_skip_comment() {
619 continue;
620 }
621 }
622 b'_' => {
624 if cursor.next().is_none() {
625 break;
626 }
627
628 match cursor.curr {
629 b'b' => {
631 if !cursor.try_replace_blank_marker(indent)? {
632 indent = 0;
633 continue;
634 }
635 }
636 b'c' => {
638 if !cursor.try_replace_comment_marker(indent)? {
639 indent = 0;
640 continue;
641 }
642 }
643 _ => {
645 indent = 0;
646 continue;
647 }
648 }
649
650 indent = 0;
651 }
652 b'#' if replace_doc_blocks => {
654 if !cursor.try_replace_doc_block(indent)? {
655 indent = 0;
656 continue;
657 }
658
659 indent = 0;
660 }
661 b' ' => {
663 indent += 1;
664 }
665 _ => {
667 indent = 0;
668 }
669 }
670
671 if cursor.next().is_none() {
672 break;
673 }
674 }
675
676 Ok(cursor.into_buffer())
677 }
678 None => Ok(Cow::Borrowed(s)),
680 }
681}
682
683#[cfg(test)]
686mod tests {
687 use std::borrow::Cow;
688
689 use pretty_assertions::assert_eq;
690
691 use crate::replace::replace_markers;
692 use crate::Error;
693
694 #[test]
695 fn blank() {
696 let source = "";
697
698 let actual = replace_markers(source, false).unwrap();
699 let expected = source;
700
701 assert_eq!(expected, actual);
702 assert!(matches!(actual, Cow::Borrowed(_)));
703 }
704
705 #[test]
706 fn no_replacements() {
707 let source = r####"// _comment!_("comment");
708
709/* /* nested comment */ */
710
711/// This is a main function
712fn main() {
713 println!("hello world");
714 println!(r##"hello raw world!"##);
715}
716_blank!_;
717"####;
718
719 let actual = replace_markers(source, false).unwrap();
720 let expected = source;
721
722 assert_eq!(expected, actual);
723 assert!(matches!(actual, Cow::Borrowed(_)));
724 }
725
726 #[test]
727 fn replace_comments() {
728 let source = r####"// _comment!_("comment");
729
730/* /* nested comment */ */
731_comment_!("comment 1\n\ncomment 2");
732_comment_!("test");
733_comment!("skip this");
734/// This is a main function
735fn main() {
736 println!(r##"hello raw world!"##);
737 _comment_!(r"");
738 _comment_!();
739 println!("hello \nworld");
740}
741
742 _comment_ !
743( r#"This is two
744comments"# )
745;
746_blank!_;
747"####;
748
749 let actual = replace_markers(source, false).unwrap();
750 let expected = r####"// _comment!_("comment");
751
752/* /* nested comment */ */
753// comment 1
754//
755// comment 2
756// test
757_comment!("skip this");
758/// This is a main function
759fn main() {
760 println!(r##"hello raw world!"##);
761 //
762 //
763 println!("hello \nworld");
764}
765
766 // This is two
767 // comments
768_blank!_;
769"####;
770
771 assert_eq!(expected, actual);
772 }
773
774 #[test]
775 fn replace_blanks() {
776 let source = r####"// _blank!_(5);
777
778/* /* nested comment */ */
779_blank_!(2);
780_blank!_("skip this");
781#[doc = "This is a main function"]
782fn main() {
783 let r#test = "hello";
784 println!(r"hello raw world!");
785 _blank_!();
786 println!("hello \nworld");
787}
788
789 _blank_
790!(
7912
792);
793_blank!_;
794"####;
795
796 let actual = replace_markers(source, false).unwrap();
797 let expected = r####"// _blank!_(5);
798
799/* /* nested comment */ */
800
801
802_blank!_("skip this");
803#[doc = "This is a main function"]
804fn main() {
805 let r#test = "hello";
806 println!(r"hello raw world!");
807
808 println!("hello \nworld");
809}
810
811
812
813_blank!_;
814"####;
815
816 assert_eq!(expected, actual);
817 }
818
819 #[test]
820 fn replace_doc_blocks() {
821 let source = r####"// _blank!_(5);
822
823/* not a nested comment */
824#[doc = r#" This is a main function"#]
825#[doc = r#" This is two doc
826 comments"#]
827#[cfg(feature = "main")]
828#[doc(hidden)]
829fn main() {
830 println!(r##"hello raw world!"##);
831 #[doc = ""]
832 println!("hello \nworld");
833}
834
835# [
836doc
837 =
838 " this is\n\n three doc comments"
839
840 ]
841fn test() {
842}
843_blank!_;
844"####;
845
846 let actual = replace_markers(source, true).unwrap();
847 let expected = r####"// _blank!_(5);
848
849/* not a nested comment */
850/// This is a main function
851/// This is two doc
852/// comments
853#[cfg(feature = "main")]
854#[doc(hidden)]
855fn main() {
856 println!(r##"hello raw world!"##);
857 ///
858 println!("hello \nworld");
859}
860
861/// this is
862///
863/// three doc comments
864fn test() {
865}
866_blank!_;
867"####;
868
869 assert_eq!(expected, actual);
870 }
871
872 #[test]
873 fn replace_crlf() {
874 let source = "_blank_!(2);\r\n";
875 let actual = replace_markers(source, false).unwrap();
876
877 let expected = "\r\n\r\n";
878 assert_eq!(expected, actual);
879 }
880
881 #[test]
882 fn marker_end_after_prefix() {
883 assert!(matches!(
884 replace_markers("_blank_!(", false),
885 Err(Error::BadSourceCode(_))
886 ));
887 }
888
889 #[test]
890 fn marker_param_not_string() {
891 assert!(matches!(
892 replace_markers("_comment_!(blah);\n", false),
893 Err(Error::BadSourceCode(_))
894 ));
895 }
896
897 #[test]
898 fn marker_bad_suffix() {
899 assert!(matches!(
900 replace_markers("_comment_!(\"blah\"];\n", false),
901 Err(Error::BadSourceCode(_))
902 ));
903 }
904
905 #[test]
906 fn doc_block_string_not_closed() {
907 assert!(matches!(
908 replace_markers("#[doc = \"test]\n", true),
909 Err(Error::BadSourceCode(_))
910 ));
911 }
912}