roup/parser/
mod.rs

1mod clause;
2mod directive;
3pub mod openmp;
4
5pub use clause::{Clause, ClauseKind, ClauseRegistry, ClauseRegistryBuilder, ClauseRule};
6pub use directive::{Directive, DirectiveRegistry, DirectiveRegistryBuilder, DirectiveRule};
7
8use super::lexer::{self, Language};
9use nom::{IResult, Parser as _};
10
11pub struct Parser {
12    clause_registry: ClauseRegistry,
13    directive_registry: DirectiveRegistry,
14    language: Language,
15}
16
17impl Parser {
18    pub fn new(directive_registry: DirectiveRegistry, clause_registry: ClauseRegistry) -> Self {
19        Self {
20            clause_registry,
21            directive_registry,
22            language: Language::default(),
23        }
24    }
25
26    pub fn with_language(mut self, language: Language) -> Self {
27        self.language = language;
28
29        // Enable case-insensitive matching for Fortran
30        // C language uses default case-sensitive matching (no changes needed)
31        if matches!(language, Language::FortranFree | Language::FortranFixed) {
32            self.directive_registry = self.directive_registry.with_case_insensitive(true);
33            self.clause_registry = self.clause_registry.with_case_insensitive(true);
34        }
35
36        self
37    }
38
39    pub fn parse<'a>(&self, input: &'a str) -> IResult<&'a str, Directive<'a>> {
40        // IMPORTANT: ROUP normalizes continuation markers before parsing
41        //
42        // Supported continuation forms:
43        // - C / C++: trailing backslash (`\`) merges the next line
44        // - Fortran: trailing `&` with optional sentinel on the following line
45        //
46        // The lexer collapses these continuations into a single logical line so the
47        // directive and clause registries operate on canonical whitespace.
48
49        let input = match self.language {
50            Language::C => {
51                let (input, _) = (
52                    lexer::lex_pragma,
53                    lexer::skip_space1_and_comments,
54                    lexer::lex_omp,
55                    lexer::skip_space1_and_comments,
56                )
57                    .parse(input)?;
58                input
59            }
60            Language::FortranFree => {
61                let (input, _) = (
62                    lexer::lex_fortran_free_sentinel,
63                    lexer::skip_space1_and_comments,
64                )
65                    .parse(input)?;
66                input
67            }
68            Language::FortranFixed => {
69                let (input, _) = (
70                    lexer::lex_fortran_fixed_sentinel,
71                    lexer::skip_space1_and_comments,
72                )
73                    .parse(input)?;
74                input
75            }
76        };
77        self.directive_registry.parse(input, &self.clause_registry)
78    }
79}
80
81impl Default for Parser {
82    fn default() -> Self {
83        openmp::parser()
84    }
85}
86
87pub fn parse_omp_directive(input: &str) -> IResult<&str, Directive<'_>> {
88    let parser = Parser::default();
89    parser.parse(input)
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95    use crate::lexer::{self, Language};
96    use std::borrow::Cow;
97
98    #[test]
99    fn parses_full_pragma_with_default_registries() {
100        let input = "#pragma omp parallel private(a, b) nowait";
101
102        let (rest, directive) = parse_omp_directive(input).expect("parsing should succeed");
103
104        assert_eq!(rest, "");
105        assert_eq!(directive.name, "parallel");
106        assert_eq!(directive.clauses.len(), 2);
107        assert_eq!(directive.clauses[0].name, "private");
108        assert_eq!(
109            directive.clauses[0].kind,
110            ClauseKind::Parenthesized("a, b".into())
111        );
112        assert_eq!(directive.clauses[1].name, "nowait");
113        assert_eq!(directive.clauses[1].kind, ClauseKind::Bare);
114    }
115
116    #[test]
117    fn parser_uses_custom_registries() {
118        fn parse_only_bare<'a>(name: Cow<'a, str>, input: &'a str) -> IResult<&'a str, Clause<'a>> {
119            let (input, _) = nom::character::complete::char('(')(input)?;
120            let (input, value) = lexer::lex_clause(input)?;
121            let (input, _) = nom::character::complete::char(')')(input)?;
122
123            Ok((
124                input,
125                Clause {
126                    name,
127                    kind: ClauseKind::Parenthesized(value.into()),
128                },
129            ))
130        }
131
132        let clause_registry = ClauseRegistry::builder()
133            .register_custom("device", parse_only_bare)
134            .build();
135
136        fn parse_prefixed<'a>(
137            name: Cow<'a, str>,
138            input: &'a str,
139            clause_registry: &ClauseRegistry,
140        ) -> IResult<&'a str, Directive<'a>> {
141            let (input, _) = (
142                nom::character::complete::multispace1,
143                nom::bytes::complete::tag("use:"),
144                nom::character::complete::multispace1,
145            )
146                .parse(input)?;
147            let (input, clauses) = clause_registry.parse_sequence(input)?;
148
149            Ok((input, Directive { name, clauses }))
150        }
151
152        let directive_registry = DirectiveRegistry::builder()
153            .register_custom("target", parse_prefixed)
154            .build();
155
156        let parser = Parser::new(directive_registry, clause_registry);
157
158        let (rest, directive) = parser
159            .parse("#pragma omp target use: device(gpu)")
160            .expect("parsing should succeed");
161
162        assert_eq!(rest, "");
163        assert_eq!(directive.name, "target");
164        assert_eq!(directive.clauses.len(), 1);
165        assert_eq!(directive.clauses[0].name, "device");
166        assert_eq!(
167            directive.clauses[0].kind,
168            ClauseKind::Parenthesized("gpu".into())
169        );
170    }
171
172    #[test]
173    fn parses_c_multiline_directive_with_backslash() {
174        let input = "#pragma omp parallel for \
175            private(a, \
176                    b) \
177            nowait";
178        let parser = Parser::default();
179        let (_, directive) = parser.parse(input).expect("directive should parse");
180
181        assert_eq!(directive.name, "parallel for");
182        assert_eq!(directive.clauses.len(), 2);
183        assert_eq!(directive.clauses[0].name, "private");
184        assert_eq!(
185            directive.clauses[0].kind,
186            ClauseKind::Parenthesized("a, b".into())
187        );
188        assert_eq!(directive.clauses[1].name, "nowait");
189        assert_eq!(directive.clauses[1].kind, ClauseKind::Bare);
190    }
191
192    #[test]
193    fn parses_fortran_free_multiline_directive() {
194        let parser = Parser::default().with_language(Language::FortranFree);
195        let input = "!$omp target teams distribute &\n!$omp parallel do &\n!$omp& private(i, j)";
196
197        let (_, directive) = parser.parse(input).expect("directive should parse");
198
199        assert_eq!(directive.name, "target teams distribute parallel do");
200        assert_eq!(directive.clauses.len(), 1);
201        assert_eq!(directive.clauses[0].name, "private");
202        assert_eq!(
203            directive.clauses[0].kind,
204            ClauseKind::Parenthesized("i, j".into())
205        );
206    }
207
208    #[test]
209    fn parses_fortran_parenthesized_clause_with_continuation() {
210        let parser = Parser::default().with_language(Language::FortranFree);
211        let input = "!$omp parallel do private(i, &\n!$omp& j, &\n!$omp& k)";
212
213        let (_, directive) = parser.parse(input).expect("directive should parse");
214
215        assert_eq!(directive.name, "parallel do");
216        assert_eq!(directive.clauses.len(), 1);
217        assert_eq!(directive.clauses[0].name, "private");
218        assert_eq!(
219            directive.clauses[0].kind,
220            ClauseKind::Parenthesized("i, j, k".into())
221        );
222    }
223
224    #[test]
225    fn parses_fortran_fixed_multiline_directive() {
226        let parser = Parser::default().with_language(Language::FortranFixed);
227        let input = "      C$OMP   DO &\n      !$OMP& SCHEDULE(DYNAMIC) &\n      !$OMP PRIVATE(I)";
228
229        let (_, directive) = parser.parse(input).expect("directive should parse");
230
231        assert_eq!(directive.name, "do");
232        assert_eq!(directive.clauses.len(), 2);
233        assert_eq!(directive.clauses[0].name, "schedule");
234        assert_eq!(
235            directive.clauses[0].kind,
236            ClauseKind::Parenthesized("DYNAMIC".into())
237        );
238        assert_eq!(directive.clauses[1].name, "private");
239        assert_eq!(
240            directive.clauses[1].kind,
241            ClauseKind::Parenthesized("I".into())
242        );
243    }
244}