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