naga_oil/compose/
comment_strip_iter.rs1use std::{borrow::Cow, str::Lines};
2
3use regex::Regex;
4
5static RE_COMMENT: once_cell::sync::Lazy<Regex> =
6 once_cell::sync::Lazy::new(|| Regex::new(r"(//|/\*|\*/)").unwrap());
7
8pub struct CommentReplaceIter<'a> {
9 lines: &'a mut Lines<'a>,
10 block_depth: usize,
11}
12
13impl<'a> Iterator for CommentReplaceIter<'a> {
14 type Item = Cow<'a, str>;
15
16 fn next(&mut self) -> Option<Self::Item> {
17 let line_in = self.lines.next()?;
18 let mut markers = RE_COMMENT
19 .captures_iter(line_in)
20 .map(|cap| cap.get(0).unwrap())
21 .peekable();
22
23 if self.block_depth == 0 && markers.peek().is_none() {
25 return Some(Cow::Borrowed(line_in));
26 }
27
28 let mut output = String::new();
29 let mut section_start = 0;
30
31 loop {
32 let mut next_marker = markers.next();
33 let mut section_end = next_marker.map(|m| m.start()).unwrap_or(line_in.len());
34
35 while next_marker.is_some() && section_start > section_end {
37 next_marker = markers.next();
38 section_end = next_marker.map(|m| m.start()).unwrap_or(line_in.len());
39 }
40
41 if self.block_depth == 0 {
42 output.push_str(&line_in[section_start..section_end]);
43 } else {
44 output.extend(std::iter::repeat(' ').take(section_end - section_start));
45 }
46
47 match next_marker {
48 None => return Some(Cow::Owned(output)),
49 Some(marker) => {
50 match marker.as_str() {
51 "//" => {
52 if self.block_depth == 0 {
55 output.extend(
56 std::iter::repeat(' ').take(line_in.len() - marker.start()),
57 );
58 return Some(Cow::Owned(output));
59 }
60 }
61 "/*" => {
62 self.block_depth += 1;
63 }
64 "*/" => {
65 self.block_depth = self.block_depth.saturating_sub(1);
66 }
67 _ => unreachable!(),
68 }
69 output.extend(std::iter::repeat(' ').take(marker.as_str().len()));
70 section_start = marker.end();
71 }
72 }
73 }
74 }
75}
76
77pub trait CommentReplaceExt<'a> {
78 fn replace_comments(&'a mut self) -> CommentReplaceIter;
80}
81
82impl<'a> CommentReplaceExt<'a> for Lines<'a> {
83 fn replace_comments(&'a mut self) -> CommentReplaceIter {
84 CommentReplaceIter {
85 lines: self,
86 block_depth: 0,
87 }
88 }
89}
90
91#[test]
92fn comment_test() {
93 const INPUT: &str = r"
94not commented
95// line commented
96not commented
97/* block commented on a line */
98not commented
99// line comment with a /* block comment unterminated
100not commented
101/* block comment
102 spanning lines */
103not commented
104/* block comment
105 spanning lines and with // line comments
106 even with a // line commented terminator */
107not commented
108";
109
110 assert_eq!(
111 INPUT
112 .lines()
113 .replace_comments()
114 .zip(INPUT.lines())
115 .find(|(line, original)| {
116 (line != "not commented" && !line.chars().all(|c| c == ' '))
117 || line.len() != original.len()
118 }),
119 None
120 );
121
122 const PARTIAL_TESTS: [(&str, &str); 4] = [
123 (
124 "1.0 /* block comment with a partial line comment on the end *// 2.0",
125 "1.0 / 2.0",
126 ),
127 (
128 "1.0 /* block comment with a partial block comment on the end */* 2.0",
129 "1.0 * 2.0",
130 ),
131 (
132 "1.0 /* block comment 1 *//* block comment 2 */ * 2.0",
133 "1.0 * 2.0",
134 ),
135 (
136 "1.0 /* block comment with real line comment after */// line comment",
137 "1.0 ",
138 ),
139 ];
140
141 for &(input, expected) in PARTIAL_TESTS.iter() {
142 let mut nasty_processed = input.lines();
143 let nasty_processed = nasty_processed.replace_comments().next().unwrap();
144 assert_eq!(&nasty_processed, expected);
145 }
146}