roup/ir/types.rs
1//! Basic IR types: SourceLocation and Language
2//!
3//! This file introduces fundamental concepts:
4//! - **Structs**: Composite data types with named fields
5//! - **Copy trait**: Types that can be copied bitwise
6//! - **repr(C)**: C-compatible memory layout for FFI
7//! - **Derive macros**: Automatic trait implementations
8//! - **Documentation**: How to document Rust code properly
9
10use std::fmt;
11
12// ============================================================================
13// SourceLocation: Track where code appears in source files
14// ============================================================================
15
16/// Source code location information
17///
18/// Stores the line and column number where a directive or clause appears
19/// in the original source file. This is useful for:
20///
21/// - **Error reporting**: "Error at line 42, column 5"
22/// - **IDE integration**: Jump to source location
23/// - **Debugging**: Know where IR came from
24///
25/// ## Learning: The `Copy` Trait
26///
27/// This struct implements `Copy`, meaning it can be duplicated by simple
28/// bitwise copy (like `memcpy` in C). This is efficient for small types.
29///
30/// Types that implement `Copy` must also implement `Clone`. The difference:
31/// - `Copy`: Implicit duplication (assignment creates a copy)
32/// - `Clone`: Explicit duplication (call `.clone()` method)
33///
34/// ## Learning: `repr(C)`
35///
36/// The `#[repr(C)]` attribute tells Rust to lay out this struct in memory
37/// exactly like C would. This is critical for FFI (Foreign Function Interface).
38///
39/// Without `repr(C)`, Rust might reorder fields for optimization.
40/// With `repr(C)`, fields appear in declaration order.
41///
42/// ## Example
43///
44/// ```
45/// use roup::ir::SourceLocation;
46///
47/// let loc = SourceLocation { line: 42, column: 5 };
48/// let copy = loc; // Implicitly copied due to Copy trait
49/// assert_eq!(loc.line, 42);
50/// assert_eq!(copy.line, 42); // Original still valid
51/// ```
52#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
53#[repr(C)]
54pub struct SourceLocation {
55 /// Line number (1-indexed, as in editors)
56 pub line: u32,
57
58 /// Column number (1-indexed, as in editors)
59 pub column: u32,
60}
61
62impl SourceLocation {
63 /// Create a new source location
64 ///
65 /// ## Example
66 ///
67 /// ```
68 /// use roup::ir::SourceLocation;
69 ///
70 /// let loc = SourceLocation::new(10, 5);
71 /// assert_eq!(loc.line, 10);
72 /// assert_eq!(loc.column, 5);
73 /// ```
74 pub const fn new(line: u32, column: u32) -> Self {
75 Self { line, column }
76 }
77
78 /// Create a location at the start of a file
79 ///
80 /// ## Example
81 ///
82 /// ```
83 /// use roup::ir::SourceLocation;
84 ///
85 /// let start = SourceLocation::start();
86 /// assert_eq!(start.line, 1);
87 /// assert_eq!(start.column, 1);
88 /// ```
89 pub const fn start() -> Self {
90 Self::new(1, 1)
91 }
92}
93
94impl Default for SourceLocation {
95 /// Default location is at the start of a file (1, 1)
96 fn default() -> Self {
97 Self::start()
98 }
99}
100
101impl fmt::Display for SourceLocation {
102 /// Format as "line:column" for error messages
103 ///
104 /// ## Example
105 ///
106 /// ```
107 /// use roup::ir::SourceLocation;
108 ///
109 /// let loc = SourceLocation::new(42, 5);
110 /// assert_eq!(format!("{}", loc), "42:5");
111 /// ```
112 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
113 write!(f, "{}:{}", self.line, self.column)
114 }
115}
116
117// ============================================================================
118// Language: Identify source programming language
119// ============================================================================
120
121/// Source programming language
122///
123/// OpenMP supports multiple host languages: C, C++, and Fortran.
124/// The IR needs to track the source language because:
125///
126/// - **Pragma syntax**: C/C++ use `#pragma omp`, Fortran uses `!$omp`
127/// - **Expression parsing**: Different languages have different expression syntax
128/// - **Type systems**: Languages have different type semantics
129/// - **Pretty-printing**: Need to output correct syntax for the language
130///
131/// ## Learning: Enums as Tagged Unions
132///
133/// In Rust, `enum` is much more powerful than in C. Each variant can:
134/// - Carry no data (like these variants)
135/// - Carry data (we'll see this in later types)
136/// - Have different types of data per variant
137///
138/// ## Learning: `repr(C)` on Enums
139///
140/// For FFI, we need stable discriminant values. `#[repr(C)]` ensures:
141/// - Discriminant is a C-compatible integer
142/// - Size and alignment match C expectations
143/// - Variants have predictable numeric values
144///
145/// We explicitly assign values (0, 1, 2, 3) so C code can rely on them.
146///
147/// ## Example
148///
149/// ```
150/// use roup::ir::Language;
151///
152/// let lang = Language::C;
153/// assert_eq!(lang as u32, 0);
154///
155/// let cpp = Language::Cpp;
156/// assert_eq!(cpp as u32, 1);
157/// ```
158#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
159#[repr(C)]
160pub enum Language {
161 /// C language
162 ///
163 /// Uses `#pragma omp` syntax
164 C = 0,
165
166 /// C++ language
167 ///
168 /// Uses `#pragma omp` syntax (same as C)
169 Cpp = 1,
170
171 /// Fortran language
172 ///
173 /// Uses `!$omp` syntax
174 Fortran = 2,
175
176 /// Unknown or unspecified language
177 ///
178 /// Used when language cannot be determined
179 Unknown = 3,
180}
181
182impl Language {
183 /// Get the pragma prefix for this language
184 ///
185 /// Returns the string used to start OpenMP directives.
186 ///
187 /// ## Example
188 ///
189 /// ```
190 /// use roup::ir::Language;
191 ///
192 /// assert_eq!(Language::C.pragma_prefix(), "#pragma omp ");
193 /// assert_eq!(Language::Cpp.pragma_prefix(), "#pragma omp ");
194 /// assert_eq!(Language::Fortran.pragma_prefix(), "!$omp ");
195 /// ```
196 pub const fn pragma_prefix(self) -> &'static str {
197 match self {
198 Language::C | Language::Cpp => "#pragma omp ",
199 Language::Fortran => "!$omp ",
200 Language::Unknown => "#pragma omp ", // Default to C syntax
201 }
202 }
203
204 /// Check if this language uses C-style syntax
205 ///
206 /// Both C and C++ use the same OpenMP syntax.
207 ///
208 /// ## Example
209 ///
210 /// ```
211 /// use roup::ir::Language;
212 ///
213 /// assert!(Language::C.is_c_family());
214 /// assert!(Language::Cpp.is_c_family());
215 /// assert!(!Language::Fortran.is_c_family());
216 /// ```
217 pub const fn is_c_family(self) -> bool {
218 matches!(self, Language::C | Language::Cpp)
219 }
220
221 /// Check if this language is Fortran
222 ///
223 /// ## Example
224 ///
225 /// ```
226 /// use roup::ir::Language;
227 ///
228 /// assert!(!Language::C.is_fortran());
229 /// assert!(Language::Fortran.is_fortran());
230 /// ```
231 pub const fn is_fortran(self) -> bool {
232 matches!(self, Language::Fortran)
233 }
234}
235
236impl Default for Language {
237 /// Default to unknown language
238 fn default() -> Self {
239 Language::Unknown
240 }
241}
242
243impl fmt::Display for Language {
244 /// Format language name for display
245 ///
246 /// ## Example
247 ///
248 /// ```
249 /// use roup::ir::Language;
250 ///
251 /// assert_eq!(format!("{}", Language::C), "C");
252 /// assert_eq!(format!("{}", Language::Cpp), "C++");
253 /// assert_eq!(format!("{}", Language::Fortran), "Fortran");
254 /// ```
255 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
256 match self {
257 Language::C => write!(f, "C"),
258 Language::Cpp => write!(f, "C++"),
259 Language::Fortran => write!(f, "Fortran"),
260 Language::Unknown => write!(f, "Unknown"),
261 }
262 }
263}
264
265// ============================================================================
266// Tests
267// ============================================================================
268
269#[cfg(test)]
270mod tests {
271 use super::*;
272
273 // ------------------------------------------------------------------------
274 // SourceLocation tests
275 // ------------------------------------------------------------------------
276
277 #[test]
278 fn source_location_new_creates_correct_location() {
279 let loc = SourceLocation::new(42, 5);
280 assert_eq!(loc.line, 42);
281 assert_eq!(loc.column, 5);
282 }
283
284 #[test]
285 fn source_location_start_is_one_one() {
286 let start = SourceLocation::start();
287 assert_eq!(start.line, 1);
288 assert_eq!(start.column, 1);
289 }
290
291 #[test]
292 fn source_location_default_is_start() {
293 let default = SourceLocation::default();
294 let start = SourceLocation::start();
295 assert_eq!(default, start);
296 }
297
298 #[test]
299 fn source_location_copy_works() {
300 let loc1 = SourceLocation::new(10, 20);
301 let loc2 = loc1; // Should copy, not move
302 assert_eq!(loc1.line, 10); // loc1 still valid
303 assert_eq!(loc2.line, 10); // loc2 has same value
304 }
305
306 #[test]
307 fn source_location_display_formats_correctly() {
308 let loc = SourceLocation::new(42, 5);
309 assert_eq!(format!("{loc}"), "42:5");
310 }
311
312 #[test]
313 fn source_location_equality_works() {
314 let loc1 = SourceLocation::new(42, 5);
315 let loc2 = SourceLocation::new(42, 5);
316 let loc3 = SourceLocation::new(42, 6);
317
318 assert_eq!(loc1, loc2);
319 assert_ne!(loc1, loc3);
320 }
321
322 // ------------------------------------------------------------------------
323 // Language tests
324 // ------------------------------------------------------------------------
325
326 #[test]
327 fn language_has_correct_discriminants() {
328 assert_eq!(Language::C as u32, 0);
329 assert_eq!(Language::Cpp as u32, 1);
330 assert_eq!(Language::Fortran as u32, 2);
331 assert_eq!(Language::Unknown as u32, 3);
332 }
333
334 #[test]
335 fn language_pragma_prefix_c_and_cpp() {
336 assert_eq!(Language::C.pragma_prefix(), "#pragma omp ");
337 assert_eq!(Language::Cpp.pragma_prefix(), "#pragma omp ");
338 }
339
340 #[test]
341 fn language_pragma_prefix_fortran() {
342 assert_eq!(Language::Fortran.pragma_prefix(), "!$omp ");
343 }
344
345 #[test]
346 fn language_pragma_prefix_unknown_defaults_to_c() {
347 assert_eq!(Language::Unknown.pragma_prefix(), "#pragma omp ");
348 }
349
350 #[test]
351 fn language_is_c_family() {
352 assert!(Language::C.is_c_family());
353 assert!(Language::Cpp.is_c_family());
354 assert!(!Language::Fortran.is_c_family());
355 assert!(!Language::Unknown.is_c_family());
356 }
357
358 #[test]
359 fn language_is_fortran() {
360 assert!(!Language::C.is_fortran());
361 assert!(!Language::Cpp.is_fortran());
362 assert!(Language::Fortran.is_fortran());
363 assert!(!Language::Unknown.is_fortran());
364 }
365
366 #[test]
367 fn language_display_formats_correctly() {
368 assert_eq!(format!("{}", Language::C), "C");
369 assert_eq!(format!("{}", Language::Cpp), "C++");
370 assert_eq!(format!("{}", Language::Fortran), "Fortran");
371 assert_eq!(format!("{}", Language::Unknown), "Unknown");
372 }
373
374 #[test]
375 fn language_default_is_unknown() {
376 assert_eq!(Language::default(), Language::Unknown);
377 }
378
379 #[test]
380 fn language_copy_works() {
381 let lang1 = Language::C;
382 let lang2 = lang1; // Should copy, not move
383 assert_eq!(lang1, Language::C); // lang1 still valid
384 assert_eq!(lang2, Language::C); // lang2 has same value
385 }
386
387 #[test]
388 fn language_equality_works() {
389 assert_eq!(Language::C, Language::C);
390 assert_ne!(Language::C, Language::Cpp);
391 assert_ne!(Language::Fortran, Language::Unknown);
392 }
393}