roup/ir/
variable.rs

1//! Variable and identifier representation
2//!
3//! This module defines types for representing identifiers and variables
4//! in OpenMP clauses. The key distinction:
5//!
6//! - **Identifier**: Simple name (e.g., `my_var`, `omp_default_mem_alloc`)
7//! - **Variable**: Name with optional array sections (e.g., `arr[0:N]`, `mat[i][j:k]`)
8//!
9//! ## Learning Objectives
10//!
11//! - **Nested structures**: Variable contains Vec of ArraySection
12//! - **Composition**: ArraySection uses Expression type
13//! - **Semantic clarity**: Types document intent
14//! - **String slices**: Using `&'a str` for zero-copy references
15//!
16//! ## Design Philosophy
17//!
18//! OpenMP clauses often work with variables that may be:
19//! 1. Simple scalars: `private(x, y, z)`
20//! 2. Array sections: `map(to: arr[0:N])`
21//! 3. Struct members: `private(point.x)` (not yet supported)
22//!
23//! We model this with clear types that preserve the original syntax
24//! while providing semantic structure.
25
26use std::fmt;
27
28use super::Expression;
29
30// ============================================================================
31// Identifier: Simple names
32// ============================================================================
33
34/// A simple identifier (not an expression, not a variable with sections)
35///
36/// Used for names that appear in various contexts:
37/// - Variable names: `x`, `my_var`
38/// - Function names: `my_function`
39/// - Allocator names: `omp_default_mem_alloc`
40/// - Mapper names: `my_mapper`
41/// - User-defined reduction operators: `my_reduction_op`
42///
43/// ## Learning: Newtype Pattern
44///
45/// This is a "newtype" - a struct with a single field that wraps another type.
46/// Why not just use `&str` directly?
47///
48/// 1. **Type safety**: Can't accidentally pass an expression where identifier expected
49/// 2. **Semantic clarity**: Code documents intent
50/// 3. **Future extension**: Can add validation, normalization, etc.
51/// 4. **Zero cost**: Compiler optimizes away the wrapper
52///
53/// ## Example
54///
55/// ```
56/// use roup::ir::Identifier;
57///
58/// let id = Identifier::new("my_var");
59/// assert_eq!(id.name(), "my_var");
60/// assert_eq!(format!("{}", id), "my_var");
61/// ```
62#[derive(Debug, Clone, PartialEq, Eq, Hash)]
63pub struct Identifier {
64    name: String,
65}
66
67impl Identifier {
68    /// Create a new identifier
69    ///
70    /// The name is trimmed of whitespace.
71    ///
72    /// ## Example
73    ///
74    /// ```
75    /// use roup::ir::Identifier;
76    ///
77    /// let id = Identifier::new("  my_var  ");
78    /// assert_eq!(id.name(), "my_var");
79    /// ```
80    pub fn new(name: impl Into<String>) -> Self {
81        let name = name.into();
82        Self {
83            name: name.trim().to_string(),
84        }
85    }
86
87    /// Get the identifier name
88    pub fn name(&self) -> &str {
89        &self.name
90    }
91
92    /// Get the identifier as a string slice
93    pub fn as_str(&self) -> &str {
94        &self.name
95    }
96}
97
98impl From<&str> for Identifier {
99    fn from(s: &str) -> Self {
100        Identifier::new(s)
101    }
102}
103
104impl From<String> for Identifier {
105    fn from(s: String) -> Self {
106        Identifier::new(s)
107    }
108}
109
110impl fmt::Display for Identifier {
111    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112        write!(f, "{}", self.name)
113    }
114}
115
116// ============================================================================
117// ArraySection: Array slicing specification
118// ============================================================================
119
120/// Array section specification: `[lower:length:stride]`
121///
122/// OpenMP allows specifying portions of arrays using array sections:
123/// - `arr[0:N]` - elements 0 through N-1
124/// - `arr[i:10:2]` - 10 elements starting at i, every 2nd element
125/// - `arr[:]` - all elements
126///
127/// ## Learning: Optional Fields
128///
129/// All three parts (lower, length, stride) are optional! This is
130/// modeled with `Option<Expression>`:
131/// - `Some(expr)` - part is present
132/// - `None` - part is omitted
133///
134/// ## Syntax Examples
135///
136/// | OpenMP Syntax | lower | length | stride |
137/// |---------------|-------|--------|--------|
138/// | `arr[0:N]` | Some(0) | Some(N) | None |
139/// | `arr[:]` | None | None | None |
140/// | `arr[i:10:2]` | Some(i) | Some(10) | Some(2) |
141/// | `arr[i]` | Some(i) | None | None |
142///
143/// ## Example
144///
145/// ```
146/// use roup::ir::{ArraySection, Expression, ParserConfig};
147///
148/// let config = ParserConfig::default();
149///
150/// // arr[0:N]
151/// let section = ArraySection {
152///     lower_bound: Some(Expression::new("0", &config)),
153///     length: Some(Expression::new("N", &config)),
154///     stride: None,
155/// };
156/// ```
157#[derive(Debug, Clone, PartialEq)]
158pub struct ArraySection {
159    /// Lower bound (starting index)
160    ///
161    /// If `None`, starts at beginning (equivalent to 0)
162    pub lower_bound: Option<Expression>,
163
164    /// Length (number of elements)
165    ///
166    /// If `None`, goes to end of dimension
167    pub length: Option<Expression>,
168
169    /// Stride (spacing between elements)
170    ///
171    /// If `None`, defaults to 1 (consecutive elements)
172    pub stride: Option<Expression>,
173}
174
175impl ArraySection {
176    /// Create a new array section with all fields
177    pub fn new(
178        lower_bound: Option<Expression>,
179        length: Option<Expression>,
180        stride: Option<Expression>,
181    ) -> Self {
182        Self {
183            lower_bound,
184            length,
185            stride,
186        }
187    }
188
189    /// Create an array section for a single index: `arr[i]`
190    ///
191    /// ## Example
192    ///
193    /// ```
194    /// use roup::ir::{ArraySection, Expression, ParserConfig};
195    ///
196    /// let config = ParserConfig::default();
197    /// let section = ArraySection::single_index(Expression::new("i", &config));
198    /// // Represents arr[i]
199    /// ```
200    pub fn single_index(index: Expression) -> Self {
201        Self {
202            lower_bound: Some(index),
203            length: None,
204            stride: None,
205        }
206    }
207
208    /// Create an array section for all elements: `arr[:]`
209    ///
210    /// ## Example
211    ///
212    /// ```
213    /// use roup::ir::ArraySection;
214    ///
215    /// let section = ArraySection::all();
216    /// // Represents arr[:]
217    /// ```
218    pub const fn all() -> Self {
219        Self {
220            lower_bound: None,
221            length: None,
222            stride: None,
223        }
224    }
225
226    /// Check if this represents a single index access
227    pub fn is_single_index(&self) -> bool {
228        self.lower_bound.is_some() && self.length.is_none() && self.stride.is_none()
229    }
230
231    /// Check if this represents all elements
232    pub fn is_all(&self) -> bool {
233        self.lower_bound.is_none() && self.length.is_none() && self.stride.is_none()
234    }
235}
236
237impl fmt::Display for ArraySection {
238    /// Format as OpenMP array section syntax
239    ///
240    /// ## Example
241    ///
242    /// ```
243    /// use roup::ir::{ArraySection, Expression, ParserConfig};
244    ///
245    /// let config = ParserConfig::default();
246    /// let section = ArraySection::new(
247    ///     Some(Expression::new("0", &config)),
248    ///     Some(Expression::new("N", &config)),
249    ///     None,
250    /// );
251    /// assert_eq!(format!("{}", section), "0:N");
252    /// ```
253    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
254        // Format: [lower:length:stride]
255        // Omitted parts are skipped, but colons are preserved
256
257        if let Some(lower) = &self.lower_bound {
258            write!(f, "{lower}")?;
259        }
260
261        if self.length.is_some() || self.stride.is_some() {
262            write!(f, ":")?;
263        }
264
265        if let Some(length) = &self.length {
266            write!(f, "{length}")?;
267        }
268
269        if self.stride.is_some() {
270            write!(f, ":")?;
271        }
272
273        if let Some(stride) = &self.stride {
274            write!(f, "{stride}")?;
275        }
276
277        Ok(())
278    }
279}
280
281// ============================================================================
282// Variable: Name with optional array sections
283// ============================================================================
284
285/// A variable reference, possibly with array sections
286///
287/// Variables in OpenMP clauses can be:
288/// - Simple: `x`, `my_var`
289/// - Array elements: `arr[i]`
290/// - Array sections: `arr[0:N]`
291/// - Multidimensional: `matrix[i][0:N]`
292///
293/// ## Learning: Composition
294///
295/// Notice how `Variable` is built from other IR types:
296/// - Uses `&'a str` for the name (borrowed from source)
297/// - Uses `Vec<ArraySection>` for subscripts
298/// - `ArraySection` uses `Expression`
299///
300/// This shows how complex structures are built from simple parts.
301///
302/// ## Example
303///
304/// ```
305/// use roup::ir::{Variable, ArraySection, Expression, ParserConfig};
306///
307/// let config = ParserConfig::default();
308///
309/// // Simple variable: x
310/// let simple = Variable::new("x");
311/// assert_eq!(simple.name(), "x");
312/// assert!(simple.is_scalar());
313///
314/// // Array section: arr[0:N]
315/// let array = Variable::with_sections(
316///     "arr",
317///     vec![ArraySection::new(
318///         Some(Expression::new("0", &config)),
319///         Some(Expression::new("N", &config)),
320///         None,
321///     )]
322/// );
323/// assert!(!array.is_scalar());
324/// ```
325#[derive(Debug, Clone, PartialEq)]
326pub struct Variable {
327    /// Variable name
328    name: String,
329
330    /// Array sections (empty for scalar variables)
331    ///
332    /// Each element represents one dimension:
333    /// - `arr[i]` → 1 section
334    /// - `matrix[i][j]` → 2 sections
335    /// - `tensor[i][j][k]` → 3 sections
336    pub array_sections: Vec<ArraySection>,
337}
338
339impl Variable {
340    /// Create a new variable without array sections (scalar)
341    ///
342    /// ## Example
343    ///
344    /// ```
345    /// use roup::ir::Variable;
346    ///
347    /// let var = Variable::new("x");
348    /// assert_eq!(var.name(), "x");
349    /// assert!(var.is_scalar());
350    /// ```
351    pub fn new(name: impl Into<String>) -> Self {
352        let name = name.into();
353        Self {
354            name: name.trim().to_string(),
355            array_sections: Vec::new(),
356        }
357    }
358
359    /// Create a variable with array sections
360    ///
361    /// ## Example
362    ///
363    /// ```
364    /// use roup::ir::{Variable, ArraySection};
365    ///
366    /// let var = Variable::with_sections(
367    ///     "arr",
368    ///     vec![ArraySection::all()]
369    /// );
370    /// assert_eq!(var.name(), "arr");
371    /// assert!(!var.is_scalar());
372    /// ```
373    pub fn with_sections(name: impl Into<String>, sections: Vec<ArraySection>) -> Self {
374        let name = name.into();
375        Self {
376            name: name.trim().to_string(),
377            array_sections: sections,
378        }
379    }
380
381    /// Get the variable name
382    pub fn name(&self) -> &str {
383        &self.name
384    }
385
386    /// Check if this is a scalar (no array sections)
387    pub fn is_scalar(&self) -> bool {
388        self.array_sections.is_empty()
389    }
390
391    /// Check if this is an array (has sections)
392    pub fn is_array(&self) -> bool {
393        !self.array_sections.is_empty()
394    }
395
396    /// Get the number of dimensions
397    ///
398    /// Returns 0 for scalars, 1+ for arrays.
399    pub fn dimensions(&self) -> usize {
400        self.array_sections.len()
401    }
402}
403
404impl From<&str> for Variable {
405    fn from(name: &str) -> Self {
406        Variable::new(name)
407    }
408}
409
410impl From<String> for Variable {
411    fn from(name: String) -> Self {
412        Variable::new(name)
413    }
414}
415
416impl From<Identifier> for Variable {
417    fn from(id: Identifier) -> Self {
418        Variable::new(id.name())
419    }
420}
421
422impl fmt::Display for Variable {
423    /// Format as OpenMP variable syntax
424    ///
425    /// ## Example
426    ///
427    /// ```
428    /// use roup::ir::{Variable, ArraySection, Expression, ParserConfig};
429    ///
430    /// let config = ParserConfig::default();
431    ///
432    /// // Scalar
433    /// let scalar = Variable::new("x");
434    /// assert_eq!(format!("{}", scalar), "x");
435    ///
436    /// // Array section
437    /// let array = Variable::with_sections(
438    ///     "arr",
439    ///     vec![ArraySection::new(
440    ///         Some(Expression::new("0", &config)),
441    ///         Some(Expression::new("N", &config)),
442    ///         None,
443    ///     )]
444    /// );
445    /// assert_eq!(format!("{}", array), "arr[0:N]");
446    /// ```
447    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
448        write!(f, "{}", self.name)?;
449
450        for section in &self.array_sections {
451            write!(f, "[{section}]")?;
452        }
453
454        Ok(())
455    }
456}
457
458// ============================================================================
459// Tests
460// ============================================================================
461
462#[cfg(test)]
463mod tests {
464    use super::*;
465    use crate::ir::ParserConfig;
466
467    // ------------------------------------------------------------------------
468    // Identifier tests
469    // ------------------------------------------------------------------------
470
471    #[test]
472    fn identifier_new_trims_whitespace() {
473        let id = Identifier::new("  my_var  ");
474        assert_eq!(id.name(), "my_var");
475    }
476
477    #[test]
478    fn identifier_from_str() {
479        let id: Identifier = "test".into();
480        assert_eq!(id.name(), "test");
481    }
482
483    #[test]
484    fn identifier_display() {
485        let id = Identifier::new("my_var");
486        assert_eq!(format!("{id}"), "my_var");
487    }
488
489    #[test]
490    fn identifier_equality() {
491        let id1 = Identifier::new("x");
492        let id2 = Identifier::new("x");
493        let id3 = Identifier::new("y");
494
495        assert_eq!(id1, id2);
496        assert_ne!(id1, id3);
497    }
498
499    // ------------------------------------------------------------------------
500    // ArraySection tests
501    // ------------------------------------------------------------------------
502
503    #[test]
504    fn array_section_single_index() {
505        let config = ParserConfig::default();
506        let section = ArraySection::single_index(Expression::new("i", &config));
507
508        assert!(section.is_single_index());
509        assert!(!section.is_all());
510        assert!(section.lower_bound.is_some());
511        assert!(section.length.is_none());
512        assert!(section.stride.is_none());
513    }
514
515    #[test]
516    fn array_section_all() {
517        let section = ArraySection::all();
518
519        assert!(!section.is_single_index());
520        assert!(section.is_all());
521        assert!(section.lower_bound.is_none());
522        assert!(section.length.is_none());
523        assert!(section.stride.is_none());
524    }
525
526    #[test]
527    fn array_section_display_single_index() {
528        let config = ParserConfig::default();
529        let section = ArraySection::single_index(Expression::new("i", &config));
530
531        assert_eq!(format!("{section}"), "i");
532    }
533
534    #[test]
535    fn array_section_display_range() {
536        let config = ParserConfig::default();
537        let section = ArraySection::new(
538            Some(Expression::new("0", &config)),
539            Some(Expression::new("N", &config)),
540            None,
541        );
542
543        assert_eq!(format!("{section}"), "0:N");
544    }
545
546    #[test]
547    fn array_section_display_with_stride() {
548        let config = ParserConfig::default();
549        let section = ArraySection::new(
550            Some(Expression::new("0", &config)),
551            Some(Expression::new("N", &config)),
552            Some(Expression::new("2", &config)),
553        );
554
555        assert_eq!(format!("{section}"), "0:N:2");
556    }
557
558    #[test]
559    fn array_section_display_all() {
560        let section = ArraySection::all();
561        assert_eq!(format!("{section}"), "");
562    }
563
564    #[test]
565    fn array_section_display_omitted_lower() {
566        let config = ParserConfig::default();
567        let section = ArraySection::new(None, Some(Expression::new("N", &config)), None);
568
569        assert_eq!(format!("{section}"), ":N");
570    }
571
572    // ------------------------------------------------------------------------
573    // Variable tests
574    // ------------------------------------------------------------------------
575
576    #[test]
577    fn variable_new_creates_scalar() {
578        let var = Variable::new("x");
579
580        assert_eq!(var.name(), "x");
581        assert!(var.is_scalar());
582        assert!(!var.is_array());
583        assert_eq!(var.dimensions(), 0);
584    }
585
586    #[test]
587    fn variable_with_sections_creates_array() {
588        let config = ParserConfig::default();
589        let var = Variable::with_sections(
590            "arr",
591            vec![ArraySection::single_index(Expression::new("i", &config))],
592        );
593
594        assert_eq!(var.name(), "arr");
595        assert!(!var.is_scalar());
596        assert!(var.is_array());
597        assert_eq!(var.dimensions(), 1);
598    }
599
600    #[test]
601    fn variable_multidimensional() {
602        let config = ParserConfig::default();
603        let var = Variable::with_sections(
604            "matrix",
605            vec![
606                ArraySection::single_index(Expression::new("i", &config)),
607                ArraySection::single_index(Expression::new("j", &config)),
608            ],
609        );
610
611        assert_eq!(var.dimensions(), 2);
612    }
613
614    #[test]
615    fn variable_from_str() {
616        let var: Variable = "x".into();
617        assert_eq!(var.name(), "x");
618        assert!(var.is_scalar());
619    }
620
621    #[test]
622    fn variable_from_identifier() {
623        let id = Identifier::new("my_var");
624        let var: Variable = id.into();
625        assert_eq!(var.name(), "my_var");
626        assert!(var.is_scalar());
627    }
628
629    #[test]
630    fn variable_display_scalar() {
631        let var = Variable::new("x");
632        assert_eq!(format!("{var}"), "x");
633    }
634
635    #[test]
636    fn variable_display_single_index() {
637        let config = ParserConfig::default();
638        let var = Variable::with_sections(
639            "arr",
640            vec![ArraySection::single_index(Expression::new("i", &config))],
641        );
642
643        assert_eq!(format!("{var}"), "arr[i]");
644    }
645
646    #[test]
647    fn variable_display_array_section() {
648        let config = ParserConfig::default();
649        let var = Variable::with_sections(
650            "arr",
651            vec![ArraySection::new(
652                Some(Expression::new("0", &config)),
653                Some(Expression::new("N", &config)),
654                None,
655            )],
656        );
657
658        assert_eq!(format!("{var}"), "arr[0:N]");
659    }
660
661    #[test]
662    fn variable_display_multidimensional() {
663        let config = ParserConfig::default();
664        let var = Variable::with_sections(
665            "matrix",
666            vec![
667                ArraySection::single_index(Expression::new("i", &config)),
668                ArraySection::new(
669                    Some(Expression::new("0", &config)),
670                    Some(Expression::new("N", &config)),
671                    None,
672                ),
673            ],
674        );
675
676        assert_eq!(format!("{var}"), "matrix[i][0:N]");
677    }
678
679    #[test]
680    fn variable_trims_name() {
681        let var = Variable::new("  arr  ");
682        assert_eq!(var.name(), "arr");
683    }
684}