naga_oil/compose/
comment_strip_iter.rs

1use 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        // fast path
24        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            // skip partial tokens
36            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                            // the specs (https://www.w3.org/TR/WGSL/#comment, https://registry.khronos.org/OpenGL/specs/gl/GLSLangSpec.4.60.pdf @ 3.4) state that
53                            // whichever comment-type starts first should cancel parsing of the other type
54                            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    /// replace WGSL and GLSL comments with whitespace characters
79    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}