roup/ir/
convert.rs

1//! Conversion from parser types to IR types
2//!
3//! This module handles the conversion from the parser's string-based
4//! representation to the IR's semantic representation.
5//!
6//! ## Learning Objectives
7//!
8//! - **Pattern matching on strings**: Mapping clause names to semantic types
9//! - **Error handling**: Using Result for fallible conversions
10//! - **Parsing clause data**: Extracting semantic meaning from strings
11//! - **Gradual refinement**: Starting simple, adding complexity incrementally
12//!
13//! ## Conversion Strategy
14//!
15//! The parser gives us:
16//! - Directive name as a string (e.g., "parallel for")
17//! - Clauses with names and optional content strings
18//!
19//! We need to convert this to IR which has:
20//! - DirectiveKind enum
21//! - ClauseData with structured semantic information
22//!
23//! ## Example
24//!
25//! ```text
26//! Parser output:
27//!   Directive { name: "parallel for",
28//!               clauses: [
29//!                 Clause { name: "private", kind: Parenthesized("x, y") },
30//!                 Clause { name: "reduction".into(), kind: Parenthesized("+: sum") }
31//!               ] }
32//!
33//! IR output:
34//!   DirectiveIR {
35//!     kind: DirectiveKind::ParallelFor,
36//!     clauses: [
37//!       ClauseData::Private { items: [Identifier("x"), Identifier("y")] },
38//!       ClauseData::Reduction { operator: Add, items: [Identifier("sum")] }
39//!     ],
40//!     ...
41//!   }
42//! ```
43
44use super::{
45    lang, ClauseData, ClauseItem, ConversionError, DefaultKind, DependType, DirectiveIR,
46    DirectiveKind, Expression, Identifier, Language, MapType, ParserConfig, ProcBind,
47    ReductionOperator, ScheduleKind, ScheduleModifier, SourceLocation,
48};
49use crate::parser::{Clause, ClauseKind, Directive};
50
51/// Convert a directive name string to DirectiveKind
52///
53/// ## Example
54///
55/// ```
56/// # use roup::ir::{DirectiveKind, convert::parse_directive_kind};
57/// let kind = parse_directive_kind("parallel for").unwrap();
58/// assert_eq!(kind, DirectiveKind::ParallelFor);
59///
60/// let kind = parse_directive_kind("target teams distribute").unwrap();
61/// assert_eq!(kind, DirectiveKind::TargetTeamsDistribute);
62/// ```
63pub fn parse_directive_kind(name: &str) -> Result<DirectiveKind, ConversionError> {
64    // Normalize whitespace for matching
65    let normalized = name.trim().to_lowercase();
66    let normalized = normalized.as_str();
67
68    match normalized {
69        // Parallel constructs
70        "parallel" => Ok(DirectiveKind::Parallel),
71        "parallel for" => Ok(DirectiveKind::ParallelFor),
72        "parallel for simd" => Ok(DirectiveKind::ParallelForSimd),
73        "parallel sections" => Ok(DirectiveKind::ParallelSections),
74        "parallel workshare" => Ok(DirectiveKind::ParallelWorkshare),
75        "parallel loop" => Ok(DirectiveKind::ParallelLoop),
76        "parallel masked" => Ok(DirectiveKind::ParallelMasked),
77        "parallel master" => Ok(DirectiveKind::ParallelMaster),
78
79        // Work-sharing constructs
80        "for" => Ok(DirectiveKind::For),
81        "for simd" => Ok(DirectiveKind::ForSimd),
82        "sections" => Ok(DirectiveKind::Sections),
83        "section" => Ok(DirectiveKind::Section),
84        "single" => Ok(DirectiveKind::Single),
85        "workshare" => Ok(DirectiveKind::Workshare),
86        "loop" => Ok(DirectiveKind::Loop),
87
88        // SIMD constructs
89        "simd" => Ok(DirectiveKind::Simd),
90        "declare simd" => Ok(DirectiveKind::DeclareSimd),
91
92        // Task constructs
93        "task" => Ok(DirectiveKind::Task),
94        "taskloop" => Ok(DirectiveKind::Taskloop),
95        "taskloop simd" => Ok(DirectiveKind::TaskloopSimd),
96        "taskyield" => Ok(DirectiveKind::Taskyield),
97        "taskwait" => Ok(DirectiveKind::Taskwait),
98        "taskgroup" => Ok(DirectiveKind::Taskgroup),
99
100        // Target constructs
101        "target" => Ok(DirectiveKind::Target),
102        "target data" => Ok(DirectiveKind::TargetData),
103        "target enter data" => Ok(DirectiveKind::TargetEnterData),
104        "target exit data" => Ok(DirectiveKind::TargetExitData),
105        "target update" => Ok(DirectiveKind::TargetUpdate),
106        "target parallel" => Ok(DirectiveKind::TargetParallel),
107        "target parallel for" => Ok(DirectiveKind::TargetParallelFor),
108        "target parallel for simd" => Ok(DirectiveKind::TargetParallelForSimd),
109        "target parallel loop" => Ok(DirectiveKind::TargetParallelLoop),
110        "target simd" => Ok(DirectiveKind::TargetSimd),
111        "target teams" => Ok(DirectiveKind::TargetTeams),
112        "target teams distribute" => Ok(DirectiveKind::TargetTeamsDistribute),
113        "target teams distribute simd" => Ok(DirectiveKind::TargetTeamsDistributeSimd),
114        "target teams distribute parallel for" => {
115            Ok(DirectiveKind::TargetTeamsDistributeParallelFor)
116        }
117        "target teams distribute parallel for simd" => {
118            Ok(DirectiveKind::TargetTeamsDistributeParallelForSimd)
119        }
120        "target teams loop" => Ok(DirectiveKind::TargetTeamsLoop),
121
122        // Teams constructs
123        "teams" => Ok(DirectiveKind::Teams),
124        "teams distribute" => Ok(DirectiveKind::TeamsDistribute),
125        "teams distribute simd" => Ok(DirectiveKind::TeamsDistributeSimd),
126        "teams distribute parallel for" => Ok(DirectiveKind::TeamsDistributeParallelFor),
127        "teams distribute parallel for simd" => Ok(DirectiveKind::TeamsDistributeParallelForSimd),
128        "teams loop" => Ok(DirectiveKind::TeamsLoop),
129
130        // Synchronization constructs
131        "barrier" => Ok(DirectiveKind::Barrier),
132        "critical" => Ok(DirectiveKind::Critical),
133        "atomic" => Ok(DirectiveKind::Atomic),
134        "flush" => Ok(DirectiveKind::Flush),
135        "ordered" => Ok(DirectiveKind::Ordered),
136        "master" => Ok(DirectiveKind::Master),
137        "masked" => Ok(DirectiveKind::Masked),
138
139        // Declare constructs
140        "declare reduction" => Ok(DirectiveKind::DeclareReduction),
141        "declare mapper" => Ok(DirectiveKind::DeclareMapper),
142        "declare target" => Ok(DirectiveKind::DeclareTarget),
143        "declare variant" => Ok(DirectiveKind::DeclareVariant),
144
145        // Distribute constructs
146        "distribute" => Ok(DirectiveKind::Distribute),
147        "distribute simd" => Ok(DirectiveKind::DistributeSimd),
148        "distribute parallel for" => Ok(DirectiveKind::DistributeParallelFor),
149        "distribute parallel for simd" => Ok(DirectiveKind::DistributeParallelForSimd),
150
151        // Meta-directives
152        "metadirective" => Ok(DirectiveKind::Metadirective),
153
154        // Other constructs
155        "threadprivate" => Ok(DirectiveKind::Threadprivate),
156        "allocate" => Ok(DirectiveKind::Allocate),
157        "requires" => Ok(DirectiveKind::Requires),
158        "scan" => Ok(DirectiveKind::Scan),
159        "depobj" => Ok(DirectiveKind::Depobj),
160        "nothing" => Ok(DirectiveKind::Nothing),
161        "error" => Ok(DirectiveKind::Error),
162
163        _ => Err(ConversionError::UnknownDirective(name.to_string())),
164    }
165}
166
167/// Parse a clause item list using the configured language front-end.
168///
169/// Used for clauses like `private(x, y, z)` or `map(to: arr[0:N])` where the
170/// payload needs to be interpreted according to the host language.
171pub fn parse_identifier_list(
172    content: &str,
173    config: &ParserConfig,
174) -> Result<Vec<ClauseItem>, ConversionError> {
175    lang::parse_clause_item_list(content, config)
176}
177
178/// Parse a reduction operator from a string
179///
180/// ## Example
181///
182/// ```
183/// # use roup::ir::{convert::parse_reduction_operator, ReductionOperator};
184/// let op = parse_reduction_operator("+").unwrap();
185/// assert_eq!(op, ReductionOperator::Add);
186///
187/// let op = parse_reduction_operator("min").unwrap();
188/// assert_eq!(op, ReductionOperator::Min);
189/// ```
190pub fn parse_reduction_operator(op_str: &str) -> Result<ReductionOperator, ConversionError> {
191    match op_str {
192        "+" => Ok(ReductionOperator::Add),
193        "-" => Ok(ReductionOperator::Subtract),
194        "*" => Ok(ReductionOperator::Multiply),
195        "&" => Ok(ReductionOperator::BitwiseAnd),
196        "|" => Ok(ReductionOperator::BitwiseOr),
197        "^" => Ok(ReductionOperator::BitwiseXor),
198        "&&" => Ok(ReductionOperator::LogicalAnd),
199        "||" => Ok(ReductionOperator::LogicalOr),
200        "min" => Ok(ReductionOperator::Min),
201        "max" => Ok(ReductionOperator::Max),
202        _ => Err(ConversionError::InvalidClauseSyntax(format!(
203            "Unknown reduction operator: {op_str}"
204        ))),
205    }
206}
207
208/// Parse a schedule clause
209///
210/// Format: `schedule([modifier[, modifier]:] kind[, chunk_size])`
211///
212/// ## Example
213///
214/// ```
215/// # use roup::ir::{convert::parse_schedule_clause, ParserConfig, Language};
216/// let config = ParserConfig::with_parsing(Language::C);
217/// let clause = parse_schedule_clause("static, 10", &config).unwrap();
218/// // Returns ClauseData::Schedule with kind=Static, chunk_size=Some(10)
219/// ```
220pub fn parse_schedule_clause(
221    content: &str,
222    config: &ParserConfig,
223) -> Result<ClauseData, ConversionError> {
224    // Check for modifiers (they end with a colon)
225    let (modifiers, rest) = if let Some(colon_pos) = content.find(':') {
226        let (mod_str, kind_str) = content.split_at(colon_pos);
227        let kind_str = kind_str[1..].trim(); // Skip the ':'
228
229        // Parse modifiers (comma-separated)
230        let mods: Vec<ScheduleModifier> = mod_str
231            .split(',')
232            .map(|s| s.trim())
233            .filter(|s| !s.is_empty())
234            .map(|s| match s {
235                "monotonic" => Ok(ScheduleModifier::Monotonic),
236                "nonmonotonic" => Ok(ScheduleModifier::Nonmonotonic),
237                "simd" => Ok(ScheduleModifier::Simd),
238                _ => Err(ConversionError::InvalidClauseSyntax(format!(
239                    "Unknown schedule modifier: {s}"
240                ))),
241            })
242            .collect::<Result<Vec<_>, _>>()?;
243
244        (mods, kind_str)
245    } else {
246        (vec![], content)
247    };
248
249    // Parse kind and optional chunk size (comma-separated)
250    let parts: Vec<&str> = rest.split(',').map(|s| s.trim()).collect();
251
252    let kind = match parts.first() {
253        Some(&"static") => ScheduleKind::Static,
254        Some(&"dynamic") => ScheduleKind::Dynamic,
255        Some(&"guided") => ScheduleKind::Guided,
256        Some(&"auto") => ScheduleKind::Auto,
257        Some(&"runtime") => ScheduleKind::Runtime,
258        Some(s) => {
259            return Err(ConversionError::InvalidClauseSyntax(format!(
260                "Unknown schedule kind: {s}"
261            )))
262        }
263        None => {
264            return Err(ConversionError::InvalidClauseSyntax(
265                "schedule clause requires a kind".to_string(),
266            ))
267        }
268    };
269
270    let chunk_size = parts.get(1).map(|s| Expression::new(*s, config));
271
272    Ok(ClauseData::Schedule {
273        kind,
274        modifiers,
275        chunk_size,
276    })
277}
278
279/// Parse a map clause
280///
281/// Format: `map([[mapper(mapper-identifier),] map-type:] list)`
282///
283/// Supports mapper syntax and respects nesting when finding colons.
284///
285/// ## Example
286///
287/// ```
288/// # use roup::ir::{convert::parse_map_clause, ParserConfig, Language};
289/// let config = ParserConfig::with_parsing(Language::C);
290/// let clause = parse_map_clause("to: arr", &config).unwrap();
291/// // Returns ClauseData::Map with map_type=To, items=[arr]
292///
293/// let clause = parse_map_clause("mapper(custom), to: arr[0:N]", &config).unwrap();
294/// // Returns ClauseData::Map with mapper=Some(custom), map_type=To, items=[arr[0:N]]
295/// ```
296pub fn parse_map_clause(
297    content: &str,
298    config: &ParserConfig,
299) -> Result<ClauseData, ConversionError> {
300    let mut remainder = content.trim();
301    let mut mapper = None;
302
303    // Check for mapper(...) prefix
304    if remainder.len() >= 6 && remainder[..6].eq_ignore_ascii_case("mapper") {
305        let after_keyword = remainder[6..].trim_start();
306        if after_keyword.starts_with('(') {
307            // Extract mapper identifier
308            let (mapper_body, rest) = extract_parenthesized(after_keyword)?;
309            mapper = Some(Identifier::new(mapper_body.trim()));
310            remainder = rest.trim_start();
311
312            // Skip optional comma
313            if remainder.starts_with(',') {
314                remainder = remainder[1..].trim_start();
315            }
316        }
317    }
318
319    // Find map-type using top-level colon detection
320    let (map_type, items_str) =
321        if let Some((type_str, items)) = lang::split_once_top_level(remainder, ':') {
322            let map_type = match type_str.trim().to_ascii_lowercase().as_str() {
323                "" => None,
324                "to" => Some(MapType::To),
325                "from" => Some(MapType::From),
326                "tofrom" => Some(MapType::ToFrom),
327                "alloc" => Some(MapType::Alloc),
328                "release" => Some(MapType::Release),
329                "delete" => Some(MapType::Delete),
330                other => {
331                    return Err(ConversionError::InvalidClauseSyntax(format!(
332                        "Unknown map type: {other}"
333                    )))
334                }
335            };
336            (map_type, items.trim())
337        } else {
338            (None, remainder)
339        };
340
341    let items = parse_identifier_list(items_str, config)?;
342
343    Ok(ClauseData::Map {
344        map_type,
345        mapper,
346        items,
347    })
348}
349
350/// Extract content from parentheses, handling nesting.
351///
352/// Returns (content, remainder) where content is what's inside the first set of parens.
353///
354/// This is a thin wrapper around the lang module's bracket extraction helper.
355fn extract_parenthesized(input: &str) -> Result<(&str, &str), ConversionError> {
356    // Delegate to the lang module's generic bracket extraction
357    lang::extract_bracket_content(input, '(', ')')
358}
359
360/// Parse a dependence type from a string
361///
362/// ## Example
363///
364/// ```
365/// # use roup::ir::{convert::parse_depend_type, DependType};
366/// let dt = parse_depend_type("in").unwrap();
367/// assert_eq!(dt, DependType::In);
368/// ```
369pub fn parse_depend_type(type_str: &str) -> Result<DependType, ConversionError> {
370    match type_str {
371        "in" => Ok(DependType::In),
372        "out" => Ok(DependType::Out),
373        "inout" => Ok(DependType::Inout),
374        "mutexinoutset" => Ok(DependType::Mutexinoutset),
375        "depobj" => Ok(DependType::Depobj),
376        "source" => Ok(DependType::Source),
377        "sink" => Ok(DependType::Sink),
378        _ => Err(ConversionError::InvalidClauseSyntax(format!(
379            "Unknown depend type: {type_str}"
380        ))),
381    }
382}
383
384/// Parse a linear clause
385///
386/// Format: `linear([modifier(list):] list[:step])`
387///
388/// Uses top-level colon detection to properly handle nested structures.
389///
390/// ## Example
391///
392/// ```
393/// # use roup::ir::{convert::parse_linear_clause, ParserConfig, Language};
394/// let config = ParserConfig::with_parsing(Language::C);
395/// let clause = parse_linear_clause("x, y: 2", &config).unwrap();
396/// // Returns ClauseData::Linear with items=[x, y], step=Some(2)
397/// ```
398pub fn parse_linear_clause(
399    content: &str,
400    config: &ParserConfig,
401) -> Result<ClauseData, ConversionError> {
402    // Use rsplit_once_top_level to find the last colon at top level
403    if let Some((items_str, step_str)) = lang::rsplit_once_top_level(content, ':') {
404        // Check if this might be modifier syntax (has opening paren before colon)
405        if items_str.contains('(') {
406            return Err(ConversionError::Unsupported(
407                "linear clause with modifiers not yet supported".to_string(),
408            ));
409        }
410
411        let items = parse_identifier_list(items_str, config)?;
412        let step = Some(Expression::new(step_str.trim(), config));
413
414        Ok(ClauseData::Linear {
415            modifier: None,
416            items,
417            step,
418        })
419    } else {
420        // No step, just items
421        let items = parse_identifier_list(content, config)?;
422        Ok(ClauseData::Linear {
423            modifier: None,
424            items,
425            step: None,
426        })
427    }
428}
429
430/// Convert a parser Clause to IR ClauseData
431///
432/// This is the main conversion function that handles all clause types.
433///
434/// ## Strategy
435///
436/// For now, we'll implement a subset of clauses and mark others as
437/// unsupported. This allows incremental development.
438pub fn parse_clause_data<'a>(
439    clause: &'a Clause<'a>,
440    config: &ParserConfig,
441) -> Result<ClauseData, ConversionError> {
442    let clause_name = clause.name.as_ref();
443
444    match clause_name {
445        // Bare clauses (no parameters)
446        "nowait" | "nogroup" | "untied" | "mergeable" | "seq_cst" | "relaxed" | "release"
447        | "acquire" | "acq_rel" => Ok(ClauseData::Bare(Identifier::new(clause_name))),
448
449        // default(kind)
450        "default" => {
451            if let ClauseKind::Parenthesized(ref content) = clause.kind {
452                let content = content.as_ref();
453                let kind_str = content.trim();
454                let kind = match kind_str {
455                    "shared" => DefaultKind::Shared,
456                    "none" => DefaultKind::None,
457                    "private" => DefaultKind::Private,
458                    "firstprivate" => DefaultKind::Firstprivate,
459                    _ => {
460                        return Err(ConversionError::InvalidClauseSyntax(format!(
461                            "Unknown default kind: {kind_str}"
462                        )))
463                    }
464                };
465                Ok(ClauseData::Default(kind))
466            } else {
467                Err(ConversionError::InvalidClauseSyntax(
468                    "default clause requires parenthesized content".to_string(),
469                ))
470            }
471        }
472
473        // private(list)
474        "private" => {
475            if let ClauseKind::Parenthesized(ref content) = clause.kind {
476                let content = content.as_ref();
477                let items = parse_identifier_list(content, config)?;
478                Ok(ClauseData::Private { items })
479            } else {
480                Ok(ClauseData::Private { items: vec![] })
481            }
482        }
483
484        // firstprivate(list)
485        "firstprivate" => {
486            if let ClauseKind::Parenthesized(ref content) = clause.kind {
487                let content = content.as_ref();
488                let items = parse_identifier_list(content, config)?;
489                Ok(ClauseData::Firstprivate { items })
490            } else {
491                Ok(ClauseData::Firstprivate { items: vec![] })
492            }
493        }
494
495        // shared(list)
496        "shared" => {
497            if let ClauseKind::Parenthesized(ref content) = clause.kind {
498                let content = content.as_ref();
499                let items = parse_identifier_list(content, config)?;
500                Ok(ClauseData::Shared { items })
501            } else {
502                Ok(ClauseData::Shared { items: vec![] })
503            }
504        }
505
506        // num_threads(expr)
507        "num_threads" => {
508            if let ClauseKind::Parenthesized(ref content) = clause.kind {
509                let content = content.as_ref();
510                Ok(ClauseData::NumThreads {
511                    num: Expression::new(content.trim(), config),
512                })
513            } else {
514                Err(ConversionError::InvalidClauseSyntax(
515                    "num_threads requires expression".to_string(),
516                ))
517            }
518        }
519
520        // if(expr)
521        "if" => {
522            if let ClauseKind::Parenthesized(ref content) = clause.kind {
523                let content = content.as_ref();
524                // Check for directive-name modifier: "if(parallel: condition)"
525                if let Some((modifier, condition)) = lang::split_once_top_level(content, ':') {
526                    Ok(ClauseData::If {
527                        directive_name: Some(Identifier::new(modifier.trim())),
528                        condition: Expression::new(condition.trim(), config),
529                    })
530                } else {
531                    Ok(ClauseData::If {
532                        directive_name: None,
533                        condition: Expression::new(content.trim(), config),
534                    })
535                }
536            } else {
537                Err(ConversionError::InvalidClauseSyntax(
538                    "if clause requires parenthesized content".to_string(),
539                ))
540            }
541        }
542
543        // collapse(n)
544        "collapse" => {
545            if let ClauseKind::Parenthesized(ref content) = clause.kind {
546                let content = content.as_ref();
547                Ok(ClauseData::Collapse {
548                    n: Expression::new(content.trim(), config),
549                })
550            } else {
551                Err(ConversionError::InvalidClauseSyntax(
552                    "collapse requires expression".to_string(),
553                ))
554            }
555        }
556
557        // ordered or ordered(n)
558        "ordered" => match clause.kind {
559            ClauseKind::Bare => Ok(ClauseData::Ordered { n: None }),
560            ClauseKind::Parenthesized(ref content) => Ok(ClauseData::Ordered {
561                n: Some(Expression::new(content.as_ref().trim(), config)),
562            }),
563        },
564
565        // reduction(operator: list)
566        "reduction" => {
567            if let ClauseKind::Parenthesized(ref content) = clause.kind {
568                let content = content.as_ref();
569                // Find the colon separator between operator and list
570                if let Some((op_str, items_str)) = lang::split_once_top_level(content, ':') {
571                    // Parse the operator
572                    let operator = parse_reduction_operator(op_str.trim())?;
573
574                    // Parse the item list
575                    let items = parse_identifier_list(items_str.trim(), config)?;
576
577                    Ok(ClauseData::Reduction { operator, items })
578                } else {
579                    Err(ConversionError::InvalidClauseSyntax(
580                        "reduction clause requires 'operator: list' format".to_string(),
581                    ))
582                }
583            } else {
584                Err(ConversionError::InvalidClauseSyntax(
585                    "reduction clause requires parenthesized content".to_string(),
586                ))
587            }
588        }
589
590        // schedule([modifier[, modifier]:] kind[, chunk_size])
591        "schedule" => {
592            if let ClauseKind::Parenthesized(ref content) = clause.kind {
593                let content = content.as_ref();
594                parse_schedule_clause(content, config)
595            } else {
596                Err(ConversionError::InvalidClauseSyntax(
597                    "schedule clause requires parenthesized content".to_string(),
598                ))
599            }
600        }
601
602        // map([[mapper(mapper-identifier),] map-type:] list)
603        "map" => {
604            if let ClauseKind::Parenthesized(ref content) = clause.kind {
605                let content = content.as_ref();
606                parse_map_clause(content, config)
607            } else {
608                Err(ConversionError::InvalidClauseSyntax(
609                    "map clause requires parenthesized content".to_string(),
610                ))
611            }
612        }
613
614        // depend(dependence-type: list)
615        "depend" => {
616            if let ClauseKind::Parenthesized(ref content) = clause.kind {
617                let content = content.as_ref();
618                // Find the colon separator using top-level detection
619                if let Some((type_str, items_str)) = lang::split_once_top_level(content, ':') {
620                    // Parse the dependence type
621                    let depend_type = parse_depend_type(type_str.trim())?;
622
623                    // Parse the item list
624                    let items = parse_identifier_list(items_str.trim(), config)?;
625
626                    Ok(ClauseData::Depend { depend_type, items })
627                } else {
628                    Err(ConversionError::InvalidClauseSyntax(
629                        "depend clause requires 'type: list' format".to_string(),
630                    ))
631                }
632            } else {
633                Err(ConversionError::InvalidClauseSyntax(
634                    "depend clause requires parenthesized content".to_string(),
635                ))
636            }
637        }
638
639        // linear([modifier(list):] list[:step])
640        "linear" => {
641            if let ClauseKind::Parenthesized(ref content) = clause.kind {
642                let content = content.as_ref();
643                parse_linear_clause(content, config)
644            } else {
645                Err(ConversionError::InvalidClauseSyntax(
646                    "linear clause requires parenthesized content".to_string(),
647                ))
648            }
649        }
650
651        // proc_bind(master|close|spread|primary)
652        "proc_bind" => {
653            if let ClauseKind::Parenthesized(ref content) = clause.kind {
654                let content = content.as_ref();
655                let kind_str = content.trim();
656                let proc_bind = match kind_str {
657                    "master" => ProcBind::Master,
658                    "close" => ProcBind::Close,
659                    "spread" => ProcBind::Spread,
660                    "primary" => ProcBind::Primary,
661                    _ => {
662                        return Err(ConversionError::InvalidClauseSyntax(format!(
663                            "Unknown proc_bind kind: {kind_str}"
664                        )))
665                    }
666                };
667                Ok(ClauseData::ProcBind(proc_bind))
668            } else {
669                Err(ConversionError::InvalidClauseSyntax(
670                    "proc_bind clause requires parenthesized content".to_string(),
671                ))
672            }
673        }
674
675        // For unsupported clauses, return a generic representation
676        _ => Ok(ClauseData::Generic {
677            name: Identifier::new(clause_name),
678            data: match clause.kind {
679                ClauseKind::Bare => None,
680                ClauseKind::Parenthesized(ref content) => Some(content.as_ref().to_string()),
681            },
682        }),
683    }
684}
685
686/// Convert a parser Directive to IR DirectiveIR
687///
688/// ## Example
689///
690/// ```
691/// # use roup::parser::{Directive, Clause, ClauseKind};
692/// # use roup::ir::{convert::convert_directive, Language, SourceLocation, ParserConfig};
693/// let directive = Directive {
694///     name: "parallel".into(),
695///     clauses: vec![
696///         Clause {
697///             name: "default".into(),
698///             kind: ClauseKind::Parenthesized("shared".into()),
699///         },
700///     ],
701/// };
702///
703/// let config = ParserConfig::default();
704/// let ir = convert_directive(&directive, SourceLocation::start(), Language::C, &config).unwrap();
705/// assert!(ir.kind().is_parallel());
706/// ```
707pub fn convert_directive<'a>(
708    directive: &'a Directive<'a>,
709    location: SourceLocation,
710    language: Language,
711    config: &ParserConfig,
712) -> Result<DirectiveIR, ConversionError> {
713    // SAFETY FIX: Clone the directive name from Cow to owned String
714    // This prevents use-after-free when Cow::Owned is dropped.
715    // The normalized name (from line continuations) is now owned by DirectiveIR.
716    let directive_name = directive.name.to_string();
717
718    // Convert directive kind
719    let kind = parse_directive_kind(&directive_name)?;
720
721    // Convert clauses
722    let mut clauses = Vec::new();
723    let clause_config = config.for_language(language);
724    for clause in &directive.clauses {
725        let clause_data = parse_clause_data(clause, &clause_config)?;
726        clauses.push(clause_data);
727    }
728
729    Ok(DirectiveIR::new(
730        kind,
731        &directive_name,
732        clauses,
733        location,
734        language,
735    ))
736}
737
738// ============================================================================
739// Tests
740// ============================================================================
741
742#[cfg(test)]
743mod tests {
744    use super::*;
745
746    #[test]
747    fn test_parse_directive_kind_parallel() {
748        assert_eq!(
749            parse_directive_kind("parallel").unwrap(),
750            DirectiveKind::Parallel
751        );
752        assert_eq!(
753            parse_directive_kind("parallel for").unwrap(),
754            DirectiveKind::ParallelFor
755        );
756    }
757
758    #[test]
759    fn test_parse_directive_kind_case_insensitive() {
760        assert_eq!(
761            parse_directive_kind("PARALLEL").unwrap(),
762            DirectiveKind::Parallel
763        );
764        assert_eq!(
765            parse_directive_kind("Parallel For").unwrap(),
766            DirectiveKind::ParallelFor
767        );
768    }
769
770    #[test]
771    fn test_parse_directive_kind_whitespace() {
772        assert_eq!(
773            parse_directive_kind("  parallel  ").unwrap(),
774            DirectiveKind::Parallel
775        );
776    }
777
778    #[test]
779    fn test_parse_directive_kind_unknown() {
780        assert!(parse_directive_kind("unknown_directive").is_err());
781    }
782
783    #[test]
784    fn test_parse_identifier_list_single() {
785        let config = ParserConfig::with_parsing(Language::C);
786        let items = parse_identifier_list("x", &config).unwrap();
787        assert_eq!(items.len(), 1);
788    }
789
790    #[test]
791    fn test_parse_identifier_list_multiple() {
792        let config = ParserConfig::with_parsing(Language::C);
793        let items = parse_identifier_list("x, y, z", &config).unwrap();
794        assert_eq!(items.len(), 3);
795    }
796
797    #[test]
798    fn test_parse_identifier_list_with_spaces() {
799        let config = ParserConfig::with_parsing(Language::C);
800        let items = parse_identifier_list("  x  ,  y  ,  z  ", &config).unwrap();
801        assert_eq!(items.len(), 3);
802    }
803
804    #[test]
805    fn test_parse_identifier_list_empty() {
806        let config = ParserConfig::with_parsing(Language::C);
807        let items = parse_identifier_list("", &config).unwrap();
808        assert_eq!(items.len(), 0);
809    }
810
811    #[test]
812    fn test_parse_clause_data_bare() {
813        let clause = Clause {
814            name: "nowait".into(),
815            kind: ClauseKind::Bare,
816        };
817        let config = ParserConfig::default();
818        let data = parse_clause_data(&clause, &config).unwrap();
819        assert!(matches!(data, ClauseData::Bare(_)));
820        assert_eq!(data.to_string(), "nowait");
821    }
822
823    #[test]
824    fn test_parse_clause_data_default_shared() {
825        let clause = Clause {
826            name: "default".into(),
827            kind: ClauseKind::Parenthesized("shared".into()),
828        };
829        let config = ParserConfig::default();
830        let data = parse_clause_data(&clause, &config).unwrap();
831        assert_eq!(data, ClauseData::Default(DefaultKind::Shared));
832    }
833
834    #[test]
835    fn test_parse_clause_data_private() {
836        let clause = Clause {
837            name: "private".into(),
838            kind: ClauseKind::Parenthesized("x, y".into()),
839        };
840        let config = ParserConfig::default();
841        let data = parse_clause_data(&clause, &config).unwrap();
842        if let ClauseData::Private { items } = data {
843            assert_eq!(items.len(), 2);
844        } else {
845            panic!("Expected Private clause");
846        }
847    }
848
849    #[test]
850    fn test_parse_clause_data_num_threads() {
851        let clause = Clause {
852            name: "num_threads".into(),
853            kind: ClauseKind::Parenthesized("4".into()),
854        };
855        let config = ParserConfig::default();
856        let data = parse_clause_data(&clause, &config).unwrap();
857        assert!(matches!(data, ClauseData::NumThreads { .. }));
858    }
859
860    #[test]
861    fn test_parse_clause_data_if_simple() {
862        let clause = Clause {
863            name: "if".into(),
864            kind: ClauseKind::Parenthesized("n > 100".into()),
865        };
866        let config = ParserConfig::default();
867        let data = parse_clause_data(&clause, &config).unwrap();
868        if let ClauseData::If {
869            directive_name,
870            condition,
871        } = data
872        {
873            assert!(directive_name.is_none());
874            assert_eq!(condition.to_string(), "n > 100");
875        } else {
876            panic!("Expected If clause");
877        }
878    }
879
880    #[test]
881    fn test_parse_clause_data_if_with_modifier() {
882        let clause = Clause {
883            name: "if".into(),
884            kind: ClauseKind::Parenthesized("parallel: n > 100".into()),
885        };
886        let config = ParserConfig::default();
887        let data = parse_clause_data(&clause, &config).unwrap();
888        if let ClauseData::If {
889            directive_name,
890            condition,
891        } = data
892        {
893            assert!(directive_name.is_some());
894            assert_eq!(directive_name.unwrap().to_string(), "parallel");
895            assert_eq!(condition.to_string(), "n > 100");
896        } else {
897            panic!("Expected If clause");
898        }
899    }
900
901    #[test]
902    fn test_convert_directive_simple() {
903        let directive = Directive {
904            name: "parallel".into(),
905            clauses: vec![],
906        };
907        let config = ParserConfig::default();
908        let ir =
909            convert_directive(&directive, SourceLocation::start(), Language::C, &config).unwrap();
910        assert_eq!(ir.kind(), DirectiveKind::Parallel);
911        assert_eq!(ir.clauses().len(), 0);
912    }
913
914    #[test]
915    fn test_convert_directive_with_clauses() {
916        let directive = Directive {
917            name: "parallel".into(),
918            clauses: vec![
919                Clause {
920                    name: "default".into(),
921                    kind: ClauseKind::Parenthesized("shared".into()),
922                },
923                Clause {
924                    name: "private".into(),
925                    kind: ClauseKind::Parenthesized("x".into()),
926                },
927            ],
928        };
929        let config = ParserConfig::default();
930        let ir =
931            convert_directive(&directive, SourceLocation::start(), Language::C, &config).unwrap();
932        assert_eq!(ir.kind(), DirectiveKind::Parallel);
933        assert_eq!(ir.clauses().len(), 2);
934    }
935
936    // Tests for reduction operator parsing
937    #[test]
938    fn test_parse_reduction_operator_arithmetic() {
939        assert_eq!(
940            parse_reduction_operator("+").unwrap(),
941            ReductionOperator::Add
942        );
943        assert_eq!(
944            parse_reduction_operator("-").unwrap(),
945            ReductionOperator::Subtract
946        );
947        assert_eq!(
948            parse_reduction_operator("*").unwrap(),
949            ReductionOperator::Multiply
950        );
951    }
952
953    #[test]
954    fn test_parse_reduction_operator_bitwise() {
955        assert_eq!(
956            parse_reduction_operator("&").unwrap(),
957            ReductionOperator::BitwiseAnd
958        );
959        assert_eq!(
960            parse_reduction_operator("|").unwrap(),
961            ReductionOperator::BitwiseOr
962        );
963        assert_eq!(
964            parse_reduction_operator("^").unwrap(),
965            ReductionOperator::BitwiseXor
966        );
967    }
968
969    #[test]
970    fn test_parse_reduction_operator_logical() {
971        assert_eq!(
972            parse_reduction_operator("&&").unwrap(),
973            ReductionOperator::LogicalAnd
974        );
975        assert_eq!(
976            parse_reduction_operator("||").unwrap(),
977            ReductionOperator::LogicalOr
978        );
979    }
980
981    #[test]
982    fn test_parse_reduction_operator_minmax() {
983        assert_eq!(
984            parse_reduction_operator("min").unwrap(),
985            ReductionOperator::Min
986        );
987        assert_eq!(
988            parse_reduction_operator("max").unwrap(),
989            ReductionOperator::Max
990        );
991    }
992
993    #[test]
994    fn test_parse_reduction_operator_unknown() {
995        assert!(parse_reduction_operator("unknown").is_err());
996    }
997
998    // Tests for reduction clause
999    #[test]
1000    fn test_parse_clause_data_reduction() {
1001        let clause = Clause {
1002            name: "reduction".into(),
1003            kind: ClauseKind::Parenthesized("+: sum".into()),
1004        };
1005        let config = ParserConfig::default();
1006        let data = parse_clause_data(&clause, &config).unwrap();
1007        if let ClauseData::Reduction { operator, items } = data {
1008            assert_eq!(operator, ReductionOperator::Add);
1009            assert_eq!(items.len(), 1);
1010        } else {
1011            panic!("Expected Reduction clause");
1012        }
1013    }
1014
1015    #[test]
1016    fn test_parse_clause_data_reduction_multiple_items() {
1017        let clause = Clause {
1018            name: "reduction".into(),
1019            kind: ClauseKind::Parenthesized("*: a, b, c".into()),
1020        };
1021        let config = ParserConfig::default();
1022        let data = parse_clause_data(&clause, &config).unwrap();
1023        if let ClauseData::Reduction { operator, items } = data {
1024            assert_eq!(operator, ReductionOperator::Multiply);
1025            assert_eq!(items.len(), 3);
1026        } else {
1027            panic!("Expected Reduction clause");
1028        }
1029    }
1030
1031    #[test]
1032    fn test_parse_clause_data_reduction_minmax() {
1033        let clause = Clause {
1034            name: "reduction".into(),
1035            kind: ClauseKind::Parenthesized("min: value".into()),
1036        };
1037        let config = ParserConfig::default();
1038        let data = parse_clause_data(&clause, &config).unwrap();
1039        if let ClauseData::Reduction { operator, items } = data {
1040            assert_eq!(operator, ReductionOperator::Min);
1041            assert_eq!(items.len(), 1);
1042        } else {
1043            panic!("Expected Reduction clause");
1044        }
1045    }
1046
1047    // Tests for schedule clause
1048    #[test]
1049    fn test_parse_schedule_clause_static() {
1050        let config = ParserConfig::with_parsing(Language::C);
1051        let data = parse_schedule_clause("static", &config).unwrap();
1052        if let ClauseData::Schedule {
1053            kind,
1054            modifiers,
1055            chunk_size,
1056        } = data
1057        {
1058            assert_eq!(kind, ScheduleKind::Static);
1059            assert!(modifiers.is_empty());
1060            assert!(chunk_size.is_none());
1061        } else {
1062            panic!("Expected Schedule clause");
1063        }
1064    }
1065
1066    #[test]
1067    fn test_parse_schedule_clause_with_chunk() {
1068        let config = ParserConfig::with_parsing(Language::C);
1069        let data = parse_schedule_clause("dynamic, 10", &config).unwrap();
1070        if let ClauseData::Schedule {
1071            kind,
1072            modifiers,
1073            chunk_size,
1074        } = data
1075        {
1076            assert_eq!(kind, ScheduleKind::Dynamic);
1077            assert!(modifiers.is_empty());
1078            assert!(chunk_size.is_some());
1079            assert_eq!(chunk_size.unwrap().to_string(), "10");
1080        } else {
1081            panic!("Expected Schedule clause");
1082        }
1083    }
1084
1085    #[test]
1086    fn test_parse_schedule_clause_with_modifier() {
1087        let config = ParserConfig::with_parsing(Language::C);
1088        let data = parse_schedule_clause("monotonic: static, 4", &config).unwrap();
1089        if let ClauseData::Schedule {
1090            kind,
1091            modifiers,
1092            chunk_size,
1093        } = data
1094        {
1095            assert_eq!(kind, ScheduleKind::Static);
1096            assert_eq!(modifiers.len(), 1);
1097            assert_eq!(modifiers[0], ScheduleModifier::Monotonic);
1098            assert!(chunk_size.is_some());
1099        } else {
1100            panic!("Expected Schedule clause");
1101        }
1102    }
1103
1104    #[test]
1105    fn test_parse_schedule_clause_with_multiple_modifiers() {
1106        let config = ParserConfig::with_parsing(Language::C);
1107        let data = parse_schedule_clause("monotonic, simd: dynamic", &config).unwrap();
1108        if let ClauseData::Schedule {
1109            kind,
1110            modifiers,
1111            chunk_size,
1112        } = data
1113        {
1114            assert_eq!(kind, ScheduleKind::Dynamic);
1115            assert_eq!(modifiers.len(), 2);
1116            assert!(chunk_size.is_none());
1117        } else {
1118            panic!("Expected Schedule clause");
1119        }
1120    }
1121
1122    // Tests for map clause
1123    #[test]
1124    fn test_parse_map_clause_with_type() {
1125        let config = ParserConfig::with_parsing(Language::C);
1126        let data = parse_map_clause("to: arr", &config).unwrap();
1127        if let ClauseData::Map {
1128            map_type,
1129            mapper,
1130            items,
1131        } = data
1132        {
1133            assert_eq!(map_type, Some(MapType::To));
1134            assert!(mapper.is_none());
1135            assert_eq!(items.len(), 1);
1136        } else {
1137            panic!("Expected Map clause");
1138        }
1139    }
1140
1141    #[test]
1142    fn test_parse_map_clause_tofrom() {
1143        let config = ParserConfig::with_parsing(Language::C);
1144        let data = parse_map_clause("tofrom: x, y, z", &config).unwrap();
1145        if let ClauseData::Map {
1146            map_type,
1147            mapper,
1148            items,
1149        } = data
1150        {
1151            assert_eq!(map_type, Some(MapType::ToFrom));
1152            assert!(mapper.is_none());
1153            assert_eq!(items.len(), 3);
1154        } else {
1155            panic!("Expected Map clause");
1156        }
1157    }
1158
1159    #[test]
1160    fn test_parse_map_clause_without_type() {
1161        let config = ParserConfig::with_parsing(Language::C);
1162        let data = parse_map_clause("var1, var2", &config).unwrap();
1163        if let ClauseData::Map {
1164            map_type,
1165            mapper,
1166            items,
1167        } = data
1168        {
1169            assert!(map_type.is_none());
1170            assert!(mapper.is_none());
1171            assert_eq!(items.len(), 2);
1172        } else {
1173            panic!("Expected Map clause");
1174        }
1175    }
1176
1177    #[test]
1178    fn test_parse_map_clause_with_array_section() {
1179        let config = ParserConfig::with_parsing(Language::C);
1180        let data = parse_map_clause("to: arr[0:N:2]", &config).unwrap();
1181        if let ClauseData::Map { items, .. } = data {
1182            match &items[0] {
1183                ClauseItem::Variable(var) => {
1184                    assert_eq!(var.name(), "arr");
1185                    assert_eq!(var.array_sections.len(), 1);
1186                    let section = &var.array_sections[0];
1187                    assert!(section.lower_bound.is_some());
1188                    assert!(section.length.is_some());
1189                    assert!(section.stride.is_some());
1190                }
1191                other => panic!("Expected variable, got {other:?}"),
1192            }
1193        } else {
1194            panic!("Expected Map clause");
1195        }
1196    }
1197
1198    #[test]
1199    fn test_parse_map_clause_with_mapper() {
1200        let config = ParserConfig::with_parsing(Language::C);
1201        let data = parse_map_clause("mapper(custom), to: arr[0:N]", &config).unwrap();
1202        if let ClauseData::Map {
1203            map_type,
1204            mapper,
1205            items,
1206        } = data
1207        {
1208            assert_eq!(map_type, Some(MapType::To));
1209            assert_eq!(mapper.unwrap().to_string(), "custom");
1210            assert_eq!(items.len(), 1);
1211            assert!(matches!(items[0], ClauseItem::Variable(_)));
1212        } else {
1213            panic!("Expected Map clause with mapper");
1214        }
1215    }
1216
1217    // Tests for depend clause
1218    #[test]
1219    fn test_parse_depend_type() {
1220        assert_eq!(parse_depend_type("in").unwrap(), DependType::In);
1221        assert_eq!(parse_depend_type("out").unwrap(), DependType::Out);
1222        assert_eq!(parse_depend_type("inout").unwrap(), DependType::Inout);
1223    }
1224
1225    #[test]
1226    fn test_parse_clause_data_depend() {
1227        let clause = Clause {
1228            name: "depend".into(),
1229            kind: ClauseKind::Parenthesized("in: x, y".into()),
1230        };
1231        let config = ParserConfig::default();
1232        let data = parse_clause_data(&clause, &config).unwrap();
1233        if let ClauseData::Depend { depend_type, items } = data {
1234            assert_eq!(depend_type, DependType::In);
1235            assert_eq!(items.len(), 2);
1236        } else {
1237            panic!("Expected Depend clause");
1238        }
1239    }
1240
1241    // Tests for linear clause
1242    #[test]
1243    fn test_parse_linear_clause_simple() {
1244        let config = ParserConfig::with_parsing(Language::C);
1245        let data = parse_linear_clause("x, y", &config).unwrap();
1246        if let ClauseData::Linear {
1247            modifier,
1248            items,
1249            step,
1250        } = data
1251        {
1252            assert!(modifier.is_none());
1253            assert_eq!(items.len(), 2);
1254            assert!(step.is_none());
1255        } else {
1256            panic!("Expected Linear clause");
1257        }
1258    }
1259
1260    #[test]
1261    fn test_parse_linear_clause_with_step() {
1262        let config = ParserConfig::with_parsing(Language::C);
1263        let data = parse_linear_clause("i: 2", &config).unwrap();
1264        if let ClauseData::Linear {
1265            modifier,
1266            items,
1267            step,
1268        } = data
1269        {
1270            assert!(modifier.is_none());
1271            assert_eq!(items.len(), 1);
1272            assert!(step.is_some());
1273            assert_eq!(step.unwrap().to_string(), "2");
1274        } else {
1275            panic!("Expected Linear clause");
1276        }
1277    }
1278
1279    #[test]
1280    fn test_parse_clause_data_fortran_private_variables() {
1281        let clause = Clause {
1282            name: "private".into(),
1283            kind: ClauseKind::Parenthesized("A(1:N), B(:, :)".into()),
1284        };
1285        let config = ParserConfig::with_parsing(Language::Fortran);
1286        let data = parse_clause_data(&clause, &config).unwrap();
1287        if let ClauseData::Private { items } = data {
1288            assert_eq!(items.len(), 2);
1289            match &items[0] {
1290                ClauseItem::Variable(var) => {
1291                    assert_eq!(var.name(), "A");
1292                    assert_eq!(var.array_sections.len(), 1);
1293                }
1294                other => panic!("expected variable, got {other:?}"),
1295            }
1296        } else {
1297            panic!("Expected Private clause");
1298        }
1299    }
1300
1301    // Tests for proc_bind clause
1302    #[test]
1303    fn test_parse_clause_data_proc_bind() {
1304        let clause = Clause {
1305            name: "proc_bind".into(),
1306            kind: ClauseKind::Parenthesized("close".into()),
1307        };
1308        let config = ParserConfig::default();
1309        let data = parse_clause_data(&clause, &config).unwrap();
1310        assert_eq!(data, ClauseData::ProcBind(ProcBind::Close));
1311    }
1312}