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}