naga_oil/compose/
comment_strip_iter.rs1use std::{borrow::Cow, str::Lines};
2
3use regex::Regex;
4
5static RE_NONE: once_cell::sync::Lazy<Regex> =
7 once_cell::sync::Lazy::new(|| Regex::new(r#"(//|/\*|\")"#).unwrap());
8static RE_BLOCK: once_cell::sync::Lazy<Regex> =
10 once_cell::sync::Lazy::new(|| Regex::new(r"(/\*|\*/)").unwrap());
11static RE_QUOTE: once_cell::sync::Lazy<Regex> =
13 once_cell::sync::Lazy::new(|| Regex::new(r#"\""#).unwrap());
14
15#[derive(PartialEq, Eq)]
16enum CommentState {
17 None,
18 Block(usize),
19 Quote,
20}
21
22pub struct CommentReplaceIter<'a> {
23 lines: &'a mut Lines<'a>,
24 state: CommentState,
25}
26
27impl<'a> Iterator for CommentReplaceIter<'a> {
28 type Item = Cow<'a, str>;
29
30 fn next(&mut self) -> Option<Self::Item> {
31 let line_in = self.lines.next()?;
32
33 if self.state == CommentState::None && !RE_NONE.is_match(line_in) {
35 return Some(Cow::Borrowed(line_in));
36 }
37
38 let mut output = String::new();
39 let mut section_start = 0;
40
41 loop {
42 let marker = match self.state {
43 CommentState::None => &RE_NONE,
44 CommentState::Block(_) => &RE_BLOCK,
45 CommentState::Quote => &RE_QUOTE,
46 }
47 .find(&line_in[section_start..]);
48
49 let section_end = marker
50 .map(|m| section_start + m.start())
51 .unwrap_or(line_in.len());
52
53 if let CommentState::Block(_) = self.state {
54 output.extend(std::iter::repeat(' ').take(section_end - section_start));
55 } else {
56 output.push_str(&line_in[section_start..section_end]);
57 }
58
59 match marker {
60 None => return Some(Cow::Owned(output)),
61 Some(marker) => {
62 match marker.as_str() {
63 "//" => {
65 output.extend(
66 std::iter::repeat(' ')
67 .take(line_in.len() - marker.start() - section_start),
68 );
69 return Some(Cow::Owned(output));
70 }
71 "/*" => {
73 self.state = match self.state {
74 CommentState::None => CommentState::Block(1),
75 CommentState::Block(n) => CommentState::Block(n + 1),
76 _ => unreachable!(),
77 };
78 output.push_str(" ");
79 }
80 "*/" => {
82 self.state = match self.state {
83 CommentState::Block(1) => CommentState::None,
84 CommentState::Block(n) => CommentState::Block(n - 1),
85 _ => unreachable!(),
86 };
87 output.push_str(" ");
88 }
89 "\"" => {
91 self.state = match self.state {
92 CommentState::None => CommentState::Quote,
93 CommentState::Quote => CommentState::None,
94 _ => unreachable!(),
95 };
96 output.push('"');
97 }
98 _ => unreachable!(),
99 }
100 section_start += marker.end();
101 }
102 }
103 }
104 }
105}
106
107pub trait CommentReplaceExt<'a> {
108 fn replace_comments(&'a mut self) -> CommentReplaceIter<'a>;
110}
111
112impl<'a> CommentReplaceExt<'a> for Lines<'a> {
113 fn replace_comments(&'a mut self) -> CommentReplaceIter<'a> {
114 CommentReplaceIter {
115 lines: self,
116 state: CommentState::None,
117 }
118 }
119}
120
121#[test]
122fn comment_test() {
123 const INPUT: &str = r"
124not commented
125// line commented
126not commented
127/* block commented on a line */
128not commented
129// line comment with a /* block comment unterminated
130not commented
131/* block comment
132 spanning lines */
133not commented
134/* block comment
135 spanning lines and with // line comments
136 even with a // line commented terminator */
137not commented
138";
139
140 assert_eq!(
141 INPUT
142 .lines()
143 .replace_comments()
144 .zip(INPUT.lines())
145 .find(|(line, original)| {
146 (line != "not commented" && !line.chars().all(|c| c == ' '))
147 || line.len() != original.len()
148 }),
149 None
150 );
151
152 const PARTIAL_TESTS: [(&str, &str); 11] = [
153 (
154 "1.0 /* block comment with a partial line comment on the end *// 2.0",
155 "1.0 / 2.0",
156 ),
157 (
158 "1.0 /* block comment with a partial block comment on the end */* 2.0",
159 "1.0 * 2.0",
160 ),
161 (
162 "1.0 /* block comment 1 *//* block comment 2 */ * 2.0",
163 "1.0 * 2.0",
164 ),
165 (
166 "1.0 /* block comment with real line comment after */// line comment",
167 "1.0 ",
168 ),
169 ("*/", "*/"),
170 (
171 r#"#import "embedded://file.wgsl""#,
172 r#"#import "embedded://file.wgsl""#,
173 ),
174 (
175 r#"// #import "embedded://file.wgsl""#,
176 r#" "#,
177 ),
178 (
179 r#"/* #import "embedded://file.wgsl" */"#,
180 r#" "#,
181 ),
182 (
183 r#"/* #import "embedded:*/file.wgsl" */"#,
184 r#" file.wgsl" */"#,
185 ),
186 (
187 r#"#import "embedded://file.wgsl" // comment"#,
188 r#"#import "embedded://file.wgsl" "#,
189 ),
190 (
191 r#"#import "embedded:/* */ /* /**/* / / /// * / //*/*/ / */*file.wgsl""#,
192 r#"#import "embedded:/* */ /* /**/* / / /// * / //*/*/ / */*file.wgsl""#,
193 ),
194 ];
195
196 for &(input, expected) in PARTIAL_TESTS.iter() {
197 let mut nasty_processed = input.lines();
198 let nasty_processed = nasty_processed.replace_comments().next().unwrap();
199 assert_eq!(&nasty_processed, expected);
200 }
201}
202
203#[test]
204fn multiline_comment_test() {
205 let test_cases = [
206 (
207 r"/*
209hoho
210*/",
211 r"
212
213 ",
214 ),
215 (
216 r"///*
218hehe
219//*/",
220 r"
221hehe
222 ",
223 ),
224 (
225 r"/* // */ code goes here /*
227Still a comment // */
228/* dummy */",
229 r" code goes here
230
231 ",
232 ),
233 (
234 r"/*
237//*
238*/commented
239*/not commented",
240 r"
241
242
243 not commented",
244 ),
245 ];
246
247 for &(input, expected) in test_cases.iter() {
248 for (output_line, expected_line) in input.lines().replace_comments().zip(expected.lines()) {
249 assert_eq!(output_line.as_ref(), expected_line);
250 }
251 }
252}
253
254#[test]
255fn test_comment_becomes_spaces() {
256 let test_cases = [("let a/**/b =3u;", "let a b =3u;")];
257 for &(input, expected) in test_cases.iter() {
258 for (output_line, expected_line) in input.lines().replace_comments().zip(expected.lines()) {
259 assert_eq!(output_line.as_ref(), expected_line);
260 }
261 }
262}